mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-10-05 15:37:00 +08:00
Disable buttons backing non-implemented functions
This commit is contained in:
11
pkg/streamcontrol/capability.go
Normal file
11
pkg/streamcontrol/capability.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package streamcontrol
|
||||
|
||||
type Capability uint
|
||||
|
||||
const (
|
||||
CapabilityUndefined = Capability(iota)
|
||||
CapabilitySendChatMessage
|
||||
CapabilityDeleteChatMessage
|
||||
CapabilityBanUser
|
||||
EndOfCapability
|
||||
)
|
@@ -179,3 +179,18 @@ func (k *Kick) StartStream(
|
||||
logger.Warnf(ctx, "not implemented yet")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Kick) IsCapable(
|
||||
ctx context.Context,
|
||||
cap streamcontrol.Capability,
|
||||
) bool {
|
||||
switch cap {
|
||||
case streamcontrol.CapabilitySendChatMessage:
|
||||
return false
|
||||
case streamcontrol.CapabilityDeleteChatMessage:
|
||||
return false
|
||||
case streamcontrol.CapabilityBanUser:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@@ -284,3 +284,10 @@ func (obs *OBS) BanUser(
|
||||
) error {
|
||||
return fmt.Errorf("not implemented, yet")
|
||||
}
|
||||
|
||||
func (obs *OBS) IsCapable(
|
||||
ctx context.Context,
|
||||
cap streamcontrol.Capability,
|
||||
) bool {
|
||||
return false
|
||||
}
|
||||
|
@@ -131,6 +131,8 @@ type StreamControllerCommons interface {
|
||||
SendChatMessage(ctx context.Context, message string) error
|
||||
RemoveChatMessage(ctx context.Context, messageID ChatMessageID) error
|
||||
BanUser(ctx context.Context, userID ChatUserID, reason string, deadline time.Time) error
|
||||
|
||||
IsCapable(context.Context, Capability) bool
|
||||
}
|
||||
|
||||
type StreamController[ProfileType StreamProfile] interface {
|
||||
@@ -241,6 +243,9 @@ func (c *abstractStreamController) RemoveChatMessage(ctx context.Context, messag
|
||||
func (c *abstractStreamController) BanUser(ctx context.Context, userID ChatUserID, reason string, deadline time.Time) error {
|
||||
return c.StreamController.BanUser(ctx, userID, reason, deadline)
|
||||
}
|
||||
func (c *abstractStreamController) IsCapable(ctx context.Context, cap Capability) bool {
|
||||
return c.StreamController.IsCapable(ctx, cap)
|
||||
}
|
||||
|
||||
func ToAbstract[T StreamProfile](c StreamController[T]) AbstractStreamController {
|
||||
if c == nil {
|
||||
|
@@ -903,3 +903,18 @@ func (t *Twitch) BanUser(
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Twitch) IsCapable(
|
||||
ctx context.Context,
|
||||
cap streamcontrol.Capability,
|
||||
) bool {
|
||||
switch cap {
|
||||
case streamcontrol.CapabilitySendChatMessage:
|
||||
return true
|
||||
case streamcontrol.CapabilityDeleteChatMessage:
|
||||
return true
|
||||
case streamcontrol.CapabilityBanUser:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@@ -1400,3 +1400,18 @@ func (yt *YouTube) BanUser(
|
||||
) error {
|
||||
return fmt.Errorf("not implemented, yet")
|
||||
}
|
||||
|
||||
func (yt *YouTube) IsCapable(
|
||||
ctx context.Context,
|
||||
cap streamcontrol.Capability,
|
||||
) bool {
|
||||
switch cap {
|
||||
case streamcontrol.CapabilitySendChatMessage:
|
||||
return true
|
||||
case streamcontrol.CapabilityDeleteChatMessage:
|
||||
return true
|
||||
case streamcontrol.CapabilityBanUser:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
10
pkg/streamd/api/backend_info.go
Normal file
10
pkg/streamd/api/backend_info.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||
)
|
||||
|
||||
type BackendInfo struct {
|
||||
Data any
|
||||
Capabilities map[streamcontrol.Capability]struct{}
|
||||
}
|
@@ -66,10 +66,10 @@ type StreamD interface {
|
||||
profile streamcontrol.AbstractStreamProfile,
|
||||
customArgs ...any,
|
||||
) error
|
||||
GetBackendData(
|
||||
GetBackendInfo(
|
||||
ctx context.Context,
|
||||
platID streamcontrol.PlatformName,
|
||||
) (any, error)
|
||||
) (*BackendInfo, error)
|
||||
EXPERIMENTAL_ReinitStreamControllers(ctx context.Context) error
|
||||
GetStreamStatus(
|
||||
ctx context.Context,
|
||||
|
@@ -26,9 +26,6 @@ import (
|
||||
"github.com/xaionaro-go/streamctl/pkg/player"
|
||||
"github.com/xaionaro-go/streamctl/pkg/player/protobuf/go/player_grpc"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
||||
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
||||
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamd/api"
|
||||
streamdconfig "github.com/xaionaro-go/streamctl/pkg/streamd/config"
|
||||
@@ -710,10 +707,10 @@ func (c *Client) EndStream(
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) GetBackendData(
|
||||
func (c *Client) GetBackendInfo(
|
||||
ctx context.Context,
|
||||
platID streamcontrol.PlatformName,
|
||||
) (any, error) {
|
||||
) (*api.BackendInfo, error) {
|
||||
reply, err := withStreamDClient(ctx, c, func(
|
||||
ctx context.Context,
|
||||
client streamd_grpc.StreamDClient,
|
||||
@@ -735,50 +732,17 @@ func (c *Client) GetBackendData(
|
||||
)
|
||||
}
|
||||
|
||||
var data any
|
||||
switch platID {
|
||||
case obs.ID:
|
||||
_data := api.BackendDataOBS{}
|
||||
err = json.Unmarshal(
|
||||
[]byte(reply.GetData()),
|
||||
&_data,
|
||||
)
|
||||
data = _data
|
||||
case twitch.ID:
|
||||
_data := api.BackendDataTwitch{}
|
||||
err = json.Unmarshal(
|
||||
[]byte(reply.GetData()),
|
||||
&_data,
|
||||
)
|
||||
data = _data
|
||||
case kick.ID:
|
||||
_data := api.BackendDataKick{}
|
||||
err = json.Unmarshal(
|
||||
[]byte(reply.GetData()),
|
||||
&_data,
|
||||
)
|
||||
data = _data
|
||||
case youtube.ID:
|
||||
_data := api.BackendDataYouTube{}
|
||||
err = json.Unmarshal(
|
||||
[]byte(reply.GetData()),
|
||||
&_data,
|
||||
)
|
||||
data = _data
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"unknown platform: '%s'",
|
||||
platID,
|
||||
)
|
||||
caps := goconv.CapabilitiesGRPC2Go(ctx, reply.Capabilities)
|
||||
|
||||
data, err := goconv.BackendDataGRPC2Go(platID, reply.GetData())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to deserialize data: %w", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"unable to deserialize data: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
return data, nil
|
||||
return &api.BackendInfo{
|
||||
Data: data,
|
||||
Capabilities: caps,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Restart(ctx context.Context) error {
|
||||
|
File diff suppressed because it is too large
Load Diff
45
pkg/streamd/grpc/goconv/backend_info.go
Normal file
45
pkg/streamd/grpc/goconv/backend_info.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package goconv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamd/api"
|
||||
)
|
||||
|
||||
func BackendDataGRPC2Go(
|
||||
platID streamcontrol.PlatformName,
|
||||
dataString string,
|
||||
) (any, error) {
|
||||
var data any
|
||||
var err error
|
||||
switch platID {
|
||||
case obs.ID:
|
||||
_data := api.BackendDataOBS{}
|
||||
err = json.Unmarshal([]byte(dataString), &_data)
|
||||
data = _data
|
||||
case twitch.ID:
|
||||
_data := api.BackendDataTwitch{}
|
||||
err = json.Unmarshal([]byte(dataString), &_data)
|
||||
data = _data
|
||||
case kick.ID:
|
||||
_data := api.BackendDataKick{}
|
||||
err = json.Unmarshal([]byte(dataString), &_data)
|
||||
data = _data
|
||||
case youtube.ID:
|
||||
_data := api.BackendDataYouTube{}
|
||||
err = json.Unmarshal([]byte(dataString), &_data)
|
||||
data = _data
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"unknown platform: '%s'",
|
||||
platID,
|
||||
)
|
||||
}
|
||||
return data, err
|
||||
}
|
57
pkg/streamd/grpc/goconv/capabilities.go
Normal file
57
pkg/streamd/grpc/goconv/capabilities.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package goconv
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc"
|
||||
)
|
||||
|
||||
func CapabilitiesGRPC2Go(
|
||||
ctx context.Context,
|
||||
caps []streamd_grpc.Capability,
|
||||
) map[streamcontrol.Capability]struct{} {
|
||||
m := map[streamcontrol.Capability]struct{}{}
|
||||
|
||||
for _, cap := range caps {
|
||||
var r streamcontrol.Capability
|
||||
switch cap {
|
||||
case streamd_grpc.Capability_SendChatMessage:
|
||||
r = streamcontrol.CapabilitySendChatMessage
|
||||
case streamd_grpc.Capability_DeleteChatMessage:
|
||||
r = streamcontrol.CapabilityDeleteChatMessage
|
||||
case streamd_grpc.Capability_BanUser:
|
||||
r = streamcontrol.CapabilityBanUser
|
||||
default:
|
||||
logger.Warnf(ctx, "unexpected capability: %v", cap)
|
||||
continue
|
||||
}
|
||||
m[r] = struct{}{}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func CapabilitiesGo2GRPC(
|
||||
ctx context.Context,
|
||||
caps map[streamcontrol.Capability]struct{},
|
||||
) []streamd_grpc.Capability {
|
||||
var result []streamd_grpc.Capability
|
||||
for cap := range caps {
|
||||
var item streamd_grpc.Capability
|
||||
switch cap {
|
||||
case streamcontrol.CapabilitySendChatMessage:
|
||||
item = streamd_grpc.Capability_SendChatMessage
|
||||
case streamcontrol.CapabilityDeleteChatMessage:
|
||||
item = streamd_grpc.Capability_DeleteChatMessage
|
||||
case streamcontrol.CapabilityBanUser:
|
||||
item = streamd_grpc.Capability_BanUser
|
||||
default:
|
||||
logger.Warnf(ctx, "unexpected capability: %v", cap)
|
||||
continue
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
return result
|
||||
}
|
@@ -160,6 +160,7 @@ message GetBackendInfoRequest {
|
||||
message GetBackendInfoReply {
|
||||
bool isInitialized = 1;
|
||||
string data = 2;
|
||||
repeated Capability capabilities = 3;
|
||||
}
|
||||
message IsBackendEnabledRequest {
|
||||
string platID = 1;
|
||||
@@ -167,6 +168,12 @@ message IsBackendEnabledRequest {
|
||||
message IsBackendEnabledReply {
|
||||
bool isInitialized = 1;
|
||||
}
|
||||
enum Capability {
|
||||
capabilityUndefined = 0;
|
||||
SendChatMessage = 1;
|
||||
DeleteChatMessage = 2;
|
||||
BanUser = 3;
|
||||
}
|
||||
message RestartRequest {}
|
||||
message RestartReply {}
|
||||
|
||||
|
@@ -334,14 +334,15 @@ func (grpc *GRPCServer) GetBackendInfo(
|
||||
err,
|
||||
)
|
||||
}
|
||||
data, err := grpc.StreamD.GetBackendData(ctx, platID)
|
||||
info, err := grpc.StreamD.GetBackendInfo(ctx, platID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"unable to get the backend info: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
dataSerialized, err := json.Marshal(data)
|
||||
|
||||
dataSerialized, err := json.Marshal(info.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"unable to serialize the backend info: %w",
|
||||
@@ -352,6 +353,7 @@ func (grpc *GRPCServer) GetBackendInfo(
|
||||
return &streamd_grpc.GetBackendInfoReply{
|
||||
IsInitialized: isEnabled,
|
||||
Data: string(dataSerialized),
|
||||
Capabilities: goconv.CapabilitiesGo2GRPC(ctx, info.Capabilities),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@@ -680,9 +680,37 @@ func (d *StreamD) EndStream(ctx context.Context, platID streamcontrol.PlatformNa
|
||||
})
|
||||
}
|
||||
|
||||
func (d *StreamD) GetBackendData(
|
||||
func (d *StreamD) GetBackendInfo(
|
||||
ctx context.Context,
|
||||
platID streamcontrol.PlatformName,
|
||||
) (*api.BackendInfo, error) {
|
||||
ctrl, err := d.streamController(ctx, platID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get stream controller for platform '%s': %w", platID, err)
|
||||
}
|
||||
|
||||
caps := map[streamcontrol.Capability]struct{}{}
|
||||
for cap := streamcontrol.CapabilityUndefined + 1; cap < streamcontrol.EndOfCapability; cap++ {
|
||||
isCapable := ctrl.IsCapable(ctx, cap)
|
||||
if isCapable {
|
||||
caps[cap] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
data, err := d.getBackendData(ctx, platID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get backend data: %w", err)
|
||||
}
|
||||
|
||||
return &api.BackendInfo{
|
||||
Data: data,
|
||||
Capabilities: caps,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *StreamD) getBackendData(
|
||||
_ context.Context,
|
||||
platID streamcontrol.PlatformName,
|
||||
) (any, error) {
|
||||
switch platID {
|
||||
case obs.ID:
|
||||
|
@@ -31,6 +31,9 @@ type chatUI struct {
|
||||
MessagesHistoryLocker sync.Mutex
|
||||
MessagesHistory []api.ChatMessage
|
||||
|
||||
CapabilitiesCacheLocker sync.Mutex
|
||||
CapabilitiesCache map[streamcontrol.PlatformName]map[streamcontrol.Capability]struct{}
|
||||
|
||||
CurrentlyPlayingChatMessageSoundCount int32
|
||||
|
||||
// TODO: do not store ctx in a struct:
|
||||
@@ -42,8 +45,9 @@ func newChatUI(
|
||||
panel *Panel,
|
||||
) (*chatUI, error) {
|
||||
ui := &chatUI{
|
||||
Panel: panel,
|
||||
ctx: ctx,
|
||||
Panel: panel,
|
||||
CapabilitiesCache: make(map[streamcontrol.PlatformName]map[streamcontrol.Capability]struct{}),
|
||||
ctx: ctx,
|
||||
}
|
||||
if err := ui.init(ctx); err != nil {
|
||||
return nil, err
|
||||
@@ -224,6 +228,28 @@ func (ui *chatUI) listCreateItem() fyne.CanvasObject {
|
||||
)
|
||||
}
|
||||
|
||||
func (ui *chatUI) getPlatformCapabilities(
|
||||
ctx context.Context,
|
||||
platID streamcontrol.PlatformName,
|
||||
) (_ret map[streamcontrol.Capability]struct{}, _err error) {
|
||||
logger.Debugf(ctx, "getPlatformCapabilities(ctx, '%s')", platID)
|
||||
defer func() { logger.Debugf(ctx, "/getPlatformCapabilities(ctx, '%s'): %#+v, %v", platID, _ret, _err) }()
|
||||
ui.CapabilitiesCacheLocker.Lock()
|
||||
defer ui.CapabilitiesCacheLocker.Unlock()
|
||||
|
||||
if m, ok := ui.CapabilitiesCache[platID]; ok {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
info, err := ui.Panel.StreamD.GetBackendInfo(ctx, platID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetBackendInfo returned error: %w", err)
|
||||
}
|
||||
|
||||
ui.CapabilitiesCache[platID] = info.Capabilities
|
||||
return info.Capabilities, nil
|
||||
}
|
||||
|
||||
func (ui *chatUI) listUpdateItem(
|
||||
rowID int,
|
||||
obj fyne.CanvasObject,
|
||||
@@ -234,6 +260,12 @@ func (ui *chatUI) listUpdateItem(
|
||||
entryID := len(ui.MessagesHistory) - 1 - rowID
|
||||
msg := ui.MessagesHistory[entryID]
|
||||
|
||||
platCaps, err := ui.getPlatformCapabilities(ctx, msg.Platform)
|
||||
if err != nil {
|
||||
ui.Panel.ReportError(fmt.Errorf("unable to get capabilities of platform '%s': %w", msg.Platform, err))
|
||||
platCaps = map[streamcontrol.Capability]struct{}{}
|
||||
}
|
||||
|
||||
containerPtr := obj.(*fyne.Container)
|
||||
objs := containerPtr.Objects
|
||||
label := objs[0].(*widget.Label)
|
||||
@@ -252,6 +284,11 @@ func (ui *chatUI) listUpdateItem(
|
||||
)
|
||||
w.Show()
|
||||
}
|
||||
if _, ok := platCaps[streamcontrol.CapabilityBanUser]; !ok {
|
||||
banUserButton.Disable()
|
||||
} else {
|
||||
banUserButton.Enable()
|
||||
}
|
||||
removeMsgButton := subContainer.Objects[1].(*widget.Button)
|
||||
removeMsgButton.OnTapped = func() {
|
||||
w := dialog.NewConfirm(
|
||||
@@ -267,6 +304,11 @@ func (ui *chatUI) listUpdateItem(
|
||||
)
|
||||
w.Show()
|
||||
}
|
||||
if _, ok := platCaps[streamcontrol.CapabilityDeleteChatMessage]; !ok {
|
||||
removeMsgButton.Disable()
|
||||
} else {
|
||||
removeMsgButton.Enable()
|
||||
}
|
||||
label.SetText(fmt.Sprintf(
|
||||
"%s: %s: %s: %s",
|
||||
msg.CreatedAt.Format("15:04"),
|
||||
|
@@ -3222,14 +3222,14 @@ func (p *Panel) profileWindow(
|
||||
}
|
||||
backendEnabled[backendID] = isEnabled
|
||||
|
||||
data, err := p.StreamD.GetBackendData(ctx, backendID)
|
||||
info, err := p.StreamD.GetBackendInfo(ctx, backendID)
|
||||
if err != nil {
|
||||
w.Close()
|
||||
p.DisplayError(fmt.Errorf("unable to get data of backend '%s': %w", backendID, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
backendData[backendID] = data
|
||||
backendData[backendID] = info.Data
|
||||
}
|
||||
_ = backendData[obs.ID].(api.BackendDataOBS)
|
||||
dataTwitch := backendData[twitch.ID].(api.BackendDataTwitch)
|
||||
|
@@ -406,6 +406,7 @@ func (p *Panel) displayIncomingServers(
|
||||
playButton := widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() {
|
||||
p.DisplayError(fmt.Errorf("playback is not implemented, yet"))
|
||||
})
|
||||
playButton.Hide() // TODO: unhide when it will be implemented
|
||||
deleteButton := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() {
|
||||
w := dialog.NewConfirm(
|
||||
fmt.Sprintf("Delete incoming server %s ?", stream.StreamID),
|
||||
|
Reference in New Issue
Block a user