diff --git a/pkg/streamcontrol/kick/chat_handler.go b/pkg/streamcontrol/kick/chat_handler.go index f5aba31..2022967 100644 --- a/pkg/streamcontrol/kick/chat_handler.go +++ b/pkg/streamcontrol/kick/chat_handler.go @@ -12,14 +12,6 @@ type ChatHandler struct { onClose func(context.Context) } -func (k *Kick) newChatHandler( - ctx context.Context, - broadcasterUserID *int, - onClose func(context.Context), -) (*ChatHandler, error) { - return NewChatHandler(ctx, k.Client, broadcasterUserID, onClose) -} - func NewChatHandler( ctx context.Context, client *gokick.Client, diff --git a/pkg/streamcontrol/kick/client.go b/pkg/streamcontrol/kick/client.go new file mode 100644 index 0000000..41f6369 --- /dev/null +++ b/pkg/streamcontrol/kick/client.go @@ -0,0 +1,46 @@ +package kick + +import ( + "context" + + "github.com/scorfly/gokick" +) + +type Client interface { + GetAuthorize(redirectURI, state, codeChallenge string, scope []gokick.Scope) (string, error) + GetToken(ctx context.Context, redirectURI, code, codeVerifier string) (gokick.TokenResponse, error) + OnUserAccessTokenRefreshed(callback func(accessToken, refreshToken string)) + UpdateStreamTitle(ctx context.Context, title string) (gokick.EmptyResponse, error) + UpdateStreamCategory(ctx context.Context, categoryID int) (gokick.EmptyResponse, error) + GetChannels(ctx context.Context, filter gokick.ChannelListFilter) (gokick.ChannelsResponseWrapper, error) + SendChatMessage( + ctx context.Context, + broadcasterUserID *int, + content string, + replyToMessageID *string, + messageType gokick.MessageType, + ) (gokick.ChatResponseWrapper, error) + BanUser( + ctx context.Context, + broadcasterUserID int, + userID int, + duration *int, + reason *string, + ) (gokick.BanUserResponseWrapper, error) +} + +type clientScorfly struct { + *gokick.Client +} + +func newClient(options *gokick.ClientOptions) (*clientScorfly, error) { + client, err := gokick.NewClient(options) + if err != nil { + return nil, err + } + return &clientScorfly{Client: client}, nil +} + +func (c *clientScorfly) OnUserAccessTokenRefreshed(callback func(accessToken, refreshToken string)) { + c.Client.OnUserAccessTokenRefreshed(callback) +} diff --git a/pkg/streamcontrol/kick/client_mock.go b/pkg/streamcontrol/kick/client_mock.go new file mode 100644 index 0000000..abbbf10 --- /dev/null +++ b/pkg/streamcontrol/kick/client_mock.go @@ -0,0 +1,87 @@ +package kick + +import ( + "context" + + "github.com/scorfly/gokick" +) + +type clientMock struct{} + +var _ Client = (*clientMock)(nil) + +func newClientMock() *clientMock { + return &clientMock{} +} + +func (clientMock) GetAuthorize(redirectURI, state, codeChallenge string, scope []gokick.Scope) (string, error) { + return "", nil +} + +func (clientMock) GetToken(ctx context.Context, redirectURI, code, codeVerifier string) (gokick.TokenResponse, error) { + return gokick.TokenResponse{}, nil +} + +func (clientMock) OnUserAccessTokenRefreshed(callback func(accessToken, refreshToken string)) {} + +func (clientMock) UpdateStreamTitle(ctx context.Context, title string) (gokick.EmptyResponse, error) { + return gokick.EmptyResponse{}, nil +} + +func (clientMock) UpdateStreamCategory(ctx context.Context, categoryID int) (gokick.EmptyResponse, error) { + return gokick.EmptyResponse{}, nil +} + +func (clientMock) GetChannels(ctx context.Context, filter gokick.ChannelListFilter) (gokick.ChannelsResponseWrapper, error) { + return gokick.ChannelsResponseWrapper{ + Result: []gokick.ChannelResponse{{ + BannerPicture: "BannerPicture", + BroadcasterUserID: 1, + Category: gokick.CategoryResponse{ + ID: 2, + Name: "Name", + Thumbnail: "Thumbnail", + }, + ChannelDescription: "ChannelDescription", + Slug: "Slug", + Stream: gokick.StreamResponse{ + Key: "Key", + URL: "URL", + IsLive: true, + IsMature: false, + Language: "Language", + StartTime: "StartTime", + Thumbnail: "Thumbnail", + ViewerCount: 3, + }, + StreamTitle: "StreamTitle", + }}, + }, nil +} + +func (clientMock) SendChatMessage( + ctx context.Context, + broadcasterUserID *int, + content string, + replyToMessageID *string, + messageType gokick.MessageType, +) (gokick.ChatResponseWrapper, error) { + return gokick.ChatResponseWrapper{ + Result: gokick.ChatResponse{ + IsSent: true, + MessageID: "MessageID", + }, + }, nil +} + +func (clientMock) BanUser( + ctx context.Context, + broadcasterUserID int, + userID int, + duration *int, + reason *string, +) (gokick.BanUserResponseWrapper, error) { + return gokick.BanUserResponseWrapper{ + Result: gokick.BanUserResponse{}, + }, nil +} diff --git a/pkg/streamcontrol/kick/kick.go b/pkg/streamcontrol/kick/kick.go index d73cb9c..8a48e59 100644 --- a/pkg/streamcontrol/kick/kick.go +++ b/pkg/streamcontrol/kick/kick.go @@ -23,6 +23,10 @@ import ( "github.com/xaionaro-go/xsync" ) +const ( + debugUseMockClient = false +) + type ReverseEngClient interface { ChatClientOBSOLETE } @@ -31,7 +35,7 @@ type Kick struct { CloseCtx context.Context CloseFn context.CancelFunc Channel *kickcom.ChannelV1 - Client *gokick.Client + Client *Client ClientOBSOLETE *kickcom.Kick ChatHandler *ChatHandlerOBSOLETE ChatHandlerLocker xsync.CtxLocker @@ -57,36 +61,50 @@ func New( return nil, fmt.Errorf("channel is not set") } - options := &gokick.ClientOptions{ - UserAccessToken: cfg.Config.UserAccessToken.Get(), - UserRefreshToken: cfg.Config.RefreshToken.Get(), - ClientID: cfg.Config.ClientID, - ClientSecret: cfg.Config.ClientSecret.Get(), - } - client, err := gokick.NewClient(options) - if err != nil { - return nil, fmt.Errorf("unable to initialize a client to Kick: %w", err) - } - clientOld, err := kickcom.New() if err != nil { return nil, fmt.Errorf("unable to initialize the old client: %w", err) } + client, err := getClient(cfg.Config) + if err != nil { + return nil, fmt.Errorf("unable to initialize the client: %w", err) + } + ctx, closeFn := context.WithCancel(ctx) k := &Kick{ CloseCtx: ctx, CloseFn: closeFn, ChatHandlerLocker: make(xsync.CtxLocker, 1), CurrentConfig: cfg, - Client: client, ClientOBSOLETE: clientOld, SaveCfgFn: saveCfgFn, } + k.SetClient(client) client.OnUserAccessTokenRefreshed(k.onUserAccessTokenRefreshed) return k, nil } +func getClient( + cfg PlatformSpecificConfig, +) (Client, error) { + if debugUseMockClient { + return newClientMock(), nil + } + + client, err := newClient(&gokick.ClientOptions{ + UserAccessToken: cfg.UserAccessToken.Get(), + UserRefreshToken: cfg.RefreshToken.Get(), + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret.Get(), + }) + if err != nil { + return nil, err + } + + return client, nil +} + func (k *Kick) onUserAccessTokenRefreshed( userAccessToken string, refreshToken string, @@ -230,7 +248,7 @@ func (k *Kick) getAccessTokenNoLock( gokick.ScopeModerationBan, } logger.Debugf(ctx, "scopes: %v", scopes) - authURL, err := k.getClient().GetAuthorize( + authURL, err := k.GetClient().GetAuthorize( redirectURL, "EMPTY", codeChallenge, @@ -248,7 +266,7 @@ func (k *Kick) getAccessTokenNoLock( code string, ) error { now := time.Now() - token, err := k.getClient().GetToken(ctx, redirectURL, code, codeVerifier) + token, err := k.GetClient().GetToken(ctx, redirectURL, code, codeVerifier) if err != nil { return fmt.Errorf("unable to get an access token: %w", err) } @@ -297,7 +315,7 @@ func (k *Kick) SetTitle(ctx context.Context, title string) (err error) { return fmt.Errorf("unable to get a prepared client: %w", err) } - _, err = k.getClient().UpdateStreamTitle(ctx, title) + _, err = k.GetClient().UpdateStreamTitle(ctx, title) return } @@ -391,7 +409,7 @@ func (k *Kick) getStreamStatusUsingNormalClient( logger.Debugf(ctx, "getStreamStatusUsingNormalClient") defer func() { logger.Debugf(ctx, "/getStreamStatusUsingNormalClient: %v %v", _ret, _err) }() - resp, err := k.getClient().GetChannels( + resp, err := k.GetClient().GetChannels( ctx, gokick.NewChannelListFilter().SetBroadcasterUserIDs([]int{int(k.Channel.UserID)}), ) @@ -500,7 +518,7 @@ func (k *Kick) GetChatMessagesChan( func (k *Kick) SendChatMessage(ctx context.Context, message string) (_err error) { logger.Debugf(ctx, "SendChatMessage(ctx, '%s')", message) defer func() { logger.Debugf(ctx, "/SendChatMessage(ctx, '%s'): %v", message, _err) }() - resp, err := k.Client.SendChatMessage(ctx, ptr(int(k.Channel.UserID)), message, nil, gokick.MessageTypeUser) + resp, err := k.GetClient().SendChatMessage(ctx, ptr(int(k.Channel.UserID)), message, nil, gokick.MessageTypeUser) logger.Debugf(ctx, "SendChatMessage(ctx, '%s'): %#+v", message, resp) return err } @@ -535,7 +553,7 @@ func (k *Kick) BanUser( return nil } } - resp, err := k.Client.BanUser(ctx, int(k.Channel.UserID), int(userIDInt), duration, reasonPtr) + resp, err := k.GetClient().BanUser(ctx, int(k.Channel.UserID), int(userIDInt), duration, reasonPtr) logger.Debugf(ctx, "BanUser(ctx, %d, '%s', %v): %#+v", userID, reason, deadline, resp) return err } @@ -556,7 +574,7 @@ func (k *Kick) ApplyProfile( if profile.CategoryID != nil { logger.Debugf(ctx, "has a CategoryID") - _, err := k.getClient().UpdateStreamCategory(ctx, int(*profile.CategoryID)) + _, err := k.GetClient().UpdateStreamCategory(ctx, int(*profile.CategoryID)) if err != nil { result = append(result, fmt.Errorf("unable to update the category: %w", err)) } diff --git a/pkg/streamcontrol/kick/kick_unsafe.go b/pkg/streamcontrol/kick/kick_unsafe.go index 1759e33..139056a 100644 --- a/pkg/streamcontrol/kick/kick_unsafe.go +++ b/pkg/streamcontrol/kick/kick_unsafe.go @@ -2,13 +2,12 @@ package kick import ( "github.com/go-ng/xatomic" - "github.com/scorfly/gokick" ) -func (k *Kick) getClient() *gokick.Client { - return xatomic.LoadPointer(&k.Client) +func (k *Kick) GetClient() Client { + return *xatomic.LoadPointer(&k.Client) } -func (k *Kick) setClient(client *gokick.Client) { - xatomic.StorePointer(&k.Client, client) +func (k *Kick) SetClient(client Client) { + xatomic.StorePointer(&k.Client, &client) } diff --git a/pkg/streamcontrol/twitch/auth/access_token_app.go b/pkg/streamcontrol/twitch/auth/access_token_app.go index 278ff16..296e2ee 100644 --- a/pkg/streamcontrol/twitch/auth/access_token_app.go +++ b/pkg/streamcontrol/twitch/auth/access_token_app.go @@ -5,13 +5,13 @@ import ( "fmt" "github.com/facebookincubator/go-belt/tool/logger" - "github.com/nicklaw5/helix/v2" "github.com/xaionaro-go/streamctl/pkg/secret" + twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types" ) func NewTokenByApp( ctx context.Context, - client *helix.Client, + client twitch.Client, ) (secret.String, error) { logger.Debugf(ctx, "getNewTokenByApp") defer func() { logger.Debugf(ctx, "/getNewTokenByApp") }() diff --git a/pkg/streamcontrol/twitch/auth/access_token_user.go b/pkg/streamcontrol/twitch/auth/access_token_user.go index 6c9bed6..7867c7a 100644 --- a/pkg/streamcontrol/twitch/auth/access_token_user.go +++ b/pkg/streamcontrol/twitch/auth/access_token_user.go @@ -5,14 +5,14 @@ import ( "fmt" "github.com/facebookincubator/go-belt/tool/logger" - "github.com/nicklaw5/helix/v2" "github.com/xaionaro-go/observability" "github.com/xaionaro-go/streamctl/pkg/secret" + twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types" ) func NewTokenByUser( ctx context.Context, - client *helix.Client, + client twitch.Client, clientCode secret.String, ) (secret.String, secret.String, error) { logger.Debugf(ctx, "getNewTokenByUser") diff --git a/pkg/streamcontrol/twitch/auth/client_code.go b/pkg/streamcontrol/twitch/auth/client_code.go index fc3727a..4301d2e 100644 --- a/pkg/streamcontrol/twitch/auth/client_code.go +++ b/pkg/streamcontrol/twitch/auth/client_code.go @@ -16,9 +16,10 @@ import ( "github.com/nicklaw5/helix/v2" "github.com/xaionaro-go/observability" "github.com/xaionaro-go/streamctl/pkg/oauthhandler" + twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types" ) -type OAuthHandler func(context.Context, oauthhandler.OAuthHandlerArgument) error +type OAuthHandler = twitch.OAuthHandler func getScopes() []string { scopes := map[string]struct{}{ diff --git a/pkg/streamcontrol/twitch/client.go b/pkg/streamcontrol/twitch/client.go new file mode 100644 index 0000000..1e8de17 --- /dev/null +++ b/pkg/streamcontrol/twitch/client.go @@ -0,0 +1,12 @@ +package twitch + +import ( + "github.com/nicklaw5/helix/v2" + twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types" +) + +type client = twitch.Client + +type clientNicklaw5 struct { + *helix.Client +} diff --git a/pkg/streamcontrol/twitch/client_mock.go b/pkg/streamcontrol/twitch/client_mock.go new file mode 100644 index 0000000..e202fe9 --- /dev/null +++ b/pkg/streamcontrol/twitch/client_mock.go @@ -0,0 +1,155 @@ +package twitch + +import ( + "time" + + "github.com/nicklaw5/helix/v2" +) + +type clientMock struct{} + +func newClientMock() *clientMock { + return &clientMock{} +} + +var _ client = (*clientMock)(nil) + +func (c *clientMock) GetAppAccessToken() string { + return "" +} +func (c *clientMock) GetUserAccessToken() string { + return "" +} +func (c *clientMock) GetRefreshToken() string { + return "" +} +func (c *clientMock) SetAppAccessToken(accessToken string) {} + +func (c *clientMock) SetUserAccessToken(accessToken string) {} + +func (c *clientMock) SetRefreshToken(refreshToken string) {} + +func (c *clientMock) OnUserAccessTokenRefreshed(f func(newAccessToken, newRefreshToken string)) {} + +func (c *clientMock) GetChannelInformation(params *helix.GetChannelInformationParams) (*helix.GetChannelInformationResponse, error) { + return &helix.GetChannelInformationResponse{ + Data: helix.ManyChannelInformation{ + Channels: []helix.ChannelInformation{{ + BroadcasterID: "BroadcasterID", + BroadcasterName: "BroadcasterName", + BroadcasterLanguage: "BroadcasterLanguage", + GameID: "GameID", + GameName: "GameName", + Title: "Title", + Delay: 1, + Tags: []string{"Tag"}, + }}, + }, + }, nil +} + +func (c *clientMock) RequestUserAccessToken(code string) (*helix.UserAccessTokenResponse, error) { + return &helix.UserAccessTokenResponse{}, nil +} + +func (c *clientMock) RequestAppAccessToken(scopes []string) (*helix.AppAccessTokenResponse, error) { + return &helix.AppAccessTokenResponse{}, nil +} + +func (c *clientMock) EditChannelInformation(params *helix.EditChannelInformationParams) (*helix.EditChannelInformationResponse, error) { + return &helix.EditChannelInformationResponse{}, nil +} + +func (c *clientMock) GetGames(params *helix.GamesParams) (*helix.GamesResponse, error) { + return &helix.GamesResponse{ + Data: helix.ManyGames{ + Games: []helix.Game{{ + ID: "ID", + Name: "Name", + BoxArtURL: "BoxArtURL", + }}, + }, + }, nil +} + +func (c *clientMock) GetStreams(params *helix.StreamsParams) (*helix.StreamsResponse, error) { + return &helix.StreamsResponse{ + Data: helix.ManyStreams{ + Streams: []helix.Stream{{ + ID: "ID", + UserID: "UserID", + UserLogin: "UserLogin", + UserName: "UserName", + GameID: "GameID", + GameName: "GameName", + TagIDs: []string{"TagID"}, + Tags: []string{"Tag"}, + IsMature: false, + Type: "Type", + Title: "Title", + ViewerCount: 1, + StartedAt: time.Now(), + Language: "Language", + ThumbnailURL: "ThumbnailURL", + }}, + }, + }, nil +} + +func (c *clientMock) GetTopGames(params *helix.TopGamesParams) (*helix.TopGamesResponse, error) { + return &helix.TopGamesResponse{ + Data: helix.ManyGamesWithPagination{ + ManyGames: helix.ManyGames{ + Games: []helix.Game{{ + ID: "ID", + Name: "Name", + BoxArtURL: "BoxArtURL", + }}, + }, + }, + }, nil +} + +func (c *clientMock) SendChatMessage(params *helix.SendChatMessageParams) (*helix.ChatMessageResponse, error) { + return &helix.ChatMessageResponse{}, nil +} + +func (c *clientMock) DeleteChatMessage(params *helix.DeleteChatMessageParams) (*helix.DeleteChatMessageResponse, error) { + return &helix.DeleteChatMessageResponse{}, nil +} + +func (c *clientMock) BanUser(params *helix.BanUserParams) (*helix.BanUserResponse, error) { + return &helix.BanUserResponse{}, nil +} + +func (c *clientMock) StartRaid(params *helix.StartRaidParams) (*helix.RaidResponse, error) { + return &helix.RaidResponse{}, nil +} + +func (c *clientMock) SendShoutout(params *helix.SendShoutoutParams) (*helix.SendShoutoutResponse, error) { + return &helix.SendShoutoutResponse{}, nil +} + +func (c *clientMock) CreateEventSubSubscription(payload *helix.EventSubSubscription) (*helix.EventSubSubscriptionsResponse, error) { + return &helix.EventSubSubscriptionsResponse{}, nil +} + +func (c *clientMock) GetUsers(params *helix.UsersParams) (*helix.UsersResponse, error) { + return &helix.UsersResponse{ + Data: helix.ManyUsers{ + Users: []helix.User{{ + ID: "ID", + Login: "Login", + DisplayName: "DisplayName", + Type: "Type", + BroadcasterType: "BroadcasterType", + Description: "Description", + ProfileImageURL: "ProfileImageURL", + OfflineImageURL: "OfflineImageURL", + ViewCount: 1, + Email: "Email", + CreatedAt: helix.Time{Time: time.Now()}, + }}, + }, + }, nil +} diff --git a/pkg/streamcontrol/twitch/twitch.go b/pkg/streamcontrol/twitch/twitch.go index 7c8160a..da1a4c6 100644 --- a/pkg/streamcontrol/twitch/twitch.go +++ b/pkg/streamcontrol/twitch/twitch.go @@ -28,7 +28,7 @@ type Twitch struct { closeFn context.CancelFunc chatHandlerSub *ChatHandlerSub chatHandlerIRC *ChatHandlerIRC - client *helix.Client + client client config Config broadcasterID string lazyInitOnce sync.Once @@ -39,7 +39,10 @@ type Twitch struct { clientSecret secret.String } -const twitchDebug = false +const ( + twitchDebug = false + debugUseMockClient = false +) var _ streamcontrol.StreamController[StreamProfile] = (*Twitch)(nil) @@ -141,7 +144,7 @@ func New( func GetUserID( _ context.Context, - client *helix.Client, + client client, login string, ) (string, error) { resp, err := client.GetUsers(&helix.UsersParams{ @@ -613,10 +616,13 @@ func (t *Twitch) getNewTokenByApp( func (t *Twitch) getClient( ctx context.Context, oauthListenPort uint16, -) (*helix.Client, error) { +) (client, error) { logger.Debugf(ctx, "getClient(ctx, %#+v, %v)", t.config, oauthListenPort) defer func() { logger.Debugf(ctx, "/getClient") }() + if debugUseMockClient { + return newClientMock(), nil + } options := &helix.Options{ ClientID: t.clientID, ClientSecret: t.clientSecret.Get(), diff --git a/pkg/streamcontrol/twitch/types/client.go b/pkg/streamcontrol/twitch/types/client.go new file mode 100644 index 0000000..d424dbb --- /dev/null +++ b/pkg/streamcontrol/twitch/types/client.go @@ -0,0 +1,29 @@ +package twitch + +import ( + "github.com/nicklaw5/helix/v2" +) + +type Client interface { + GetAppAccessToken() string + GetUserAccessToken() string + GetRefreshToken() string + SetAppAccessToken(accessToken string) + SetUserAccessToken(accessToken string) + SetRefreshToken(refreshToken string) + OnUserAccessTokenRefreshed(f func(newAccessToken, newRefreshToken string)) + GetChannelInformation(params *helix.GetChannelInformationParams) (*helix.GetChannelInformationResponse, error) + RequestUserAccessToken(code string) (*helix.UserAccessTokenResponse, error) + RequestAppAccessToken(scopes []string) (*helix.AppAccessTokenResponse, error) + EditChannelInformation(params *helix.EditChannelInformationParams) (*helix.EditChannelInformationResponse, error) + GetGames(params *helix.GamesParams) (*helix.GamesResponse, error) + GetStreams(params *helix.StreamsParams) (*helix.StreamsResponse, error) + GetTopGames(params *helix.TopGamesParams) (*helix.TopGamesResponse, error) + SendChatMessage(params *helix.SendChatMessageParams) (*helix.ChatMessageResponse, error) + DeleteChatMessage(params *helix.DeleteChatMessageParams) (*helix.DeleteChatMessageResponse, error) + BanUser(params *helix.BanUserParams) (*helix.BanUserResponse, error) + StartRaid(params *helix.StartRaidParams) (*helix.RaidResponse, error) + SendShoutout(params *helix.SendShoutoutParams) (*helix.SendShoutoutResponse, error) + CreateEventSubSubscription(payload *helix.EventSubSubscription) (*helix.EventSubSubscriptionsResponse, error) + GetUsers(params *helix.UsersParams) (*helix.UsersResponse, error) +} diff --git a/pkg/streamcontrol/twitch/types/config.go b/pkg/streamcontrol/twitch/types/config.go index bacf818..94c16d6 100644 --- a/pkg/streamcontrol/twitch/types/config.go +++ b/pkg/streamcontrol/twitch/types/config.go @@ -4,13 +4,10 @@ import ( "github.com/xaionaro-go/streamctl/pkg/buildvars" "github.com/xaionaro-go/streamctl/pkg/secret" streamctl "github.com/xaionaro-go/streamctl/pkg/streamcontrol" - "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/auth" ) const ID = streamctl.PlatformName("twitch") -type OAuthHandler = auth.OAuthHandler - type PlatformSpecificConfig struct { Channel string ClientID string diff --git a/pkg/streamcontrol/twitch/types/oauth_handler.go b/pkg/streamcontrol/twitch/types/oauth_handler.go new file mode 100644 index 0000000..b7bb57c --- /dev/null +++ b/pkg/streamcontrol/twitch/types/oauth_handler.go @@ -0,0 +1,9 @@ +package twitch + +import ( + "context" + + "github.com/xaionaro-go/streamctl/pkg/oauthhandler" +) + +type OAuthHandler func(context.Context, oauthhandler.OAuthHandlerArgument) error diff --git a/pkg/streamcontrol/youtube/chat_listener.go b/pkg/streamcontrol/youtube/chat_listener.go index a96d8c6..e0d9486 100644 --- a/pkg/streamcontrol/youtube/chat_listener.go +++ b/pkg/streamcontrol/youtube/chat_listener.go @@ -17,20 +17,20 @@ import ( type ChatListener struct { videoID string liveChatID string - client YouTubeChatClient + client ChatClient wg sync.WaitGroup cancelFunc context.CancelFunc messagesOutChan chan streamcontrol.ChatMessage } -type YouTubeChatClient interface { +type ChatClient interface { GetLiveChatMessages(ctx context.Context, chatID string, pageToken string, parts []string) (*youtube.LiveChatMessageListResponse, error) } func NewChatListener( ctx context.Context, - ytClient YouTubeChatClient, + ytClient ChatClient, videoID string, liveChatID string, ) (*ChatListener, error) { diff --git a/pkg/streamcontrol/youtube/youtube_client.go b/pkg/streamcontrol/youtube/client.go similarity index 91% rename from pkg/streamcontrol/youtube/youtube_client.go rename to pkg/streamcontrol/youtube/client.go index 3a60abb..077eb8c 100644 --- a/pkg/streamcontrol/youtube/youtube_client.go +++ b/pkg/streamcontrol/youtube/client.go @@ -36,8 +36,8 @@ func (t BroadcastType) String() string { return fmt.Sprintf("unexpected_value_%d", int(t)) } -type YouTubeClient interface { - YouTubeChatClient +type client interface { + ChatClient Ping(context.Context) error GetBroadcasts(ctx context.Context, t BroadcastType, ids []string, parts []string, pageToken string) (*youtube.LiveBroadcastListResponse, error) UpdateBroadcast(context.Context, *youtube.LiveBroadcast, []string) error @@ -65,7 +65,7 @@ const ( EventTypeUpcoming = EventType("upcoming") ) -type YouTubeClientV3 struct { +type clientV3 struct { *youtube.Service RequestWrapper func(context.Context, func(context.Context) error) error } @@ -106,24 +106,24 @@ func wrapRequestR[T any]( return } -var _ YouTubeClient = (*YouTubeClientV3)(nil) +var _ client = (*clientV3)(nil) -func NewYouTubeClientV3( +func newClientV3( ctx context.Context, requestWrapper func(context.Context, func(context.Context) error) error, opts ...option.ClientOption, -) (*YouTubeClientV3, error) { +) (*clientV3, error) { srv, err := youtube.NewService(ctx, opts...) if err != nil { return nil, err } - return &YouTubeClientV3{ + return &clientV3{ Service: srv, RequestWrapper: requestWrapper, }, nil } -func (c *YouTubeClientV3) Ping( +func (c *clientV3) Ping( ctx context.Context, ) (_err error) { logger.Tracef(ctx, "Ping") @@ -141,7 +141,7 @@ func (c *YouTubeClientV3) Ping( return err } -func (c *YouTubeClientV3) GetBroadcasts( +func (c *clientV3) GetBroadcasts( ctx context.Context, t BroadcastType, ids []string, @@ -171,7 +171,7 @@ func (c *YouTubeClientV3) GetBroadcasts( return wrapRequestR(ctx, c.RequestWrapper, r.Do, googleapi.QueryParameter("order", "date")) } -func (c *YouTubeClientV3) UpdateBroadcast( +func (c *clientV3) UpdateBroadcast( ctx context.Context, broadcast *youtube.LiveBroadcast, parts []string, @@ -182,7 +182,7 @@ func (c *YouTubeClientV3) UpdateBroadcast( return wrapRequestS(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) InsertBroadcast( +func (c *clientV3) InsertBroadcast( ctx context.Context, broadcast *youtube.LiveBroadcast, parts []string, @@ -192,7 +192,7 @@ func (c *YouTubeClientV3) InsertBroadcast( return c.Service.LiveBroadcasts.Insert(parts, broadcast).Context(ctx).Do() } -func (c *YouTubeClientV3) DeleteBroadcast( +func (c *clientV3) DeleteBroadcast( ctx context.Context, broadcastID string, ) (_err error) { @@ -202,7 +202,7 @@ func (c *YouTubeClientV3) DeleteBroadcast( return wrapRequest(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) InsertCuepoint( +func (c *clientV3) InsertCuepoint( ctx context.Context, p *youtube.Cuepoint, ) (_err error) { @@ -212,7 +212,7 @@ func (c *YouTubeClientV3) InsertCuepoint( return wrapRequestS(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) GetVideos( +func (c *clientV3) GetVideos( ctx context.Context, broadcastIDs []string, parts []string, @@ -223,7 +223,7 @@ func (c *YouTubeClientV3) GetVideos( return wrapRequestR(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) UpdateVideo( +func (c *clientV3) UpdateVideo( ctx context.Context, video *youtube.Video, parts []string, @@ -234,7 +234,7 @@ func (c *YouTubeClientV3) UpdateVideo( return wrapRequestS(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) GetPlaylists( +func (c *clientV3) GetPlaylists( ctx context.Context, parts []string, ) (_ret *youtube.PlaylistListResponse, _err error) { @@ -244,7 +244,7 @@ func (c *YouTubeClientV3) GetPlaylists( return wrapRequestR(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) GetPlaylistItems( +func (c *clientV3) GetPlaylistItems( ctx context.Context, playlistID string, videoID string, @@ -256,7 +256,7 @@ func (c *YouTubeClientV3) GetPlaylistItems( return wrapRequestR(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) InsertPlaylistItem( +func (c *clientV3) InsertPlaylistItem( ctx context.Context, item *youtube.PlaylistItem, parts []string, @@ -267,7 +267,7 @@ func (c *YouTubeClientV3) InsertPlaylistItem( return wrapRequestS(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) SetThumbnail( +func (c *clientV3) SetThumbnail( ctx context.Context, broadcastID string, thumbnail io.Reader, @@ -278,7 +278,7 @@ func (c *YouTubeClientV3) SetThumbnail( return wrapRequestS(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) GetStreams( +func (c *clientV3) GetStreams( ctx context.Context, parts []string, ) (_ret *youtube.LiveStreamListResponse, _err error) { @@ -288,7 +288,7 @@ func (c *YouTubeClientV3) GetStreams( return wrapRequestR(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) InsertCommentThread( +func (c *clientV3) InsertCommentThread( ctx context.Context, t *youtube.CommentThread, parts []string, @@ -299,7 +299,7 @@ func (c *YouTubeClientV3) InsertCommentThread( return wrapRequestS(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) ListChatMessages( +func (c *clientV3) ListChatMessages( ctx context.Context, chatID string, parts []string, @@ -310,7 +310,7 @@ func (c *YouTubeClientV3) ListChatMessages( return wrapRequestR(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) DeleteChatMessage( +func (c *clientV3) DeleteChatMessage( ctx context.Context, messageID string, ) (_err error) { @@ -320,7 +320,7 @@ func (c *YouTubeClientV3) DeleteChatMessage( return wrapRequest(ctx, c.RequestWrapper, do) } -func (c *YouTubeClientV3) GetLiveChatMessages( +func (c *clientV3) GetLiveChatMessages( ctx context.Context, chatID string, pageToken string, @@ -335,7 +335,7 @@ func (c *YouTubeClientV3) GetLiveChatMessages( return q.Do() } -func (c *YouTubeClientV3) Search( +func (c *clientV3) Search( ctx context.Context, chanID string, eventType EventType, diff --git a/pkg/streamcontrol/youtube/youtube_client_calc_points.go b/pkg/streamcontrol/youtube/client_calc_points.go similarity index 80% rename from pkg/streamcontrol/youtube/youtube_client_calc_points.go rename to pkg/streamcontrol/youtube/client_calc_points.go index 71ef8c9..09d3f7e 100644 --- a/pkg/streamcontrol/youtube/youtube_client_calc_points.go +++ b/pkg/streamcontrol/youtube/client_calc_points.go @@ -26,17 +26,17 @@ func init() { } // see also: https://developers.google.com/youtube/v3/determine_quota_cost -type YouTubeClientCalcPoints struct { - Client YouTubeClient +type ClientCalcPoints struct { + Client client UsedPoints atomic.Uint64 CheckMutex sync.Mutex PreviousCheckAt time.Time } -var _ YouTubeClient = (*YouTubeClientCalcPoints)(nil) +var _ client = (*ClientCalcPoints)(nil) -func NewYouTubeClientCalcPoints(client YouTubeClient) *YouTubeClientCalcPoints { - return &YouTubeClientCalcPoints{ +func NewYouTubeClientCalcPoints(client client) *ClientCalcPoints { + return &ClientCalcPoints{ Client: client, } } @@ -45,7 +45,7 @@ func getQuotaCutoffDate(t time.Time) string { return t.In(tzLosAngeles).Format("2006-01-02") } -func (c *YouTubeClientCalcPoints) addUsedPointsIfNoError( +func (c *ClientCalcPoints) addUsedPointsIfNoError( ctx context.Context, points uint, err error, @@ -71,12 +71,12 @@ func (c *YouTubeClientCalcPoints) addUsedPointsIfNoError( } } -func (c *YouTubeClientCalcPoints) Ping(ctx context.Context) (_err error) { +func (c *ClientCalcPoints) Ping(ctx context.Context) (_err error) { defer func() { c.addUsedPointsIfNoError(ctx, 1, _err) }() return c.Client.Ping(ctx) } -func (c *YouTubeClientCalcPoints) GetBroadcasts( +func (c *ClientCalcPoints) GetBroadcasts( ctx context.Context, t BroadcastType, ids []string, @@ -87,7 +87,7 @@ func (c *YouTubeClientCalcPoints) GetBroadcasts( return c.Client.GetBroadcasts(ctx, t, ids, parts, pageToken) } -func (c *YouTubeClientCalcPoints) UpdateBroadcast( +func (c *ClientCalcPoints) UpdateBroadcast( ctx context.Context, broadcast *youtube.LiveBroadcast, parts []string, @@ -96,7 +96,7 @@ func (c *YouTubeClientCalcPoints) UpdateBroadcast( return c.Client.UpdateBroadcast(ctx, broadcast, parts) } -func (c *YouTubeClientCalcPoints) InsertBroadcast( +func (c *ClientCalcPoints) InsertBroadcast( ctx context.Context, broadcast *youtube.LiveBroadcast, parts []string, @@ -105,7 +105,7 @@ func (c *YouTubeClientCalcPoints) InsertBroadcast( return c.Client.InsertBroadcast(ctx, broadcast, parts) } -func (c *YouTubeClientCalcPoints) DeleteBroadcast( +func (c *ClientCalcPoints) DeleteBroadcast( ctx context.Context, broadcastID string, ) (_err error) { @@ -113,7 +113,7 @@ func (c *YouTubeClientCalcPoints) DeleteBroadcast( return c.Client.DeleteBroadcast(ctx, broadcastID) } -func (c *YouTubeClientCalcPoints) GetStreams( +func (c *ClientCalcPoints) GetStreams( ctx context.Context, parts []string, ) (_ret *youtube.LiveStreamListResponse, _err error) { @@ -121,7 +121,7 @@ func (c *YouTubeClientCalcPoints) GetStreams( return c.Client.GetStreams(ctx, parts) } -func (c *YouTubeClientCalcPoints) GetVideos( +func (c *ClientCalcPoints) GetVideos( ctx context.Context, broadcastIDs []string, parts []string, @@ -130,7 +130,7 @@ func (c *YouTubeClientCalcPoints) GetVideos( return c.Client.GetVideos(ctx, broadcastIDs, parts) } -func (c *YouTubeClientCalcPoints) UpdateVideo( +func (c *ClientCalcPoints) UpdateVideo( ctx context.Context, video *youtube.Video, parts []string, @@ -139,7 +139,7 @@ func (c *YouTubeClientCalcPoints) UpdateVideo( return c.Client.UpdateVideo(ctx, video, parts) } -func (c *YouTubeClientCalcPoints) InsertCuepoint( +func (c *ClientCalcPoints) InsertCuepoint( ctx context.Context, cuepoint *youtube.Cuepoint, ) (_err error) { @@ -147,7 +147,7 @@ func (c *YouTubeClientCalcPoints) InsertCuepoint( return c.Client.InsertCuepoint(ctx, cuepoint) } -func (c *YouTubeClientCalcPoints) GetPlaylists( +func (c *ClientCalcPoints) GetPlaylists( ctx context.Context, playlistParts []string, ) (_ret *youtube.PlaylistListResponse, _err error) { @@ -155,7 +155,7 @@ func (c *YouTubeClientCalcPoints) GetPlaylists( return c.Client.GetPlaylists(ctx, playlistParts) } -func (c *YouTubeClientCalcPoints) GetPlaylistItems( +func (c *ClientCalcPoints) GetPlaylistItems( ctx context.Context, playlistID string, videoID string, @@ -165,7 +165,7 @@ func (c *YouTubeClientCalcPoints) GetPlaylistItems( return c.Client.GetPlaylistItems(ctx, playlistID, videoID, parts) } -func (c *YouTubeClientCalcPoints) InsertPlaylistItem( +func (c *ClientCalcPoints) InsertPlaylistItem( ctx context.Context, item *youtube.PlaylistItem, parts []string, @@ -174,7 +174,7 @@ func (c *YouTubeClientCalcPoints) InsertPlaylistItem( return c.Client.InsertPlaylistItem(ctx, item, parts) } -func (c *YouTubeClientCalcPoints) SetThumbnail( +func (c *ClientCalcPoints) SetThumbnail( ctx context.Context, broadcastID string, thumbnail io.Reader, @@ -183,7 +183,7 @@ func (c *YouTubeClientCalcPoints) SetThumbnail( return c.Client.SetThumbnail(ctx, broadcastID, thumbnail) } -func (c *YouTubeClientCalcPoints) InsertCommentThread( +func (c *ClientCalcPoints) InsertCommentThread( ctx context.Context, t *youtube.CommentThread, parts []string, @@ -192,7 +192,7 @@ func (c *YouTubeClientCalcPoints) InsertCommentThread( return c.Client.InsertCommentThread(ctx, t, parts) } -func (c *YouTubeClientCalcPoints) ListChatMessages( +func (c *ClientCalcPoints) ListChatMessages( ctx context.Context, chatID string, parts []string, @@ -201,7 +201,7 @@ func (c *YouTubeClientCalcPoints) ListChatMessages( return c.Client.ListChatMessages(ctx, chatID, parts) } -func (c *YouTubeClientCalcPoints) DeleteChatMessage( +func (c *ClientCalcPoints) DeleteChatMessage( ctx context.Context, messageID string, ) (_err error) { @@ -209,7 +209,7 @@ func (c *YouTubeClientCalcPoints) DeleteChatMessage( return c.Client.DeleteChatMessage(ctx, messageID) } -func (c *YouTubeClientCalcPoints) GetLiveChatMessages( +func (c *ClientCalcPoints) GetLiveChatMessages( ctx context.Context, chatID string, pageToken string, @@ -219,7 +219,7 @@ func (c *YouTubeClientCalcPoints) GetLiveChatMessages( return c.Client.GetLiveChatMessages(ctx, chatID, pageToken, parts) } -func (c *YouTubeClientCalcPoints) Search( +func (c *ClientCalcPoints) Search( ctx context.Context, chanID string, eventType EventType, diff --git a/pkg/streamcontrol/youtube/youtube_client_mock.go b/pkg/streamcontrol/youtube/client_mock.go similarity index 82% rename from pkg/streamcontrol/youtube/youtube_client_mock.go rename to pkg/streamcontrol/youtube/client_mock.go index 4c1dbc4..6677d2f 100644 --- a/pkg/streamcontrol/youtube/youtube_client_mock.go +++ b/pkg/streamcontrol/youtube/client_mock.go @@ -9,21 +9,21 @@ import ( "google.golang.org/api/youtube/v3" ) -type YouTubeClientMock struct{} +type clientMock struct{} -var _ YouTubeClient = (*YouTubeClientMock)(nil) +var _ client = (*clientMock)(nil) -func NewYouTubeClientMock() *YouTubeClientMock { - return &YouTubeClientMock{} +func newClientMock() *clientMock { + return &clientMock{} } -func (c *YouTubeClientMock) Ping(ctx context.Context) (_err error) { +func (c *clientMock) Ping(ctx context.Context) (_err error) { logger.Tracef(ctx, "Ping") defer func() { logger.Tracef(ctx, "/Ping: %v", _err) }() return nil } -func (c *YouTubeClientMock) GetBroadcasts( +func (c *clientMock) GetBroadcasts( ctx context.Context, t BroadcastType, ids []string, @@ -39,7 +39,7 @@ func (c *YouTubeClientMock) GetBroadcasts( }, nil } -func (c *YouTubeClientMock) UpdateBroadcast( +func (c *clientMock) UpdateBroadcast( ctx context.Context, broadcast *youtube.LiveBroadcast, parts []string, @@ -49,7 +49,7 @@ func (c *YouTubeClientMock) UpdateBroadcast( return fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) InsertBroadcast( +func (c *clientMock) InsertBroadcast( ctx context.Context, broadcast *youtube.LiveBroadcast, parts []string, @@ -59,7 +59,7 @@ func (c *YouTubeClientMock) InsertBroadcast( return nil, fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) DeleteBroadcast( +func (c *clientMock) DeleteBroadcast( ctx context.Context, broadcastID string, ) (_err error) { @@ -68,7 +68,7 @@ func (c *YouTubeClientMock) DeleteBroadcast( return fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) GetStreams( +func (c *clientMock) GetStreams( ctx context.Context, parts []string, ) (_ret *youtube.LiveStreamListResponse, _err error) { @@ -80,7 +80,7 @@ func (c *YouTubeClientMock) GetStreams( }, nil } -func (c *YouTubeClientMock) GetVideos( +func (c *clientMock) GetVideos( ctx context.Context, broadcastIDs []string, parts []string, @@ -90,7 +90,7 @@ func (c *YouTubeClientMock) GetVideos( return nil, fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) UpdateVideo( +func (c *clientMock) UpdateVideo( ctx context.Context, video *youtube.Video, parts []string, @@ -100,7 +100,7 @@ func (c *YouTubeClientMock) UpdateVideo( return fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) InsertCuepoint( +func (c *clientMock) InsertCuepoint( ctx context.Context, cuepoint *youtube.Cuepoint, ) (_err error) { @@ -109,7 +109,7 @@ func (c *YouTubeClientMock) InsertCuepoint( return fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) GetPlaylists( +func (c *clientMock) GetPlaylists( ctx context.Context, playlistParts []string, ) (_ret *youtube.PlaylistListResponse, _err error) { @@ -118,7 +118,7 @@ func (c *YouTubeClientMock) GetPlaylists( return nil, fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) GetPlaylistItems( +func (c *clientMock) GetPlaylistItems( ctx context.Context, playlistID string, videoID string, @@ -129,7 +129,7 @@ func (c *YouTubeClientMock) GetPlaylistItems( return nil, fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) InsertPlaylistItem( +func (c *clientMock) InsertPlaylistItem( ctx context.Context, item *youtube.PlaylistItem, parts []string, @@ -139,7 +139,7 @@ func (c *YouTubeClientMock) InsertPlaylistItem( return fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) SetThumbnail( +func (c *clientMock) SetThumbnail( ctx context.Context, broadcastID string, thumbnail io.Reader, @@ -149,7 +149,7 @@ func (c *YouTubeClientMock) SetThumbnail( return fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) InsertCommentThread( +func (c *clientMock) InsertCommentThread( ctx context.Context, t *youtube.CommentThread, parts []string, @@ -159,7 +159,7 @@ func (c *YouTubeClientMock) InsertCommentThread( return fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) ListChatMessages( +func (c *clientMock) ListChatMessages( ctx context.Context, chatID string, parts []string, @@ -169,7 +169,7 @@ func (c *YouTubeClientMock) ListChatMessages( return nil, fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) DeleteChatMessage( +func (c *clientMock) DeleteChatMessage( ctx context.Context, messageID string, ) (_err error) { @@ -178,7 +178,7 @@ func (c *YouTubeClientMock) DeleteChatMessage( return fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) GetLiveChatMessages( +func (c *clientMock) GetLiveChatMessages( ctx context.Context, chatID string, pageToken string, @@ -189,7 +189,7 @@ func (c *YouTubeClientMock) GetLiveChatMessages( return nil, fmt.Errorf("not implemented") } -func (c *YouTubeClientMock) Search( +func (c *clientMock) Search( ctx context.Context, chanID string, eventType EventType, diff --git a/pkg/streamcontrol/youtube/youtube.go b/pkg/streamcontrol/youtube/youtube.go index dc27728..1d7361d 100644 --- a/pkg/streamcontrol/youtube/youtube.go +++ b/pkg/streamcontrol/youtube/youtube.go @@ -41,7 +41,7 @@ const ( type YouTube struct { locker xsync.Mutex Config Config - YouTubeClient *YouTubeClientCalcPoints + YouTubeClient *ClientCalcPoints CancelFunc context.CancelFunc SaveConfigFunc func(Config) error @@ -208,12 +208,12 @@ func (yt *YouTube) initNoLock(ctx context.Context) (_err error) { return fmt.Errorf("the token is invalid: %w", err) } - var youtubeClient YouTubeClient + var youtubeClient client if debugUseMockClient { - youtubeClient = NewYouTubeClientMock() + youtubeClient = newClientMock() } else { var err error - youtubeClient, err = NewYouTubeClientV3( + youtubeClient, err = newClientV3( ctx, yt.wrapRequest, option.WithTokenSource(tokenSource),