From 35a1bec9c37aab4dbc20c9cafe48c995d64d4700 Mon Sep 17 00:00:00 2001 From: Dmitrii Okunev Date: Sun, 20 Oct 2024 22:00:02 +0100 Subject: [PATCH] Add the boilerplate code for Kick --- cmd/streamcli/commands/commands.go | 3 +- cmd/streamctl/commands/commands.go | 55 ++++++++++- cmd/streamd/main.go | 4 + cmd/streamd/ui/ui.go | 24 +++++ cmd/streampanel/streamd.go | 7 ++ pkg/streamcontrol/kick/kick.go | 19 +++- pkg/streamd/api/streamd.go | 3 + pkg/streamd/client/client.go | 8 ++ pkg/streamd/config/config.go | 5 + pkg/streamd/config/read.go | 11 +++ pkg/streamd/grpc/goconv/profile.go | 3 + .../platcollection/new_stream_profile.go | 3 + pkg/streamd/stream_controller.go | 62 +++++++++++++ pkg/streamd/stream_forward.go | 3 + pkg/streamd/streamd.go | 43 +++++++++ pkg/streamd/ui/ui.go | 6 ++ pkg/streampanel/monitor.go | 2 + pkg/streampanel/panel.go | 92 +++++++++++++++++++ pkg/streampanel/timers.go | 2 + 19 files changed, 348 insertions(+), 7 deletions(-) diff --git a/cmd/streamcli/commands/commands.go b/cmd/streamcli/commands/commands.go index abade39..b37834f 100644 --- a/cmd/streamcli/commands/commands.go +++ b/cmd/streamcli/commands/commands.go @@ -16,6 +16,7 @@ import ( "github.com/spf13/cobra" "github.com/xaionaro-go/streamctl/pkg/observability" "github.com/xaionaro-go/streamctl/pkg/streamcontrol" + kick "github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick/types" 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" @@ -216,7 +217,7 @@ func streamStatus(cmd *cobra.Command, args []string) { result := map[streamcontrol.PlatformName]*streamcontrol.StreamStatus{} for _, platID := range []streamcontrol.PlatformName{ - obs.ID, twitch.ID, youtube.ID, + obs.ID, twitch.ID, youtube.ID, kick.ID, } { isEnabled, err := streamD.IsBackendEnabled(ctx, platID) assertNoError(ctx, err) diff --git a/cmd/streamctl/commands/commands.go b/cmd/streamctl/commands/commands.go index 64943ca..8d90df7 100644 --- a/cmd/streamctl/commands/commands.go +++ b/cmd/streamctl/commands/commands.go @@ -12,6 +12,7 @@ import ( "github.com/goccy/go-yaml" "github.com/spf13/cobra" "github.com/xaionaro-go/streamctl/pkg/streamcontrol" + "github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick" "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch" "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube" "github.com/xaionaro-go/streamctl/pkg/xsync" @@ -138,12 +139,14 @@ func expandPath(rawPath string) string { const ( idTwitch = twitch.ID + idKick = kick.ID idYoutube = youtube.ID ) func newConfig() streamcontrol.Config { cfg := streamcontrol.Config{} twitch.InitConfig(cfg) + kick.InitConfig(cfg) youtube.InitConfig(cfg) return cfg } @@ -157,6 +160,9 @@ func generateConfig(cmd *cobra.Command, args []string) { cfg[idTwitch].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{ "some_profile": twitch.StreamProfile{}, } + cfg[idKick].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{ + "some_profile": kick.StreamProfile{}, + } cfg[idYoutube].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{ "some_profile": youtube.StreamProfile{}, } @@ -218,13 +224,24 @@ func readConfigFromPath( logger.Debugf(ctx, "final stream profiles of twitch: %#+v", (*cfg)[idTwitch].StreamProfiles) } + if (*cfg)[idKick] != nil { + err = streamcontrol.ConvertStreamProfiles[kick.StreamProfile]( + ctx, + (*cfg)[idKick].StreamProfiles, + ) + if err != nil { + return fmt.Errorf("unable to convert stream profiles of kick: %w: <%s>", err, b) + } + logger.Debugf(ctx, "final stream profiles of kick: %#+v", (*cfg)[idKick].StreamProfiles) + } + if (*cfg)[idYoutube] != nil { err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile]( ctx, (*cfg)[idYoutube].StreamProfiles, ) if err != nil { - return fmt.Errorf("unable to convert stream profiles of twitch: %w: <%s>", err, b) + return fmt.Errorf("unable to convert stream profiles of youtube: %w: <%s>", err, b) } logger.Debugf( ctx, @@ -283,6 +300,34 @@ func getTwitchStreamController( ) } +func getKickStreamController( + ctx context.Context, + cfg streamcontrol.Config, +) (*kick.Kick, error) { + platCfg := streamcontrol.GetPlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile]( + ctx, + cfg, + idKick, + ) + if platCfg == nil { + logger.Infof(ctx, "kick config was not found") + return nil, nil + } + + logger.Debugf(ctx, "kick config: %#+v", platCfg) + return kick.New(ctx, *platCfg, + func(c kick.Config) error { + return xsync.DoR1(ctx, &saveConfigLock, func() error { + cfg[idKick] = &streamcontrol.AbstractPlatformConfig{ + Config: c.Config, + StreamProfiles: streamcontrol.ToAbstractStreamProfiles(c.StreamProfiles), + } + return saveConfig(ctx, cfg) + }) + }, + ) +} + func getYouTubeStreamController( ctx context.Context, cfg streamcontrol.Config, @@ -325,6 +370,14 @@ func getStreamControllers( result = append(result, streamcontrol.ToAbstract(twitch)) } + kick, err := getKickStreamController(ctx, cfg) + if err != nil { + logger.Panic(ctx, err) + } + if kick != nil { + result = append(result, streamcontrol.ToAbstract(kick)) + } + youtube, err := getYouTubeStreamController(ctx, cfg) if err != nil { logger.Panic(ctx, err) diff --git a/cmd/streamd/main.go b/cmd/streamd/main.go index 9f8456f..4b2aea5 100644 --- a/cmd/streamd/main.go +++ b/cmd/streamd/main.go @@ -23,6 +23,7 @@ import ( "github.com/xaionaro-go/streamctl/cmd/streamd/ui" "github.com/xaionaro-go/streamctl/pkg/observability" "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" @@ -265,6 +266,9 @@ func main() { func(ctx context.Context, cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile]) (bool, error) { return false, streamd.ErrSkipBackend }, + func(ctx context.Context, cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile]) (bool, error) { + return false, streamd.ErrSkipBackend + }, func(ctx context.Context, cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile]) (bool, error) { return false, streamd.ErrSkipBackend }, diff --git a/cmd/streamd/ui/ui.go b/cmd/streamd/ui/ui.go index 5933384..77334ff 100644 --- a/cmd/streamd/ui/ui.go +++ b/cmd/streamd/ui/ui.go @@ -9,6 +9,7 @@ import ( "github.com/facebookincubator/go-belt/tool/logger" "github.com/xaionaro-go/streamctl/pkg/oauthhandler" "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" @@ -28,6 +29,10 @@ type UI struct { ctx context.Context, cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile], ) (bool, error) + InputKickUserInfoFn func( + ctx context.Context, + cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile], + ) (bool, error) InputYouTubeUserInfoFn func( ctx context.Context, cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile], @@ -50,6 +55,10 @@ func NewUI( ctx context.Context, cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile], ) (bool, error), + inputKickUserInfoFn func( + ctx context.Context, + cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile], + ) (bool, error), inputYouTubeUserInfoFn func( ctx context.Context, cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile], @@ -68,6 +77,7 @@ func NewUI( CodeChMapLocker: xsync.RWMutex{}, SetLoggingLevelFn: setLoggingLevel, InputTwitchUserInfoFn: inputTwitchUserInfoFn, + InputKickUserInfoFn: inputKickUserInfoFn, InputYouTubeUserInfoFn: inputYouTubeUserInfoFn, InputOBSConnectInfoFn: inputOBSConnectInfoFn, } @@ -220,6 +230,13 @@ func (ui *UI) OAuthHandlerTwitch( return ui.oauth2Handler(ctx, twitch.ID, arg) } +func (ui *UI) OAuthHandlerKick( + ctx context.Context, + arg oauthhandler.OAuthHandlerArgument, +) error { + return ui.oauth2Handler(ctx, kick.ID, arg) +} + func (ui *UI) OAuthHandlerYouTube( ctx context.Context, arg oauthhandler.OAuthHandlerArgument, @@ -234,6 +251,13 @@ func (ui *UI) InputTwitchUserInfo( return ui.InputTwitchUserInfoFn(ctx, cfg) } +func (ui *UI) InputKickUserInfo( + ctx context.Context, + cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile], +) (bool, error) { + return ui.InputKickUserInfoFn(ctx, cfg) +} + func (ui *UI) InputYouTubeUserInfo( ctx context.Context, cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile], diff --git a/cmd/streampanel/streamd.go b/cmd/streampanel/streamd.go index 7d4a0e4..21c71e6 100644 --- a/cmd/streampanel/streamd.go +++ b/cmd/streampanel/streamd.go @@ -19,6 +19,7 @@ import ( "github.com/xaionaro-go/streamctl/pkg/mainprocess" "github.com/xaionaro-go/streamctl/pkg/observability" "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" @@ -147,6 +148,12 @@ func runStreamd( ) (bool, error) { return false, streamd.ErrSkipBackend }, + func( + ctx context.Context, + cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile], + ) (bool, error) { + return false, streamd.ErrSkipBackend + }, func( ctx context.Context, cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile], diff --git a/pkg/streamcontrol/kick/kick.go b/pkg/streamcontrol/kick/kick.go index ca3a2f9..0b56fd3 100644 --- a/pkg/streamcontrol/kick/kick.go +++ b/pkg/streamcontrol/kick/kick.go @@ -19,26 +19,35 @@ type Kick struct { Channel *kickcom.ChannelV1 Client Client ChatHandler *ChatHandler + SaveCfgFn func(Config) error } var _ streamcontrol.StreamController[StreamProfile] = (*Kick)(nil) -func New(channelSlug string) (*Kick, error) { - ctx := context.TODO() +func New( + ctx context.Context, + cfg Config, + saveCfgFn func(Config) error, +) (*Kick, error) { + + if cfg.Config.Channel == "" { + return nil, fmt.Errorf("channel is not set") + } client, err := kickcom.New() if err != nil { return nil, fmt.Errorf("unable to initialize a client to Kick: %w", err) } - channel, err := client.GetChannelV1(ctx, channelSlug) + channel, err := client.GetChannelV1(ctx, cfg.Config.Channel) if err != nil { return nil, fmt.Errorf("unable to obtain channel info: %w", err) } k := &Kick{ - Client: client, - Channel: channel, + Client: client, + Channel: channel, + SaveCfgFn: saveCfgFn, } chatHandler, err := k.newChatHandler(ctx, channel.ID) diff --git a/pkg/streamd/api/streamd.go b/pkg/streamd/api/streamd.go index a3c3717..500274b 100644 --- a/pkg/streamd/api/streamd.go +++ b/pkg/streamd/api/streamd.go @@ -298,6 +298,9 @@ type BackendDataTwitch struct { Cache cache.Twitch } +type BackendDataKick struct { +} + type BackendDataYouTube struct { Cache cache.YouTube } diff --git a/pkg/streamd/client/client.go b/pkg/streamd/client/client.go index df8fe7f..f10b757 100644 --- a/pkg/streamd/client/client.go +++ b/pkg/streamd/client/client.go @@ -23,6 +23,7 @@ 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" @@ -802,6 +803,13 @@ func (c *Client) GetBackendData( &_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( diff --git a/pkg/streamd/config/config.go b/pkg/streamd/config/config.go index f0a102e..4ed67cf 100644 --- a/pkg/streamd/config/config.go +++ b/pkg/streamd/config/config.go @@ -7,6 +7,7 @@ import ( "github.com/facebookincubator/go-belt/tool/logger" "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" @@ -35,6 +36,7 @@ func NewConfig() Config { cfg := streamcontrol.Config{} obs.InitConfig(cfg) twitch.InitConfig(cfg) + kick.InitConfig(cfg) youtube.InitConfig(cfg) return Config{ Backends: cfg, @@ -54,6 +56,9 @@ func NewSampleConfig() Config { cfg.Backends[twitch.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{ "some_profile": twitch.StreamProfile{}, } + cfg.Backends[kick.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{ + "some_profile": kick.StreamProfile{}, + } cfg.Backends[youtube.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{ "some_profile": youtube.StreamProfile{}, } diff --git a/pkg/streamd/config/read.go b/pkg/streamd/config/read.go index 3c3295c..9ab1011 100644 --- a/pkg/streamd/config/read.go +++ b/pkg/streamd/config/read.go @@ -11,6 +11,7 @@ import ( "github.com/goccy/go-yaml" "github.com/xaionaro-go/streamctl/pkg/observability" "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" @@ -89,6 +90,16 @@ func (cfg *Config) UnmarshalYAML(b []byte) (_err error) { } } + if cfg.Backends[kick.ID] != nil { + err = streamcontrol.ConvertStreamProfiles[kick.StreamProfile]( + context.Background(), + cfg.Backends[kick.ID].StreamProfiles, + ) + if err != nil { + return fmt.Errorf("unable to convert stream profiles of kick: %w", err) + } + } + if cfg.Backends[youtube.ID] != nil { err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile]( context.Background(), diff --git a/pkg/streamd/grpc/goconv/profile.go b/pkg/streamd/grpc/goconv/profile.go index cf0fd5d..830b034 100644 --- a/pkg/streamd/grpc/goconv/profile.go +++ b/pkg/streamd/grpc/goconv/profile.go @@ -6,6 +6,7 @@ import ( "github.com/goccy/go-yaml" "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" @@ -22,6 +23,8 @@ func ProfileGRPC2Go( profile = &obs.StreamProfile{} case twitch.ID: profile = &twitch.StreamProfile{} + case kick.ID: + profile = &kick.StreamProfile{} case youtube.ID: profile = &youtube.StreamProfile{} default: diff --git a/pkg/streamd/platcollection/new_stream_profile.go b/pkg/streamd/platcollection/new_stream_profile.go index 32cd664..f0ebf1e 100644 --- a/pkg/streamd/platcollection/new_stream_profile.go +++ b/pkg/streamd/platcollection/new_stream_profile.go @@ -4,6 +4,7 @@ import ( "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" @@ -15,6 +16,8 @@ func NewStreamProfile( switch platID { case twitch.ID: return &twitch.StreamProfile{}, nil + case kick.ID: + return &kick.StreamProfile{}, nil case youtube.ID: return &youtube.StreamProfile{}, nil case obs.ID: diff --git a/pkg/streamd/stream_controller.go b/pkg/streamd/stream_controller.go index 4490417..846748b 100644 --- a/pkg/streamd/stream_controller.go +++ b/pkg/streamd/stream_controller.go @@ -14,6 +14,7 @@ import ( "github.com/facebookincubator/go-belt/tool/logger" "github.com/hashicorp/go-multierror" "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" @@ -37,6 +38,8 @@ func (d *StreamD) EXPERIMENTAL_ReinitStreamControllers(ctx context.Context) erro err = d.initOBSBackend(ctx) case strings.ToLower(string(twitch.ID)): err = d.initTwitchBackend(ctx) + case strings.ToLower(string(kick.ID)): + err = d.initKickBackend(ctx) case strings.ToLower(string(youtube.ID)): err = d.initYouTubeBackend(ctx) } @@ -196,6 +199,47 @@ func newTwitch( return twitch, nil } +func newKick( + ctx context.Context, + cfg *streamcontrol.AbstractPlatformConfig, + setUserData func(context.Context, *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile]) (bool, error), + saveCfgFunc func(*streamcontrol.AbstractPlatformConfig) error, + customOAuthHandler kick.OAuthHandler, + getOAuthListenPorts func() []uint16, +) ( + *kick.Kick, + error, +) { + platCfg := streamcontrol.ConvertPlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile]( + ctx, + cfg, + ) + if platCfg == nil { + return nil, fmt.Errorf("kick config was not found") + } + + if cfg.Enable != nil && !*cfg.Enable { + return nil, ErrSkipBackend + } + + logger.Debugf(ctx, "kick config: %#+v", platCfg) + cfg = streamcontrol.ToAbstractPlatformConfig(ctx, platCfg) + kick, err := kick.New(ctx, *platCfg, + func(c kick.Config) error { + return saveCfgFunc(&streamcontrol.AbstractPlatformConfig{ + Enable: c.Enable, + Config: c.Config, + StreamProfiles: streamcontrol.ToAbstractStreamProfiles(c.StreamProfiles), + Custom: c.Custom, + }) + }, + ) + if err != nil { + return nil, fmt.Errorf("unable to initialize Kick client: %w", err) + } + return kick, nil +} + func newYouTube( ctx context.Context, cfg *streamcontrol.AbstractPlatformConfig, @@ -365,6 +409,24 @@ func (d *StreamD) initTwitchBackend(ctx context.Context) error { return nil } +func (d *StreamD) initKickBackend(ctx context.Context) error { + kick, err := newKick( + ctx, + d.Config.Backends[kick.ID], + d.UI.InputKickUserInfo, + func(cfg *streamcontrol.AbstractPlatformConfig) error { + return d.setPlatformConfig(ctx, kick.ID, cfg) + }, + d.UI.OAuthHandlerKick, + d.GetOAuthListenPorts, + ) + if err != nil { + return err + } + d.StreamControllers.Kick = kick + return nil +} + func (d *StreamD) initYouTubeBackend(ctx context.Context) error { youTube, err := newYouTube( ctx, diff --git a/pkg/streamd/stream_forward.go b/pkg/streamd/stream_forward.go index 9315450..b8421d5 100644 --- a/pkg/streamd/stream_forward.go +++ b/pkg/streamd/stream_forward.go @@ -8,6 +8,7 @@ import ( "github.com/facebookincubator/go-belt/tool/logger" "github.com/xaionaro-go/streamctl/pkg/streamcontrol" + "github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick" 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" @@ -35,6 +36,8 @@ func (a *platformsControllerAdapter) CheckStreamStartedByURL( platID = youtube.ID case strings.Contains(destination.Hostname(), "twitch"): platID = twitch.ID + case strings.Contains(destination.Hostname(), "global-contribute.live-video.net"): + platID = kick.ID default: return false, fmt.Errorf( "do not know how to check if the stream started for '%s'", diff --git a/pkg/streamd/streamd.go b/pkg/streamd/streamd.go index 2df5c25..a6f843f 100644 --- a/pkg/streamd/streamd.go +++ b/pkg/streamd/streamd.go @@ -19,6 +19,7 @@ import ( "github.com/xaionaro-go/streamctl/pkg/player" "github.com/xaionaro-go/streamctl/pkg/repository" "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" @@ -42,6 +43,7 @@ import ( type StreamControllers struct { OBS *obs.OBS Twitch *twitch.Twitch + Kick *kick.Kick YouTube *youtube.YouTube } @@ -450,6 +452,8 @@ func (d *StreamD) IsBackendEnabled( return d.StreamControllers.OBS != nil, nil case twitch.ID: return d.StreamControllers.Twitch != nil, nil + case kick.ID: + return d.StreamControllers.Kick != nil, nil case youtube.ID: return d.StreamControllers.YouTube != nil, nil default: @@ -518,6 +522,21 @@ func (d *StreamD) StartStream( return fmt.Errorf("unable to start the stream on Twitch: %w", err) } return nil + case kick.ID: + profile, err := streamcontrol.GetStreamProfile[kick.StreamProfile](ctx, profile) + if err != nil { + return fmt.Errorf("unable to convert the profile into Twitch profile: %w", err) + } + err = d.StreamControllers.Kick.StartStream( + d.ctxForController(ctx), + title, + description, + *profile, + customArgs...) + if err != nil { + return fmt.Errorf("unable to start the stream on Twitch: %w", err) + } + return nil case youtube.ID: profile, err := streamcontrol.GetStreamProfile[youtube.StreamProfile](ctx, profile) if err != nil { @@ -617,6 +636,8 @@ func (d *StreamD) GetBackendData( return api.BackendDataOBS{}, nil case twitch.ID: return api.BackendDataTwitch{Cache: d.Cache.Twitch}, nil + case kick.ID: + return api.BackendDataKick{}, nil case youtube.ID: return api.BackendDataYouTube{Cache: d.Cache.Youtube}, nil default: @@ -644,6 +665,21 @@ func (d *StreamD) tryConnectTwitch( errmon.ObserveErrorCtx(ctx, err) } +func (d *StreamD) tryConnectKick( + ctx context.Context, +) { + if d.StreamControllers.Kick != nil { + return + } + + if _, ok := d.Config.Backends[kick.ID]; !ok { + return + } + + err := d.initKickBackend(ctx) + errmon.ObserveErrorCtx(ctx, err) +} + func (d *StreamD) tryConnectYouTube( ctx context.Context, ) { @@ -676,6 +712,13 @@ func (d *StreamD) streamController( if d.StreamControllers.Twitch != nil { result = streamcontrol.ToAbstract(d.StreamControllers.Twitch) } + case kick.ID: + if d.StreamControllers.Kick == nil { + d.tryConnectKick(ctx) + } + if d.StreamControllers.Kick != nil { + result = streamcontrol.ToAbstract(d.StreamControllers.Kick) + } case youtube.ID: if d.StreamControllers.YouTube == nil { d.tryConnectYouTube(ctx) diff --git a/pkg/streamd/ui/ui.go b/pkg/streamd/ui/ui.go index 44ce16c..0a92b13 100644 --- a/pkg/streamd/ui/ui.go +++ b/pkg/streamd/ui/ui.go @@ -6,6 +6,7 @@ import ( "github.com/facebookincubator/go-belt/tool/logger" "github.com/xaionaro-go/streamctl/pkg/oauthhandler" "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" @@ -19,12 +20,17 @@ type UI interface { ctx context.Context, ) (bool, string, []byte, error) OAuthHandlerTwitch(ctx context.Context, arg oauthhandler.OAuthHandlerArgument) error + OAuthHandlerKick(ctx context.Context, arg oauthhandler.OAuthHandlerArgument) error OAuthHandlerYouTube(ctx context.Context, arg oauthhandler.OAuthHandlerArgument) error OpenBrowser(ctx context.Context, url string) error InputTwitchUserInfo( ctx context.Context, cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile], ) (bool, error) + InputKickUserInfo( + ctx context.Context, + cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile], + ) (bool, error) InputYouTubeUserInfo( ctx context.Context, cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile], diff --git a/pkg/streampanel/monitor.go b/pkg/streampanel/monitor.go index 8b0e082..d41e50e 100644 --- a/pkg/streampanel/monitor.go +++ b/pkg/streampanel/monitor.go @@ -26,6 +26,7 @@ import ( "github.com/xaionaro-go/streamctl/pkg/colorx" "github.com/xaionaro-go/streamctl/pkg/observability" "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" @@ -278,6 +279,7 @@ func (p *Panel) updateMonitorPageStreamStatus( obs.ID, youtube.ID, twitch.ID, + kick.ID, } { wg.Add(1) observability.Go(ctx, func() { diff --git a/pkg/streampanel/panel.go b/pkg/streampanel/panel.go index 6773e06..82c3f63 100644 --- a/pkg/streampanel/panel.go +++ b/pkg/streampanel/panel.go @@ -35,6 +35,7 @@ import ( "github.com/xaionaro-go/streamctl/pkg/screenshot" "github.com/xaionaro-go/streamctl/pkg/screenshoter" "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" @@ -107,6 +108,7 @@ type Panel struct { youtubeCheck *widget.Check twitchCheck *widget.Check + kickCheck *widget.Check configPath string configCache *streamdconfig.Config @@ -662,6 +664,15 @@ func (p *Panel) OAuthHandlerTwitch( return p.oauthHandler(ctx, twitch.ID, arg) } +func (p *Panel) OAuthHandlerKick( + ctx context.Context, + arg oauthhandler.OAuthHandlerArgument, +) error { + logger.Infof(ctx, "OAuthHandlerKick: %#+v", arg) + defer logger.Infof(ctx, "/OAuthHandlerKick") + return p.oauthHandler(ctx, kick.ID, arg) +} + func (p *Panel) OAuthHandlerYouTube( ctx context.Context, arg oauthhandler.OAuthHandlerArgument, @@ -857,6 +868,13 @@ func (p *Panel) InputTwitchUserInfo( return true, nil } +func (p *Panel) InputKickUserInfo( + ctx context.Context, + cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile], +) (bool, error) { + return false, fmt.Errorf("not implemented, yet") +} + var youtubeCredentialsCreateLink, _ = url.Parse( "https://console.cloud.google.com/apis/credentials/oauthclient", ) @@ -1105,6 +1123,7 @@ func (p *Panel) refilterProfiles(ctx context.Context) { subValueMatch = true break } + case kick.StreamProfile: case youtube.StreamProfile: if containTagSubstringCI(prof.Tags, filterValue) { subValueMatch = true @@ -1214,6 +1233,7 @@ func (p *Panel) openSettingsWindow(ctx context.Context) error { for _, backendID := range []streamcontrol.PlatformName{ obs.ID, twitch.ID, + kick.ID, youtube.ID, } { isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID) @@ -1319,6 +1339,13 @@ func (p *Panel) openSettingsWindow(ctx context.Context) error { twitchAlreadyLoggedIn.SetText("(already logged in)") } + kickAlreadyLoggedIn := widget.NewLabel("") + if !backendEnabled[kick.ID] { + kickAlreadyLoggedIn.SetText("(not logged in)") + } else { + kickAlreadyLoggedIn.SetText("(already logged in)") + } + youtubeAlreadyLoggedIn := widget.NewLabel("") if !backendEnabled[youtube.ID] { youtubeAlreadyLoggedIn.SetText("(not logged in)") @@ -1432,6 +1459,27 @@ func (p *Panel) openSettingsWindow(ctx context.Context) error { }), twitchAlreadyLoggedIn, ), + container.NewHBox( + widget.NewButtonWithIcon("(Re-)login in Kick", theme.LoginIcon(), func() { + if cfg.Backends[kick.ID] == nil { + kick.InitConfig(cfg.Backends) + } + + cfg.Backends[kick.ID].Enable = nil + cfg.Backends[kick.ID].Config = kick.PlatformSpecificConfig{} + + if err := p.StreamD.SetConfig(ctx, cfg); err != nil { + p.DisplayError(err) + return + } + + if err := p.StreamD.EXPERIMENTAL_ReinitStreamControllers(ctx); err != nil { + p.DisplayError(err) + return + } + }), + kickAlreadyLoggedIn, + ), container.NewHBox( widget.NewButtonWithIcon("(Re-)login in YouTube", theme.LoginIcon(), func() { if cfg.Backends[youtube.ID] == nil { @@ -1603,6 +1651,7 @@ func (p *Panel) getUpdatedStatus_backends_noLock(ctx context.Context) { for _, backendID := range []streamcontrol.PlatformName{ obs.ID, twitch.ID, + kick.ID, youtube.ID, } { isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID) @@ -1616,6 +1665,9 @@ func (p *Panel) getUpdatedStatus_backends_noLock(ctx context.Context) { if backendEnabled[twitch.ID] { p.twitchCheck.Enable() } + if backendEnabled[kick.ID] { + p.kickCheck.Enable() + } if backendEnabled[youtube.ID] { p.youtubeCheck.Enable() } @@ -1856,6 +1908,10 @@ func (p *Panel) initMainWindow( p.twitchCheck.SetChecked(true) p.twitchCheck.Disable() + p.kickCheck = widget.NewCheck("Kick", nil) + p.kickCheck.SetChecked(true) + p.kickCheck.Disable() + p.youtubeCheck = widget.NewCheck("YouTube", nil) p.youtubeCheck.SetChecked(true) p.youtubeCheck.Disable() @@ -1892,6 +1948,9 @@ func (p *Panel) initMainWindow( twLabel := widget.NewLabel("TW:") twLabel.Importance = widget.HighImportance p.streamStatus[twitch.ID] = widget.NewLabel("") + kcLabel := widget.NewLabel("Kc:") + kcLabel.Importance = widget.HighImportance + p.streamStatus[kick.ID] = widget.NewLabel("") ytLabel := widget.NewLabel("YT:") ytLabel.Importance = widget.HighImportance p.streamStatus[youtube.ID] = widget.NewLabel("") @@ -1903,6 +1962,7 @@ func (p *Panel) initMainWindow( } streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), obsLabel, p.streamStatus[obs.ID])) streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), twLabel, p.streamStatus[twitch.ID])) + streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), kcLabel, p.streamStatus[kick.ID])) streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), ytLabel, p.streamStatus[youtube.ID])) streamInfoContainer := container.NewBorder( nil, @@ -2243,6 +2303,7 @@ func (p *Panel) setupStreamNoLock(ctx context.Context) { for _, backendID := range []streamcontrol.PlatformName{ obs.ID, twitch.ID, + kick.ID, youtube.ID, } { isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID) @@ -2278,6 +2339,19 @@ func (p *Panel) setupStreamNoLock(ctx context.Context) { } } + if p.kickCheck.Checked && backendEnabled[kick.ID] { + err := p.StreamD.StartStream( + ctx, + kick.ID, + p.streamTitleField.Text, + p.streamDescriptionField.Text, + profile.PerPlatform[kick.ID], + ) + if err != nil { + p.DisplayError(fmt.Errorf("unable to setup the stream on Twitch: %w", err)) + } + } + if p.youtubeCheck.Checked && backendEnabled[youtube.ID] { if p.streamIsRunning(ctx, youtube.ID) { logger.Debugf(ctx, "updating the stream info at YouTube") @@ -2435,6 +2509,9 @@ func (p *Panel) stopStreamNoLock(ctx context.Context) { if backendEnabled[twitch.ID] { p.twitchCheck.Enable() } + if backendEnabled[kick.ID] { + p.kickCheck.Enable() + } if backendEnabled[youtube.ID] { p.youtubeCheck.Enable() } @@ -2845,6 +2922,7 @@ func (p *Panel) profileWindow( var ( obsProfile *obs.StreamProfile twitchProfile *twitch.StreamProfile + kickProfile *kick.StreamProfile youtubeProfile *youtube.StreamProfile ) @@ -2870,6 +2948,7 @@ func (p *Panel) profileWindow( for _, backendID := range []streamcontrol.PlatformName{ obs.ID, twitch.ID, + kick.ID, youtube.ID, } { isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID) @@ -2893,6 +2972,8 @@ func (p *Panel) profileWindow( } _ = backendData[obs.ID].(api.BackendDataOBS) dataTwitch := backendData[twitch.ID].(api.BackendDataTwitch) + dataKick := backendData[kick.ID].(api.BackendDataKick) + _ = dataKick // TODO: delete me! dataYouTube := backendData[youtube.ID].(api.BackendDataYouTube) var bottomContent []fyne.CanvasObject @@ -3026,6 +3107,14 @@ func (p *Panel) profileWindow( bottomContent = append(bottomContent, widget.NewLabel("Twitch is disabled")) } + bottomContent = append(bottomContent, widget.NewSeparator()) + bottomContent = append(bottomContent, widget.NewRichTextFromMarkdown("# Kick:")) + if backendEnabled[kick.ID] { + bottomContent = append(bottomContent, widget.NewLabel("Kick configuration is not implemented, yet")) + } else { + bottomContent = append(bottomContent, widget.NewLabel("Kick is disabled")) + } + var getYoutubeTags func() []string bottomContent = append(bottomContent, widget.NewSeparator()) bottomContent = append(bottomContent, widget.NewRichTextFromMarkdown("# YouTube:")) @@ -3228,6 +3317,9 @@ func (p *Panel) profileWindow( } profile.PerPlatform[twitch.ID] = twitchProfile } + if kickProfile != nil { + profile.PerPlatform[kick.ID] = kickProfile + } if youtubeProfile != nil { if getYoutubeTags != nil { youtubeProfile.Tags = sanitizeTags(getYoutubeTags()) diff --git a/pkg/streampanel/timers.go b/pkg/streampanel/timers.go index 754d5a2..372ad18 100644 --- a/pkg/streampanel/timers.go +++ b/pkg/streampanel/timers.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/xaionaro-go/streamctl/pkg/observability" "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" @@ -250,6 +251,7 @@ func (ui *timersUI) kickOffRemotely( for _, platID := range []streamcontrol.PlatformName{ youtube.ID, twitch.ID, + kick.ID, obs.ID, } { _, err := streamD.AddTimer(ctx, deadline, &action.EndStream{