Skip to content

Commit 7b07bbf

Browse files
committed
Fix youtube chatter UserID
1 parent 7356358 commit 7b07bbf

7 files changed

Lines changed: 130 additions & 16 deletions

File tree

cmd/streampanel/runtime.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ func seppukuIfMemHugeLeak(
165165
buf.Reset()
166166
err := pprof.WriteHeapProfile(&buf)
167167
if err == nil {
168-
fmt.Fprintf(os.Stderr, "HEAP-PROFILE: %s\n", base64.StdEncoding.EncodeToString(buf.Bytes()))
168+
msg := fmt.Sprintf("HEAP-PROFILE: %s\n", base64.StdEncoding.EncodeToString(buf.Bytes()))
169+
fmt.Fprintf(os.Stderr, "%s", msg)
170+
logger.Debugf(ctx, msg)
169171
} else {
170172
logger.Errorf(ctx, "unable to get heap profile: %v", err)
171173
}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ replace github.com/joeyak/go-twitch-eventsub/v3 => github.com/xaionaro-go/go-twi
3333

3434
replace github.com/scorfly/gokick => github.com/xaionaro-go/gokick v0.0.0-20250713175656-f538d6396851
3535

36+
replace github.com/abhinavxd/youtube-live-chat-downloader/v2 => github.com/xaionaro-go/youtube-live-chat-downloader/v2 v2.0.0-20250726224735-811554015ae8
37+
3638
require (
3739
github.com/facebookincubator/go-belt v0.0.0-20250308011339-62fb7027b11f
3840
github.com/go-git/go-billy/v5 v5.6.2
@@ -332,7 +334,6 @@ require (
332334
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250424061409-ccd60fbc7c1c
333335
github.com/coder/websocket v1.8.13
334336
github.com/joeyak/go-twitch-eventsub/v3 v3.0.0
335-
github.com/jweslley/localtunnel v0.1.0
336337
github.com/phuslu/goid v1.0.2 // indirect
337338
github.com/pion/datachannel v1.5.10 // indirect
338339
github.com/pion/dtls/v2 v2.2.12 // indirect

go.sum

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,6 @@ github.com/RomainMichau/cloudscraper_go v0.4.1 h1:np8XbbkXXTPK+KOqtO49FYiAD34bYC
112112
github.com/RomainMichau/cloudscraper_go v0.4.1/go.mod h1:j+U5wG7A4FASVo0vroqf3td+9k0+RbLRH62IlYxg7Ao=
113113
github.com/abema/go-mp4 v1.4.1 h1:YoS4VRqd+pAmddRPLFf8vMk74kuGl6ULSjzhsIqwr6M=
114114
github.com/abema/go-mp4 v1.4.1/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws=
115-
github.com/abhinavxd/youtube-live-chat-downloader/v2 v2.0.3 h1:rJRsl52IhgD5yaUBV7npuyqxB5h5TfILn7xs0/JV220=
116-
github.com/abhinavxd/youtube-live-chat-downloader/v2 v2.0.3/go.mod h1:TrUogg8mrebgMD/JU094CmSXn3yKrt+CZjiDL3YtmMw=
117115
github.com/adeithe/go-twitch v0.3.1 h1:vGvnS3zq8fYl1sCK0I5dUozlb24eHUp1j2zZXxaBchU=
118116
github.com/adeithe/go-twitch v0.3.1/go.mod h1:hjUvA7PIUsv2oqi34mExy/clwQz026bjotsqXQOFVJY=
119117
github.com/adrg/libvlc-go/v3 v3.1.6 h1:Cm22w6xNMDdzYCW8koHgAvjonYm4xbPP5TrlVTtMdl4=
@@ -632,8 +630,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
632630
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
633631
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
634632
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
635-
github.com/jweslley/localtunnel v0.1.0 h1:9VChFBu1lfIq9s6mVF4u4roXFakWArRVF2WhGFVWBLA=
636-
github.com/jweslley/localtunnel v0.1.0/go.mod h1:gf1VMi7Ii8y7PewgFNl1LFxGrJy5uIrSDWxZzTNfddA=
637633
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
638634
github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12/go.mod h1:u9MdXq/QageOOSGp7qG4XAQsYUMP+V5zEel/Vrl6OOc=
639635
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
@@ -1088,8 +1084,6 @@ github.com/xaionaro-go/avpipeline v0.0.0-20250525204026-17104bc4baca h1:Cls4rEim
10881084
github.com/xaionaro-go/avpipeline v0.0.0-20250525204026-17104bc4baca/go.mod h1:LMh5Qi7cuntcktUezfA9toVCUCCsx9pjyGDWe9GLt9A=
10891085
github.com/xaionaro-go/datacounter v1.0.4 h1:+QMZLmu73R5WGkQfUPwlXF/JFN+Weo4iuDZkiL2wVm8=
10901086
github.com/xaionaro-go/datacounter v1.0.4/go.mod h1:Sf9vBevuV6w5iE6K3qJ9pWVKcyS60clWBUSQLjt5++c=
1091-
github.com/xaionaro-go/eventbus v0.0.0-20250712221024-f0986ef769fa h1:p4rzmAuKfYTgoHp4yL3qIL3Js4P3I91yhe654L4gnFo=
1092-
github.com/xaionaro-go/eventbus v0.0.0-20250712221024-f0986ef769fa/go.mod h1:zSbWHZpDvsRhjD3Sr3bruqqsWotjXvsIKmx6/THwXFw=
10931087
github.com/xaionaro-go/eventbus v0.0.0-20250720144534-4670758005d9 h1:ZAm8ueMw5D85LDeV1Kboc3ANqXr3LK/eXIl9hj1BJyM=
10941088
github.com/xaionaro-go/eventbus v0.0.0-20250720144534-4670758005d9/go.mod h1:zSbWHZpDvsRhjD3Sr3bruqqsWotjXvsIKmx6/THwXFw=
10951089
github.com/xaionaro-go/fyne/v2 v2.0.0-20250622004601-3a26ee69528a h1:awMQXlaweeiSZB4rSNfMmJGJriyn1ca/m/lglBi9uyA=
@@ -1163,6 +1157,8 @@ github.com/xaionaro-go/xpath v0.0.0-20250111145115-55f5728f643f h1:ofxY1akRlVdJ/
11631157
github.com/xaionaro-go/xpath v0.0.0-20250111145115-55f5728f643f/go.mod h1:f0DVcqddOy1RALOgXJHwpQnkp1u1yeBX/+A2+Bf4EGc=
11641158
github.com/xaionaro-go/xsync v0.0.0-20250713122735-6e002588c3d0 h1:JDl35mgD3RsmEx7xbBCSONLceBCTFUWQvPUG5YsWrgU=
11651159
github.com/xaionaro-go/xsync v0.0.0-20250713122735-6e002588c3d0/go.mod h1:FCpywNAl4a4hgzE8j7Z+TpdhBQi5WHxnI35jOrFpoQw=
1160+
github.com/xaionaro-go/youtube-live-chat-downloader/v2 v2.0.0-20250726224735-811554015ae8 h1:iRcIRlIryjDkgFoKJZQQWra/zv783CnKtXnpq9z7VTQ=
1161+
github.com/xaionaro-go/youtube-live-chat-downloader/v2 v2.0.0-20250726224735-811554015ae8/go.mod h1:TrUogg8mrebgMD/JU094CmSXn3yKrt+CZjiDL3YtmMw=
11661162
github.com/xaionaro-go/zerolog2belt v0.0.0-20241103164018-a3bc1ea487e5 h1:jAy7VLg8y8XE1R8jBte4PRDJzOaAE+sUfmttfB9ZcAY=
11671163
github.com/xaionaro-go/zerolog2belt v0.0.0-20241103164018-a3bc1ea487e5/go.mod h1:KJuX7yl27vU+KV6CpsWOe5ZMY4zSg70hvEhwoYdr17w=
11681164
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=

pkg/streamcontrol/kick/kick.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ func (k *Kick) onUserAccessTokenRefreshed(
110110
refreshToken string,
111111
) {
112112
ctx := context.TODO()
113+
logger.Debugf(ctx, "onUserAccessTokenRefreshed")
114+
defer logger.Debugf(ctx, "/onUserAccessTokenRefreshed")
113115
k.CurrentConfigLocker.Do(ctx, func() {
114116
k.CurrentConfig.Config.UserAccessToken.Set(userAccessToken)
115117
k.CurrentConfig.Config.RefreshToken.Set(refreshToken)

pkg/streamcontrol/youtube/chat_listener_obsolete.go

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@ package youtube
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
67
"fmt"
78
"math/rand"
89
"net/http"
910
"net/url"
11+
"strings"
1012
"sync"
1113
"time"
1214

1315
ytchat "github.com/abhinavxd/youtube-live-chat-downloader/v2"
1416
"github.com/facebookincubator/go-belt/tool/logger"
1517
"github.com/xaionaro-go/observability"
1618
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
19+
"github.com/xaionaro-go/xsync"
1720
)
1821

1922
const youtubeWatchURLString = `https://www.youtube.com/watch`
@@ -50,13 +53,18 @@ func ytWatchURL(videoID string) *url.URL {
5053
return result
5154
}
5255

56+
// TODO: delete this handler after explaining to YouTube the application and
57+
// getting a quota for normal ChatListener.
5358
type ChatListenerOBSOLETE struct {
5459
videoID string
5560
continuationCode string
5661
clientConfig ytchat.YtCfg
5762
wg sync.WaitGroup
5863
cancelFunc context.CancelFunc
5964
messagesOutChan chan streamcontrol.ChatMessage
65+
66+
channelIDToName map[string]streamcontrol.ChatUserID
67+
channelIDToNameLocker xsync.Mutex
6068
}
6169

6270
func NewChatListenerOBSOLETE(
@@ -82,6 +90,7 @@ func NewChatListenerOBSOLETE(
8290
clientConfig: cfg,
8391
cancelFunc: cancelFunc,
8492
messagesOutChan: make(chan streamcontrol.ChatMessage, 100),
93+
channelIDToName: map[string]streamcontrol.ChatUserID{},
8594
}
8695
l.wg.Add(1)
8796
observability.Go(ctx, func(ctx context.Context) {
@@ -134,7 +143,7 @@ func (l *ChatListenerOBSOLETE) listenLoop(ctx context.Context) (_err error) {
134143
l.messagesOutChan <- streamcontrol.ChatMessage{
135144
CreatedAt: msg.Timestamp,
136145
EventType: streamcontrol.EventTypeChatMessage,
137-
UserID: streamcontrol.ChatUserID(msg.AuthorName),
146+
UserID: l.getUserID(ctx, msg.AuthorID),
138147
Username: msg.AuthorName,
139148
// TODO: find a way to extract the message ID,
140149
// in the mean while we we use a soft key for that:
@@ -145,7 +154,63 @@ func (l *ChatListenerOBSOLETE) listenLoop(ctx context.Context) (_err error) {
145154
}
146155
}
147156

148-
func (h *ChatListenerOBSOLETE) Close(ctx context.Context) error {
157+
func (h *ChatListenerOBSOLETE) getUserID(
158+
ctx context.Context,
159+
authorID string,
160+
) (_ret streamcontrol.ChatUserID) {
161+
logger.Tracef(ctx, "getUserID(ctx, '%s')", authorID)
162+
defer func() { logger.Tracef(ctx, "/getUserID(ctx, '%s'): %v", authorID, _ret) }()
163+
return xsync.DoR1(ctx, &h.channelIDToNameLocker, func() streamcontrol.ChatUserID {
164+
if v, ok := h.channelIDToName[authorID]; ok {
165+
return v
166+
}
167+
168+
v, err := h.resolveChannelID(ctx, authorID)
169+
if err != nil {
170+
logger.Errorf(ctx, "unable to resolve channel ID '%s': %v", authorID, err)
171+
return streamcontrol.ChatUserID(authorID)
172+
}
173+
174+
h.channelIDToName[authorID] = v
175+
return v
176+
})
177+
}
178+
179+
func (h *ChatListenerOBSOLETE) resolveChannelID(
180+
ctx context.Context,
181+
authorID string,
182+
) (_ret streamcontrol.ChatUserID, _err error) {
183+
logger.Debugf(ctx, "resolveChannelID(ctx, '%s')", authorID)
184+
defer func() { logger.Debugf(ctx, "/resolveChannelID(ctx, '%s'): %v %v", authorID, _ret, _err) }()
185+
186+
urlString := fmt.Sprintf("https://www.youtube.com/channel/%s", authorID)
187+
initialDataBytes, _, err := ytchat.GetYTDataFromURL(urlString)
188+
if err != nil {
189+
return "", fmt.Errorf("unable to get data from '%s': %w", urlString, err)
190+
}
191+
192+
type initialDataT struct {
193+
Metadata struct {
194+
ChannelMetadataRenderer struct {
195+
VanityChannelURL string `json:"vanityChannelUrl"`
196+
} `json:"channelMetadataRenderer"`
197+
} `json:"metadata"`
198+
}
199+
var initialData initialDataT
200+
if err := json.Unmarshal(initialDataBytes, &initialData); err != nil {
201+
return "", fmt.Errorf("unable to JSON-unmarshal '%s': %w", initialDataBytes, err)
202+
}
203+
204+
vanityURLString := initialData.Metadata.ChannelMetadataRenderer.VanityChannelURL
205+
vanityURLParts := strings.Split(vanityURLString, "/")
206+
channelSlug := vanityURLParts[len(vanityURLParts)-1]
207+
channelSlug = strings.Trim(channelSlug, "@")
208+
return streamcontrol.ChatUserID(channelSlug), nil
209+
}
210+
211+
func (h *ChatListenerOBSOLETE) Close(ctx context.Context) (_err error) {
212+
logger.Debugf(ctx, "Close(ctx)")
213+
defer func() { logger.Debugf(ctx, "/Close(ctx): %v", _err) }()
149214
h.cancelFunc()
150215
return nil
151216
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"os"
8+
9+
"github.com/facebookincubator/go-belt"
10+
"github.com/facebookincubator/go-belt/tool/logger"
11+
xlogrus "github.com/facebookincubator/go-belt/tool/logger/implementation/logrus"
12+
"github.com/spf13/pflag"
13+
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
14+
)
15+
16+
func assertNoError(err error) {
17+
if err == nil {
18+
return
19+
}
20+
log.Panic(err)
21+
}
22+
23+
func main() {
24+
logLevel := logger.LevelInfo
25+
pflag.Var(&logLevel, "log-level", "")
26+
pflag.Parse()
27+
28+
if pflag.NArg() != 1 {
29+
fmt.Fprintf(os.Stderr, "expected 1 argument\n")
30+
os.Exit(1)
31+
}
32+
33+
videoID := pflag.Arg(0)
34+
35+
ctx := logger.CtxWithLogger(context.Background(), xlogrus.Default().WithLevel(logLevel))
36+
logger.Default = func() logger.Logger {
37+
return logger.FromCtx(ctx)
38+
}
39+
defer belt.Flush(ctx)
40+
41+
h, err := youtube.NewChatListenerOBSOLETE(ctx, videoID, nil)
42+
assertNoError(err)
43+
44+
fmt.Println("started")
45+
for ev := range h.MessagesChan() {
46+
fmt.Printf("%#+v\n", ev)
47+
}
48+
}

pkg/streamd/chat.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ func (d *StreamD) shoutoutIfNeeded(
7373
ctx context.Context,
7474
msg api.ChatMessage,
7575
) {
76-
logger.Tracef(ctx, "shoutoutIfNeeded(ctx, %#+v)", msg)
77-
defer logger.Tracef(ctx, "/shoutoutIfNeeded(ctx, %#+v)", msg)
76+
logger.Debugf(ctx, "shoutoutIfNeeded(ctx, %#+v)", msg)
77+
defer logger.Debugf(ctx, "/shoutoutIfNeeded(ctx, %#+v)", msg)
7878
if !msg.IsLive {
7979
logger.Tracef(ctx, "is not a live message")
8080
return
@@ -88,7 +88,7 @@ func (d *StreamD) shoutoutIfNeeded(
8888
User: streamcontrol.ChatUserID(strings.ToLower(string(msg.UserID))),
8989
}
9090
lastShoutoutAt := d.lastShoutoutAt[userID]
91-
logger.Tracef(ctx, "lastShoutoutAt(%#+v): %v", userID, lastShoutoutAt)
91+
logger.Debugf(ctx, "lastShoutoutAt(%#+v): %v", userID, lastShoutoutAt)
9292
if v := time.Since(lastShoutoutAt); v < time.Hour {
9393
logger.Tracef(ctx, "the previous shoutout was too soon: %v < %v", v, time.Hour)
9494
return
@@ -116,7 +116,7 @@ func (d *StreamD) shoutoutIfNeeded(
116116
}
117117

118118
if !found {
119-
logger.Tracef(ctx, "not in the list for auto-shoutout")
119+
logger.Debugf(ctx, "not in the list for auto-shoutout")
120120
return
121121
}
122122

@@ -128,8 +128,8 @@ func (d *StreamD) shoutoutIfCan(
128128
platID streamcontrol.PlatformName,
129129
userID streamcontrol.ChatUserID,
130130
) {
131-
logger.Tracef(ctx, "shoutoutIfCan('%s', '%s')", platID, userID)
132-
defer logger.Tracef(ctx, "/shoutoutIfCan('%s', '%s')", platID, userID)
131+
logger.Debugf(ctx, "shoutoutIfCan('%s', '%s')", platID, userID)
132+
defer logger.Debugf(ctx, "/shoutoutIfCan('%s', '%s')", platID, userID)
133133

134134
ctrl, err := d.streamController(ctx, platID)
135135
if err != nil {

0 commit comments

Comments
 (0)