mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-09-26 19:41:17 +08:00
Add auto shoutouts
This commit is contained in:
2
Makefile
2
Makefile
@@ -134,7 +134,7 @@ DOCKER_CONTAINER_NAME?=streampanel-android-builder
|
||||
|
||||
dockerbuilder-android-arm64:
|
||||
docker pull $(DOCKER_IMAGE)
|
||||
docker start $(DOCKER_IMAGE) >/dev/null 2>&1 || \
|
||||
docker start $(DOCKER_CONTAINER_NAME) >/dev/null 2>&1 || \
|
||||
docker run \
|
||||
--detach \
|
||||
--init \
|
||||
|
@@ -5,4 +5,4 @@ Website = "https://github.com/xaionaro/streamctl"
|
||||
Name = "streampanel"
|
||||
ID = "center.dx.streampanel"
|
||||
Version = "0.1.0"
|
||||
Build = 433
|
||||
Build = 437
|
||||
|
1
go.mod
1
go.mod
@@ -332,6 +332,7 @@ require (
|
||||
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250424061409-ccd60fbc7c1c
|
||||
github.com/coder/websocket v1.8.13
|
||||
github.com/joeyak/go-twitch-eventsub/v3 v3.0.0
|
||||
github.com/jweslley/localtunnel v0.1.0
|
||||
github.com/phuslu/goid v1.0.2 // indirect
|
||||
github.com/pion/datachannel v1.5.10 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.12 // indirect
|
||||
|
2
go.sum
2
go.sum
@@ -632,6 +632,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jweslley/localtunnel v0.1.0 h1:9VChFBu1lfIq9s6mVF4u4roXFakWArRVF2WhGFVWBLA=
|
||||
github.com/jweslley/localtunnel v0.1.0/go.mod h1:gf1VMi7Ii8y7PewgFNl1LFxGrJy5uIrSDWxZzTNfddA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12/go.mod h1:u9MdXq/QageOOSGp7qG4XAQsYUMP+V5zEel/Vrl6OOc=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
|
@@ -28,16 +28,17 @@ type ReverseEngClient interface {
|
||||
}
|
||||
|
||||
type Kick struct {
|
||||
CloseCtx context.Context
|
||||
CloseFn context.CancelFunc
|
||||
Channel *kickcom.ChannelV1
|
||||
Client *gokick.Client
|
||||
ClientOBSOLETE *kickcom.Kick
|
||||
ChatHandler *ChatHandlerOBSOLETE
|
||||
ChatHandlerLocker xsync.CtxLocker
|
||||
CurrentConfig Config
|
||||
SaveCfgFn func(Config) error
|
||||
PrepareLocker xsync.Mutex
|
||||
CloseCtx context.Context
|
||||
CloseFn context.CancelFunc
|
||||
Channel *kickcom.ChannelV1
|
||||
Client *gokick.Client
|
||||
ClientOBSOLETE *kickcom.Kick
|
||||
ChatHandler *ChatHandlerOBSOLETE
|
||||
ChatHandlerLocker xsync.CtxLocker
|
||||
CurrentConfig Config
|
||||
CurrentConfigLocker xsync.Mutex
|
||||
SaveCfgFn func(Config) error
|
||||
PrepareLocker xsync.Mutex
|
||||
|
||||
lazyInitOnce sync.Once
|
||||
getAccessTokenLocker xsync.Mutex
|
||||
@@ -57,9 +58,10 @@ func New(
|
||||
}
|
||||
|
||||
options := &gokick.ClientOptions{
|
||||
UserAccessToken: cfg.Config.UserAccessToken.Get(),
|
||||
ClientID: cfg.Config.ClientID,
|
||||
ClientSecret: cfg.Config.ClientSecret.Get(),
|
||||
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 {
|
||||
@@ -81,9 +83,25 @@ func New(
|
||||
ClientOBSOLETE: clientOld,
|
||||
SaveCfgFn: saveCfgFn,
|
||||
}
|
||||
client.OnUserAccessTokenRefreshed(k.onUserAccessTokenRefreshed)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func (k *Kick) onUserAccessTokenRefreshed(
|
||||
userAccessToken string,
|
||||
refreshToken string,
|
||||
) {
|
||||
ctx := context.TODO()
|
||||
k.CurrentConfigLocker.Do(ctx, func() {
|
||||
k.CurrentConfig.Config.UserAccessToken.Set(userAccessToken)
|
||||
k.CurrentConfig.Config.RefreshToken.Set(refreshToken)
|
||||
err := k.SaveCfgFn(k.CurrentConfig)
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to save the config: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (k *Kick) initChatHandler(
|
||||
ctx context.Context,
|
||||
) error {
|
||||
@@ -666,7 +684,7 @@ func (k *Kick) IsCapable(
|
||||
case streamcontrol.CapabilityBanUser:
|
||||
return true
|
||||
case streamcontrol.CapabilityShoutout:
|
||||
return false
|
||||
return true
|
||||
case streamcontrol.CapabilityIsChannelStreaming:
|
||||
return false
|
||||
case streamcontrol.CapabilityRaid:
|
||||
@@ -693,5 +711,40 @@ func (k *Kick) Shoutout(
|
||||
ctx context.Context,
|
||||
chanID streamcontrol.ChatUserID,
|
||||
) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
reply, err := k.ClientOBSOLETE.GetChannelV1(ctx, string(chanID))
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to get channel info ('%s'): %w", chanID, err)
|
||||
return k.sendShoutoutMessageWithoutChanInfo(ctx, chanID)
|
||||
}
|
||||
if len(reply.PreviousLivestreams) == 0 {
|
||||
return k.sendShoutoutMessageWithoutChanInfo(ctx, chanID)
|
||||
}
|
||||
return k.sendShoutoutMessage(ctx, chanID, reply.PreviousLivestreams[0])
|
||||
}
|
||||
|
||||
func (k *Kick) sendShoutoutMessageWithoutChanInfo(
|
||||
ctx context.Context,
|
||||
chanID streamcontrol.ChatUserID,
|
||||
) (_err error) {
|
||||
logger.Debugf(ctx, "sendShoutoutMessageWithoutChanInfo(ctx, '%s')", chanID)
|
||||
defer func() { logger.Debugf(ctx, "/sendShoutoutMessageWithoutChanInfo(ctx, '%s'): %v", chanID, _err) }()
|
||||
err := k.SendChatMessage(ctx, fmt.Sprintf("Shoutout to %s! Great creator! Take a look at their channel and click that follow button! https://www.twitch.tv/%s", chanID, chanID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to send the message (case #0): %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Kick) sendShoutoutMessage(
|
||||
ctx context.Context,
|
||||
chanID streamcontrol.ChatUserID,
|
||||
stream kickcom.LivestreamV1,
|
||||
) (_err error) {
|
||||
logger.Debugf(ctx, "sendShoutoutMessage(ctx, '%s')", chanID)
|
||||
defer func() { logger.Debugf(ctx, "/sendShoutoutMessage(ctx, '%s'): %v", chanID, _err) }()
|
||||
err := k.SendChatMessage(ctx, fmt.Sprintf("Shoutout to %s! Great creator! Their last stream: '%s'. Take a look at their channel and click that follow button! https://kick.com/%s", chanID, stream.SessionTitle, chanID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to send the message (case #1): %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -939,5 +939,43 @@ func (t *Twitch) Shoutout(
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to send the shoutout (%#+v): %w", params, err)
|
||||
}
|
||||
|
||||
reply, err := t.client.GetStreams(&helix.StreamsParams{
|
||||
UserIDs: []string{string(chanID)},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to get channel info ('%s'): %w", chanID, err)
|
||||
return t.sendShoutoutMessageWithoutChanInfo(ctx, chanID)
|
||||
}
|
||||
if len(reply.Data.Streams) == 0 {
|
||||
return t.sendShoutoutMessageWithoutChanInfo(ctx, chanID)
|
||||
}
|
||||
return t.sendShoutoutMessage(ctx, chanID, reply.Data.Streams[0])
|
||||
}
|
||||
|
||||
func (t *Twitch) sendShoutoutMessageWithoutChanInfo(
|
||||
ctx context.Context,
|
||||
chanID streamcontrol.ChatUserID,
|
||||
) (_err error) {
|
||||
logger.Debugf(ctx, "sendShoutoutMessageWithoutChanInfo(ctx, '%s')", chanID)
|
||||
defer func() { logger.Debugf(ctx, "/sendShoutoutMessageWithoutChanInfo(ctx, '%s'): %v", chanID, _err) }()
|
||||
err := t.SendChatMessage(ctx, fmt.Sprintf("Shoutout to %s! Great creator! Take a look at their channel and click that follow button! https://www.twitch.tv/%s", chanID, chanID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to send the message (case #0): %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Twitch) sendShoutoutMessage(
|
||||
ctx context.Context,
|
||||
chanID streamcontrol.ChatUserID,
|
||||
stream helix.Stream,
|
||||
) (_err error) {
|
||||
logger.Debugf(ctx, "sendShoutoutMessage(ctx, '%s')", chanID)
|
||||
defer func() { logger.Debugf(ctx, "/sendShoutoutMessage(ctx, '%s'): %v", chanID, _err) }()
|
||||
err := t.SendChatMessage(ctx, fmt.Sprintf("Shoutout to %s! Great creator! Their last stream: '%s'. Take a look at their channel and click that follow button! https://www.twitch.tv/%s", chanID, stream.Title, chanID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to send the message (case #1): %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -235,6 +235,8 @@ func getAuthCfgBase(cfg Config) *oauth2.Config {
|
||||
Endpoint: google.Endpoint,
|
||||
Scopes: []string{
|
||||
"https://www.googleapis.com/auth/youtube",
|
||||
"https://www.googleapis.com/auth/youtube.force-ssl",
|
||||
"https://www.googleapis.com/auth/youtube.upload",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1012,8 +1014,10 @@ func (yt *YouTube) startChatListener(
|
||||
yt.chatListeners[broadcast.Id] = _chatListener
|
||||
return oldListener
|
||||
})
|
||||
if err := oldListener.Close(ctx); err != nil {
|
||||
logger.Debugf(ctx, "unable to close the old chat listener: %v", err)
|
||||
if oldListener != nil {
|
||||
if err := oldListener.Close(ctx); err != nil {
|
||||
logger.Debugf(ctx, "unable to close the old chat listener: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
observability.Go(ctx, func(ctx context.Context) {
|
||||
@@ -1509,9 +1513,9 @@ func (yt *YouTube) IsCapable(
|
||||
case streamcontrol.CapabilityBanUser:
|
||||
return false
|
||||
case streamcontrol.CapabilityShoutout:
|
||||
return false
|
||||
return true
|
||||
case streamcontrol.CapabilityIsChannelStreaming:
|
||||
return false
|
||||
return true
|
||||
case streamcontrol.CapabilityRaid:
|
||||
return false
|
||||
}
|
||||
@@ -1522,13 +1526,19 @@ func (yt *YouTube) IsChannelStreaming(
|
||||
ctx context.Context,
|
||||
chanID streamcontrol.ChatUserID,
|
||||
) (bool, error) {
|
||||
return false, fmt.Errorf("not implemented")
|
||||
resp, err := yt.YouTubeClient.Search(ctx, string(chanID), EventTypeLive, []string{"snippet"})
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to search: %w", err)
|
||||
}
|
||||
|
||||
return len(resp.Items) > 0, nil
|
||||
}
|
||||
|
||||
func (yt *YouTube) RaidTo(
|
||||
ctx context.Context,
|
||||
chanID streamcontrol.ChatUserID,
|
||||
) error {
|
||||
// https://issuetracker.google.com/issues/408498307?pli=1
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
@@ -1536,5 +1546,30 @@ func (yt *YouTube) Shoutout(
|
||||
ctx context.Context,
|
||||
chanID streamcontrol.ChatUserID,
|
||||
) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
resp, err := yt.YouTubeClient.Search(ctx, string(chanID), "", []string{"snippet"})
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to get channel info ('%s'): %w", chanID, err)
|
||||
return yt.shoutoutWithoutSearch(ctx, chanID)
|
||||
}
|
||||
if len(resp.Items) == 0 {
|
||||
return yt.shoutoutWithoutSearch(ctx, chanID)
|
||||
}
|
||||
lastStream := resp.Items[0]
|
||||
|
||||
err = yt.SendChatMessage(ctx, fmt.Sprintf("Shoutout to %s! Great creator! Their last stream: '%s'. Take a look at their channel and click that subscribe button! https://www.youtube.com/channel/%s", lastStream.Snippet.ChannelTitle, lastStream.Snippet.Title, chanID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to send the message (case #0): %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (yt *YouTube) shoutoutWithoutSearch(
|
||||
ctx context.Context,
|
||||
chanID streamcontrol.ChatUserID,
|
||||
) error {
|
||||
err := yt.SendChatMessage(ctx, fmt.Sprintf("Shoutout to a great creator! Take a look at their channel and click that subscribe button! https://www.youtube.com/channel/%s", chanID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to send the message (case #1): %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -54,8 +54,17 @@ type YouTubeClient interface {
|
||||
InsertCommentThread(ctx context.Context, t *youtube.CommentThread, parts []string) error
|
||||
ListChatMessages(ctx context.Context, chatID string, parts []string) (*youtube.LiveChatMessageListResponse, error)
|
||||
DeleteChatMessage(ctx context.Context, messageID string) error
|
||||
Search(ctx context.Context, chanID string, eventType EventType, parts []string) (*youtube.SearchListResponse, error)
|
||||
}
|
||||
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
EventTypeCompleted = EventType("completed")
|
||||
EventTypeLive = EventType("live")
|
||||
EventTypeUpcoming = EventType("upcoming")
|
||||
)
|
||||
|
||||
type YouTubeClientV3 struct {
|
||||
*youtube.Service
|
||||
RequestWrapper func(context.Context, func(context.Context) error) error
|
||||
@@ -325,3 +334,21 @@ func (c *YouTubeClientV3) GetLiveChatMessages(
|
||||
}
|
||||
return q.Do()
|
||||
}
|
||||
|
||||
func (c *YouTubeClientV3) Search(
|
||||
ctx context.Context,
|
||||
chanID string,
|
||||
eventType EventType,
|
||||
parts []string,
|
||||
) (_ret *youtube.SearchListResponse, _err error) {
|
||||
logger.Tracef(ctx, "Search")
|
||||
defer func() { logger.Tracef(ctx, "/Search: %v", _err) }()
|
||||
q := c.Service.Search.List(parts).Context(ctx).Order("date").MaxResults(50)
|
||||
if chanID != "" {
|
||||
q = q.ChannelId(chanID)
|
||||
}
|
||||
if eventType != "" {
|
||||
q = q.EventType(string(eventType))
|
||||
}
|
||||
return q.Do()
|
||||
}
|
||||
|
@@ -218,3 +218,13 @@ func (c *YouTubeClientCalcPoints) GetLiveChatMessages(
|
||||
defer func() { c.addUsedPointsIfNoError(ctx, 1, _err) }()
|
||||
return c.Client.GetLiveChatMessages(ctx, chatID, pageToken, parts)
|
||||
}
|
||||
|
||||
func (c *YouTubeClientCalcPoints) Search(
|
||||
ctx context.Context,
|
||||
chanID string,
|
||||
eventType EventType,
|
||||
parts []string,
|
||||
) (_ret *youtube.SearchListResponse, _err error) {
|
||||
defer func() { c.addUsedPointsIfNoError(ctx, 1, _err) }()
|
||||
return c.Client.Search(ctx, chanID, eventType, parts)
|
||||
}
|
||||
|
@@ -188,3 +188,12 @@ func (c *YouTubeClientMock) GetLiveChatMessages(
|
||||
defer func() { logger.Tracef(ctx, "/GetLiveChatMessages: %v", _err) }()
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (c *YouTubeClientMock) Search(
|
||||
ctx context.Context,
|
||||
chanID string,
|
||||
eventType EventType,
|
||||
parts []string,
|
||||
) (_ret *youtube.SearchListResponse, _err error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
@@ -49,16 +49,20 @@ func (d *StreamD) startListeningForChatMessages(
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
msg := api.ChatMessage{
|
||||
ChatMessage: ev,
|
||||
IsLive: true,
|
||||
Platform: platName,
|
||||
}
|
||||
if err := d.ChatMessagesStorage.AddMessage(ctx, msg); err != nil {
|
||||
logger.Errorf(ctx, "unable to add the message %#+v to the chat messages storage: %v", msg, err)
|
||||
}
|
||||
publishEvent(ctx, d.EventBus, msg)
|
||||
d.shoutoutIfNeeded(ctx, msg)
|
||||
func() {
|
||||
msg := api.ChatMessage{
|
||||
ChatMessage: ev,
|
||||
IsLive: true,
|
||||
Platform: platName,
|
||||
}
|
||||
logger.Tracef(ctx, "received chat message: %#+v", msg)
|
||||
defer logger.Tracef(ctx, "finished processing the chat message")
|
||||
if err := d.ChatMessagesStorage.AddMessage(ctx, msg); err != nil {
|
||||
logger.Errorf(ctx, "unable to add the message %#+v to the chat messages storage: %v", msg, err)
|
||||
}
|
||||
publishEvent(ctx, d.EventBus, msg)
|
||||
d.shoutoutIfNeeded(ctx, msg)
|
||||
}()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -69,6 +73,8 @@ func (d *StreamD) shoutoutIfNeeded(
|
||||
ctx context.Context,
|
||||
msg api.ChatMessage,
|
||||
) {
|
||||
logger.Tracef(ctx, "shoutoutIfNeeded(ctx, %#+v)", msg)
|
||||
defer logger.Tracef(ctx, "/shoutoutIfNeeded(ctx, %#+v)", msg)
|
||||
if !msg.IsLive {
|
||||
logger.Tracef(ctx, "is not a live message")
|
||||
return
|
||||
@@ -82,6 +88,7 @@ func (d *StreamD) shoutoutIfNeeded(
|
||||
User: streamcontrol.ChatUserID(strings.ToLower(string(msg.UserID))),
|
||||
}
|
||||
lastShoutoutAt := d.lastShoutoutAt[userID]
|
||||
logger.Tracef(ctx, "lastShoutoutAt(%#+v): %v", userID, lastShoutoutAt)
|
||||
if v := time.Since(lastShoutoutAt); v < time.Hour {
|
||||
logger.Tracef(ctx, "the previous shoutout was too soon: %v < %v", v, time.Hour)
|
||||
return
|
||||
@@ -121,6 +128,9 @@ func (d *StreamD) shoutoutIfCan(
|
||||
platID streamcontrol.PlatformName,
|
||||
userID streamcontrol.ChatUserID,
|
||||
) {
|
||||
logger.Tracef(ctx, "shoutoutIfCan('%s', '%s')", platID, userID)
|
||||
defer logger.Tracef(ctx, "/shoutoutIfCan('%s', '%s')", platID, userID)
|
||||
|
||||
ctrl, err := d.streamController(ctx, platID)
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to get a stream controller '%s': %v", platID, err)
|
||||
@@ -137,6 +147,11 @@ func (d *StreamD) shoutoutIfCan(
|
||||
logger.Errorf(ctx, "unable to shoutout '%s' at '%s': %v", userID, platID, err)
|
||||
return
|
||||
}
|
||||
userFullID := config.ChatUserID{
|
||||
Platform: platID,
|
||||
User: userID,
|
||||
}
|
||||
d.lastShoutoutAt[userFullID] = time.Now()
|
||||
}
|
||||
|
||||
func (d *StreamD) RemoveChatMessage(
|
||||
|
@@ -2657,7 +2657,7 @@ func (c *Client) SubmitEvent(
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to submit the event: %w", err)
|
||||
return fmt.Errorf("unable to query: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2714,7 +2714,7 @@ func (c *Client) RemoveChatMessage(
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to submit the event: %w", err)
|
||||
return fmt.Errorf("unable to query: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2747,7 +2747,7 @@ func (c *Client) BanUser(
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to submit the event: %w", err)
|
||||
return fmt.Errorf("unable to query: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2773,7 +2773,7 @@ func (c *Client) SendChatMessage(
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to submit the event: %w", err)
|
||||
return fmt.Errorf("unable to query: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2799,7 +2799,7 @@ func (c *Client) Shoutout(
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to submit the event: %w", err)
|
||||
return fmt.Errorf("unable to query: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2825,7 +2825,7 @@ func (c *Client) RaidTo(
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to submit the event: %w", err)
|
||||
return fmt.Errorf("unable to query: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2879,7 +2879,7 @@ func (c *Client) GetPeerIDs(ctx context.Context) ([]p2ptypes.PeerID, error) {
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to submit the event: %w", err)
|
||||
return nil, fmt.Errorf("unable to query: %w", err)
|
||||
}
|
||||
|
||||
r := make([]p2ptypes.PeerID, 0, len(resp.GetPeerIDs()))
|
||||
@@ -2915,7 +2915,7 @@ func (c *Client) LLMGenerate(
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to submit the event: %w", err)
|
||||
return "", fmt.Errorf("unable to query: %w", err)
|
||||
}
|
||||
|
||||
return resp.GetResponse(), nil
|
||||
|
@@ -1133,6 +1133,8 @@ func (p *Panel) getUpdatedStatus_backends(ctx context.Context) {
|
||||
})
|
||||
}
|
||||
func (p *Panel) getUpdatedStatus_backends_noLock(ctx context.Context) {
|
||||
logger.Tracef(ctx, "getUpdatedStatus_backends_noLock")
|
||||
defer logger.Tracef(ctx, "/getUpdatedStatus_backends_noLock")
|
||||
backendEnabled := map[streamcontrol.PlatformName]bool{}
|
||||
for _, backendID := range []streamcontrol.PlatformName{
|
||||
obs.ID,
|
||||
@@ -1206,6 +1208,9 @@ func (p *Panel) getUpdatedStatus_startStopStreamButton(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (p *Panel) getUpdatedStatus_startStopStreamButton_noLock(ctx context.Context) {
|
||||
logger.Debugf(ctx, "getUpdatedStatus_startStopStreamButton_noLock")
|
||||
defer logger.Debugf(ctx, "/getUpdatedStatus_startStopStreamButton_noLock")
|
||||
|
||||
obsIsEnabled, _ := p.StreamD.IsBackendEnabled(ctx, obs.ID)
|
||||
if obsIsEnabled {
|
||||
obsStreamStatus, err := p.StreamD.GetStreamStatus(ctx, obs.ID)
|
||||
@@ -1432,8 +1437,7 @@ func (p *Panel) initMainWindow(
|
||||
return
|
||||
}
|
||||
|
||||
p.startStopButton.OnTapped()
|
||||
p.startStopButton.OnTapped()
|
||||
p.setupStreamButton.OnTapped()
|
||||
}
|
||||
p.streamTitleLabel = widget.NewLabel("")
|
||||
p.streamTitleLabel.Wrapping = fyne.TextWrapWord
|
||||
@@ -1472,8 +1476,7 @@ func (p *Panel) initMainWindow(
|
||||
return
|
||||
}
|
||||
|
||||
p.startStopButton.OnTapped()
|
||||
p.startStopButton.OnTapped()
|
||||
p.setupStreamButton.OnTapped()
|
||||
}
|
||||
p.streamDescriptionLabel = widget.NewLabel("")
|
||||
streamDescriptionButton := widget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
|
||||
@@ -1516,7 +1519,7 @@ func (p *Panel) initMainWindow(
|
||||
p.twitchCheck.Disable()
|
||||
|
||||
p.kickCheck = widget.NewCheck("Kick", nil)
|
||||
p.kickCheck.SetChecked(false)
|
||||
p.kickCheck.SetChecked(true)
|
||||
p.kickCheck.Disable()
|
||||
|
||||
p.youtubeCheck = widget.NewCheck("YouTube", nil)
|
||||
@@ -1939,9 +1942,10 @@ func (p *Panel) subscribeUpdateControlPage(ctx context.Context) {
|
||||
t := time.NewTicker(time.Second * 5)
|
||||
defer t.Stop()
|
||||
for {
|
||||
var ok bool
|
||||
ok := true
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Debugf(ctx, "subscribeUpdateControlPage: context closed")
|
||||
return
|
||||
case _, ok = <-chStreams:
|
||||
case _, ok = <-restartChStreams:
|
||||
@@ -1950,7 +1954,7 @@ func (p *Panel) subscribeUpdateControlPage(ctx context.Context) {
|
||||
case <-t.C:
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
logger.Debugf(ctx, "subscribeUpdateControlPage: channel closed")
|
||||
}
|
||||
p.getUpdatedStatus(ctx)
|
||||
}
|
||||
@@ -2117,6 +2121,12 @@ func (p *Panel) setupStreamNoLock(ctx context.Context) {
|
||||
deadline := time.Now().Add(waitFor)
|
||||
|
||||
p.streamMutex.Do(ctx, func() {
|
||||
defer func(){
|
||||
p.startStopButton.SetText(startStreamString())
|
||||
p.startStopButton.Icon = theme.MediaRecordIcon()
|
||||
p.startStopButton.Importance = widget.SuccessImportance
|
||||
p.startStopButton.Enable()
|
||||
}()
|
||||
p.startStopButton.Disable()
|
||||
p.startStopButton.Icon = theme.ViewRefreshIcon()
|
||||
p.startStopButton.Importance = widget.DangerImportance
|
||||
|
Reference in New Issue
Block a user