Skip to content

Commit c82718d

Browse files
committed
Start implementing different event types in Twitch
1 parent bbc9419 commit c82718d

File tree

18 files changed

+1231
-819
lines changed

18 files changed

+1231
-819
lines changed

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ replace github.com/asticode/go-astiav v0.36.0 => github.com/xaionaro-go/astiav v
2929

3030
replace github.com/bluenviron/mediacommon/v2 v2.0.1-0.20250324151931-b8ce69d15d3d => github.com/xaionaro-go/mediacommon/v2 v2.0.0-20250420012906-03d6d69ac3b7
3131

32+
replace github.com/joeyak/go-twitch-eventsub/v3 => github.com/xaionaro-go/go-twitch-eventsub/v3 v3.0.0-20250713163657-276e5a5b7adc
33+
34+
replace github.com/scorfly/gokick => github.com/xaionaro-go/gokick v0.0.0-20250713175656-f538d6396851
35+
3236
require (
3337
github.com/facebookincubator/go-belt v0.0.0-20250308011339-62fb7027b11f
3438
github.com/go-git/go-billy/v5 v5.6.2
@@ -51,6 +55,7 @@ require (
5155
fyne.io/systray v1.11.0 // indirect
5256
github.com/BurntSushi/toml v1.5.0 // indirect
5357
github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.26 // indirect
58+
github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1 // indirect
5459
github.com/DataDog/gostackparse v0.7.0 // indirect
5560
github.com/MicahParks/jwkset v0.8.0 // indirect
5661
github.com/MicahParks/keyfunc/v3 v3.3.10 // indirect
@@ -252,7 +257,6 @@ require (
252257
fyne.io/fyne/v2 v2.5.5
253258
github.com/AgustinSRG/go-child-process-manager v1.0.1
254259
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802
255-
github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1
256260
github.com/abhinavxd/youtube-live-chat-downloader/v2 v2.0.3
257261
github.com/adeithe/go-twitch v0.3.1
258262
github.com/andreykaipov/goobs v1.4.1

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -615,8 +615,6 @@ github.com/jfreymuth/oggvorbis v1.0.5 h1:u+Ck+R0eLSRhgq8WTmffYnrVtSztJcYrl588DM4
615615
github.com/jfreymuth/oggvorbis v1.0.5/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
616616
github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvNE=
617617
github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ=
618-
github.com/joeyak/go-twitch-eventsub/v3 v3.0.0 h1:6BDgmYJynNDyCP7P+wM9jPQnE3leJAi58nohDnzliJ4=
619-
github.com/joeyak/go-twitch-eventsub/v3 v3.0.0/go.mod h1:rpqOjYP1ftWDj3H4D8fA58AdOpkvK9YvODoduDpPCQU=
620618
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
621619
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
622620
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -948,8 +946,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
948946
github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
949947
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
950948
github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
951-
github.com/scorfly/gokick v1.11.0 h1:4VMpyO8hnPUW4dYgCfOi/YzMgVgj/Rh/OJW7oi2rn+0=
952-
github.com/scorfly/gokick v1.11.0/go.mod h1:9Tc5+/nwcCLjgDVUeW2K/vhEbkMkN+OE8UQVx4fL1WA=
953949
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
954950
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
955951
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
@@ -1096,6 +1092,10 @@ github.com/xaionaro-go/fyne/v2 v2.0.0-20250622004601-3a26ee69528a h1:awMQXlaweei
10961092
github.com/xaionaro-go/fyne/v2 v2.0.0-20250622004601-3a26ee69528a/go.mod h1:0GOXKqyvNwk3DLmsFu9v0oYM0ZcD1ysGnlHCerKoAmo=
10971093
github.com/xaionaro-go/go-rtmp v0.0.0-20241009130244-1e3160f27f42 h1:izCjREd+62HDF9FRYqUI7dgJNdUxAIysEuqed8lBcDY=
10981094
github.com/xaionaro-go/go-rtmp v0.0.0-20241009130244-1e3160f27f42/go.mod h1:IuQWd+hy/tLuvuqFX0N9SMZrzOprM8Jvvdu+42RJwk4=
1095+
github.com/xaionaro-go/go-twitch-eventsub/v3 v3.0.0-20250713163657-276e5a5b7adc h1:RwmId5gCeAesuyNKr7BaakXLmHSUxOUqRwV3ME32peU=
1096+
github.com/xaionaro-go/go-twitch-eventsub/v3 v3.0.0-20250713163657-276e5a5b7adc/go.mod h1:rpqOjYP1ftWDj3H4D8fA58AdOpkvK9YvODoduDpPCQU=
1097+
github.com/xaionaro-go/gokick v0.0.0-20250713175656-f538d6396851 h1:P9DQMVL1OWRGKlXZFIwgzivUKsYfQV0y8lfYi5Ek8Yw=
1098+
github.com/xaionaro-go/gokick v0.0.0-20250713175656-f538d6396851/go.mod h1:9Tc5+/nwcCLjgDVUeW2K/vhEbkMkN+OE8UQVx4fL1WA=
10991099
github.com/xaionaro-go/goobs v0.0.0-20241103210141-030e538ac440 h1:hzQ+65oWq54XAqheyJ9E6wt+WH75051w+eLP5zWlD68=
11001100
github.com/xaionaro-go/goobs v0.0.0-20241103210141-030e538ac440/go.mod h1:Fhk0SWHxZ1atNEIm3tUlB93HHRl/v6hWulG/B7BkIOk=
11011101
github.com/xaionaro-go/gorex v0.0.0-20241010205749-bcd59d639c4d h1:9DyH0lboWWzKUwiqGmp9sTZ3bSPhgJHiiWgV+hqY9Uo=

pkg/chatmessagesstorage/sort_and_deduplicate.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ func msgLess(ctx context.Context, a *api.ChatMessage, b *api.ChatMessage) bool {
1212
if a.CreatedAt != b.CreatedAt {
1313
return a.CreatedAt.Before(b.CreatedAt)
1414
}
15+
if a.EventType != b.EventType {
16+
return a.EventType < b.EventType
17+
}
1518
if a.Platform != b.Platform {
1619
return a.Platform < b.Platform
1720
}

pkg/streamcontrol/event.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package streamcontrol
2+
3+
import "time"
4+
5+
type ChatUserID string
6+
type ChatMessageID string
7+
8+
type ChatMessage struct {
9+
CreatedAt time.Time
10+
EventType EventType
11+
UserID ChatUserID
12+
Username string
13+
MessageID ChatMessageID
14+
Message string
15+
Paid Money
16+
}
17+
18+
type EventType int
19+
20+
const (
21+
EventTypeUndefined = EventType(iota)
22+
EventTypeChatMessage
23+
EventTypeCheer
24+
EventTypeAutoModHold
25+
EventTypeAdBreak
26+
EventTypeBan
27+
EventTypeFollow
28+
EventTypeRaid
29+
EventTypeChannelShoutoutReceive
30+
EventTypeSubscribe
31+
EventTypeStreamOnline
32+
EventTypeStreamOffline
33+
EventTypeOther
34+
)

pkg/streamcontrol/kick/chat_handler_obsolete.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ func (h *ChatHandlerOBSOLETE) sendMessage(
148148
select {
149149
case h.messagesOutChan <- streamcontrol.ChatMessage{
150150
CreatedAt: msg.CreatedAt,
151+
EventType: streamcontrol.EventTypeChatMessage,
151152
UserID: streamcontrol.ChatUserID(fmt.Sprintf("%d", msg.UserID)),
152153
Username: msg.Sender.Slug,
153154
MessageID: streamcontrol.ChatMessageID(msg.ID),

pkg/streamcontrol/kick/kick.go

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,16 @@ func New(
7878

7979
func (k *Kick) initChatHandler(
8080
ctx context.Context,
81-
) {
81+
) error {
8282
if !k.ChatHandlerLocker.Lock(ctx) {
83-
logger.Debugf(ctx, "initChatHandler: cancelled (case #0)")
84-
return
83+
return ctx.Err()
8584
}
8685

8786
chatHandler, err := k.newChatHandlerOBSOLETE(ctx, k.CurrentConfig.Config.Channel, k.onChatHandlerClose)
8887
if err == nil {
8988
k.ChatHandlerLocker.Unlock()
9089
k.ChatHandler = chatHandler
91-
return
90+
return nil
9291
}
9392

9493
go func() {
@@ -110,6 +109,7 @@ func (k *Kick) initChatHandler(
110109
}
111110
k.ChatHandler = chatHandler
112111
}()
112+
return nil
113113
}
114114

115115
func (k *Kick) onChatHandlerClose(
@@ -194,18 +194,21 @@ func (k *Kick) getAccessTokenNoLock(
194194
codeVerifierSHA256 := sha256.Sum256([]byte(codeVerifier))
195195
codeChallenge := base64.URLEncoding.EncodeToString(codeVerifierSHA256[:])
196196

197+
scopes := []gokick.Scope{
198+
gokick.ScopeUserRead,
199+
gokick.ScopeChannelRead,
200+
gokick.ScopeChannelWrite,
201+
gokick.ScopeChatWrite,
202+
gokick.ScopeStremkeyRead,
203+
gokick.ScopeEventSubscribe,
204+
gokick.ScopeModerationBan,
205+
}
206+
logger.Debugf(ctx, "scopes: %v", scopes)
197207
authURL, err := k.getClient().GetAuthorize(
198208
redirectURL,
199209
"EMPTY",
200210
codeChallenge,
201-
[]gokick.Scope{
202-
gokick.ScopeUserRead,
203-
gokick.ScopeChannelRead,
204-
gokick.ScopeChannelWrite,
205-
gokick.ScopeChatWrite,
206-
gokick.ScopeStremkeyRead,
207-
gokick.ScopeEventSubscribe,
208-
},
211+
scopes,
209212
)
210213
if err != nil {
211214
return fmt.Errorf("unable to get an authorization endpoint URL: %w", err)
@@ -574,25 +577,31 @@ func (k *Kick) prepare(ctx context.Context) (_err error) {
574577
func (k *Kick) prepareNoLock(ctx context.Context) error {
575578
err := k.getAccessTokenIfNeeded(ctx)
576579
if err != nil {
577-
return err
580+
return fmt.Errorf("getAccessTokenIfNeeded: %w", err)
578581
}
579582

580583
k.lazyInitOnce.Do(func() {
581-
k.initChannelInfo(ctx)
582-
k.initChatHandler(ctx)
584+
if err = k.initChannelInfo(ctx); err != nil {
585+
err = fmt.Errorf("initChannelInfo: %w", err)
586+
return
587+
}
588+
if err = k.initChatHandler(ctx); err != nil {
589+
err = fmt.Errorf("initChatHandler: %w", err)
590+
return
591+
}
583592
})
584593
return err
585594
}
586595

587596
func (k *Kick) initChannelInfo(
588597
ctx context.Context,
589-
) {
598+
) error {
590599
var channel *gokick.ChannelResponse
591600
cache := CacheFromCtx(ctx)
592601
if chanInfo := cache.GetChanInfo(); chanInfo != nil && chanInfo.Slug == k.CurrentConfig.Config.Channel {
593602
logger.Debugf(ctx, "reuse the cache, instead of querying channel info")
594603
k.Channel = chanInfo
595-
return
604+
return nil
596605
}
597606

598607
for {
@@ -613,7 +622,7 @@ func (k *Kick) initChannelInfo(
613622
cache.SetChanInfo(channel)
614623
}
615624
k.Channel = channel
616-
return
625+
return nil
617626
}
618627
}
619628

pkg/streamcontrol/stream_control.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -106,22 +106,11 @@ type StreamStatus struct {
106106
CustomData any `json:",omitempty"`
107107
}
108108

109-
type ChatUserID string
110-
type ChatMessageID string
111-
112-
type ChatMessage struct {
113-
CreatedAt time.Time
114-
UserID ChatUserID
115-
Username string
116-
MessageID ChatMessageID
117-
Message string
118-
Paid Money
119-
}
120-
121109
type Currency int
122110

123111
const (
124112
CurrencyNone = Currency(iota)
113+
CurrencyBits
125114
CurrencyUSD
126115
CurrencyOther
127116
)

pkg/streamcontrol/twitch/auth/client_code.go

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package auth
33
import (
44
"context"
55
"fmt"
6+
"maps"
7+
"slices"
8+
"sort"
69
"strings"
710
"sync"
811
"time"
@@ -17,6 +20,99 @@ import (
1720

1821
type OAuthHandler func(context.Context, oauthhandler.OAuthHandlerArgument) error
1922

23+
func getScopes() []string {
24+
scopes := map[string]struct{}{
25+
"chat:read": {},
26+
"chat:edit": {},
27+
28+
"analytics:read:extensions": {},
29+
"analytics:read:games": {},
30+
31+
"bits:read": {},
32+
33+
"channel:bot": {},
34+
"channel:manage:ads": {},
35+
"channel:read:ads": {},
36+
"channel:manage:broadcast": {},
37+
"channel:read:charity": {},
38+
"channel:edit:commercial": {},
39+
"channel:read:editors": {},
40+
"channel:manage:extensions": {},
41+
"channel:read:goals": {},
42+
"channel:read:guest_star": {},
43+
"channel:manage:guest_star": {},
44+
"channel:read:hype_train": {},
45+
"channel:manage:moderators": {},
46+
"channel:read:polls": {},
47+
"channel:manage:polls": {},
48+
"channel:read:predictions": {},
49+
"channel:manage:predictions": {},
50+
"channel:manage:raids": {},
51+
"channel:read:redemptions": {},
52+
"channel:manage:redemptions": {},
53+
"channel:manage:schedule": {},
54+
"channel:read:stream_key": {},
55+
"channel:read:subscriptions": {},
56+
"channel:manage:videos": {},
57+
"channel:read:vips": {},
58+
"channel:manage:vips": {},
59+
"channel:moderate": {},
60+
61+
"clips:edit": {},
62+
63+
"moderation:read": {},
64+
65+
"moderator:manage:announcements": {},
66+
"moderator:manage:automod": {},
67+
"moderator:read:automod_settings": {},
68+
"moderator:manage:automod_settings": {},
69+
"moderator:read:banned_users": {},
70+
"moderator:manage:banned_users": {},
71+
"moderator:read:blocked_terms": {},
72+
"moderator:read:chat_messages": {},
73+
"moderator:manage:blocked_terms": {},
74+
"moderator:manage:chat_messages": {},
75+
"moderator:read:chat_settings": {},
76+
"moderator:manage:chat_settings": {},
77+
"moderator:read:chatters": {},
78+
"moderator:read:followers": {},
79+
"moderator:read:guest_star": {},
80+
"moderator:manage:guest_star": {},
81+
"moderator:read:moderators": {},
82+
"moderator:read:shield_mode": {},
83+
"moderator:manage:shield_mode": {},
84+
"moderator:read:shoutouts": {},
85+
"moderator:manage:shoutouts": {},
86+
"moderator:read:suspicious_users": {},
87+
"moderator:read:unban_requests": {},
88+
"moderator:manage:unban_requests": {},
89+
"moderator:read:vips": {},
90+
"moderator:read:warnings": {},
91+
"moderator:manage:warnings": {},
92+
93+
"user:bot": {},
94+
"user:edit": {},
95+
"user:edit:broadcast": {},
96+
"user:read:blocked_users": {},
97+
"user:manage:blocked_users": {},
98+
"user:read:broadcast": {},
99+
"user:read:chat": {},
100+
"user:manage:chat_color": {},
101+
"user:read:email": {},
102+
"user:read:emotes": {},
103+
"user:read:follows": {},
104+
"user:read:moderated_channels": {},
105+
"user:read:subscriptions": {},
106+
"user:read:whispers": {},
107+
"user:manage:whispers": {},
108+
"user:write:chat": {},
109+
}
110+
111+
scopesStrings := slices.Collect(maps.Keys(scopes))
112+
sort.Strings(scopesStrings)
113+
return scopesStrings
114+
}
115+
20116
func NewClientCode(
21117
ctx context.Context,
22118
clientID string,
@@ -69,14 +165,7 @@ func NewClientCode(
69165
authURL := GetAuthorizationURL(
70166
&helix.AuthorizationURLParams{
71167
ResponseType: "code", // or "token"
72-
Scopes: []string{
73-
"user:read:chat",
74-
"chat:read",
75-
"chat:edit",
76-
"channel:manage:broadcast",
77-
"moderator:manage:chat_messages",
78-
"moderator:manage:banned_users",
79-
},
168+
Scopes: getScopes(),
80169
},
81170
clientID,
82171
RedirectURI(listenPort),

pkg/streamcontrol/twitch/chat_handler_irc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ func newChatHandlerIRC(
9393
select {
9494
case h.messagesOutChan <- streamcontrol.ChatMessage{
9595
CreatedAt: ev.CreatedAt,
96+
EventType: streamcontrol.EventTypeChatMessage,
9697
UserID: streamcontrol.ChatUserID(ev.Sender.Username),
9798
Username: ev.Sender.Username,
9899
MessageID: streamcontrol.ChatMessageID(ev.ID),

0 commit comments

Comments
 (0)