Skip to content

Commit 53b32c5

Browse files
committed
Linkilize youtube chat emoticons
1 parent aa3a9bd commit 53b32c5

8 files changed

Lines changed: 1272 additions & 1087 deletions

File tree

pkg/streamcontrol/event.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ type ChatUserID string
99
type ChatMessageID string
1010

1111
type ChatMessage struct {
12-
CreatedAt time.Time
13-
EventType EventType
14-
UserID ChatUserID
15-
Username string
16-
MessageID ChatMessageID
17-
Message string
18-
Paid Money
12+
CreatedAt time.Time
13+
EventType EventType
14+
UserID ChatUserID
15+
Username string
16+
MessageID ChatMessageID
17+
Message string
18+
FormatType TextFormatType
19+
Paid Money
1920
}
2021

2122
type EventType int

pkg/streamcontrol/kick/chat_handler_obsolete.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,13 @@ func (h *ChatHandlerOBSOLETE) sendMessage(
155155
h.lastMessageID = msg.ID
156156
select {
157157
case h.messagesOutChan <- streamcontrol.ChatMessage{
158-
CreatedAt: msg.CreatedAt,
159-
EventType: streamcontrol.EventTypeChatMessage,
160-
UserID: streamcontrol.ChatUserID(fmt.Sprintf("%d", msg.UserID)),
161-
Username: msg.Sender.Slug,
162-
MessageID: streamcontrol.ChatMessageID(msg.ID),
163-
Message: msg.Content,
158+
CreatedAt: msg.CreatedAt,
159+
EventType: streamcontrol.EventTypeChatMessage,
160+
UserID: streamcontrol.ChatUserID(fmt.Sprintf("%d", msg.UserID)),
161+
Username: msg.Sender.Slug,
162+
MessageID: streamcontrol.ChatMessageID(msg.ID),
163+
Message: msg.Content,
164+
FormatType: streamcontrol.TextFormatTypePlain,
164165
}:
165166
default:
166167
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package streamcontrol
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
type TextFormatType int
9+
10+
const (
11+
TextFormatTypeUndefined = TextFormatType(iota)
12+
TextFormatTypePlain
13+
TextFormatTypeMarkdown
14+
TextFormatTypeHTML
15+
)
16+
17+
func (t TextFormatType) String() string {
18+
switch t {
19+
case TextFormatTypeUndefined:
20+
return "undefined"
21+
case TextFormatTypePlain:
22+
return "plain"
23+
case TextFormatTypeMarkdown:
24+
return "markdown"
25+
case TextFormatTypeHTML:
26+
return "html"
27+
default:
28+
return fmt.Sprintf("unknown_%d", int(t))
29+
}
30+
}
31+
32+
func (t TextFormatType) MarshalJSON() ([]byte, error) {
33+
return json.Marshal(t.String())
34+
}
35+
36+
func TextFormatTypeFromString(s string) (TextFormatType, error) {
37+
for i := TextFormatTypeUndefined + 1; i < TextFormatTypeHTML; i++ {
38+
if s == i.String() {
39+
return i, nil
40+
}
41+
}
42+
return TextFormatTypeUndefined, fmt.Errorf("unknown text format type: '%v'", s)
43+
}
44+
45+
func (t *TextFormatType) UnmarshalJSON(data []byte) error {
46+
if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
47+
return fmt.Errorf("invalid JSON string: %s", data)
48+
}
49+
var s string
50+
err := json.Unmarshal(data, &s)
51+
if err != nil {
52+
return fmt.Errorf("unable to unmarshal JSON string: %w", err)
53+
}
54+
v, err := TextFormatTypeFromString(s)
55+
if err != nil {
56+
return fmt.Errorf("unable to convert string '%s' to TextFormatType: %w", s, err)
57+
}
58+
*t = v
59+
return nil
60+
}

pkg/streamcontrol/twitch/chat_handler_irc.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,13 @@ func newChatHandlerIRC(
9292
}
9393
select {
9494
case h.messagesOutChan <- streamcontrol.ChatMessage{
95-
CreatedAt: ev.CreatedAt,
96-
EventType: streamcontrol.EventTypeChatMessage,
97-
UserID: streamcontrol.ChatUserID(ev.Sender.Username),
98-
Username: ev.Sender.Username,
99-
MessageID: streamcontrol.ChatMessageID(ev.ID),
100-
Message: ev.Text, // TODO: investigate if we need ev.IRCMessage.Text
95+
CreatedAt: ev.CreatedAt,
96+
EventType: streamcontrol.EventTypeChatMessage,
97+
UserID: streamcontrol.ChatUserID(ev.Sender.Username),
98+
Username: ev.Sender.Username,
99+
MessageID: streamcontrol.ChatMessageID(ev.ID),
100+
Message: ev.Text, // TODO: investigate if we need ev.IRCMessage.Text
101+
FormatType: streamcontrol.TextFormatTypePlain,
101102
}:
102103
default:
103104
logger.Warnf(ctx, "the queue is full, skipping the message")

pkg/streamcontrol/twitch/chat_handler_sub.go

Lines changed: 73 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -95,32 +95,35 @@ func NewChatHandlerSub(
9595
}
9696
eventSubClient.OnEventAutomodMessageHold(func(event twitcheventsub.EventAutomodMessageHold, msg twitcheventsub.NotificationMessage) {
9797
h.sendMessage(ctx, streamcontrol.ChatMessage{
98-
CreatedAt: msg.Metadata.MessageTimestamp,
99-
EventType: streamcontrol.EventTypeAutoModHold,
100-
UserID: streamcontrol.ChatUserID(event.UserID),
101-
Username: event.UserName,
102-
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
103-
Message: event.Message.Text,
98+
CreatedAt: msg.Metadata.MessageTimestamp,
99+
EventType: streamcontrol.EventTypeAutoModHold,
100+
UserID: streamcontrol.ChatUserID(event.UserID),
101+
Username: event.UserName,
102+
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
103+
Message: event.Message.Text,
104+
FormatType: streamcontrol.TextFormatTypePlain,
104105
})
105106
})
106107
eventSubClient.OnEventChannelAdBreakBegin(func(event twitcheventsub.EventChannelAdBreakBegin, msg twitcheventsub.NotificationMessage) {
107108
h.sendMessage(ctx, streamcontrol.ChatMessage{
108-
CreatedAt: msg.Metadata.MessageTimestamp,
109-
EventType: streamcontrol.EventTypeAdBreak,
110-
UserID: "twitch",
111-
Username: "Twitch",
112-
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
113-
Message: fmt.Sprintf("%d seconds", event.DurationSeconds),
109+
CreatedAt: msg.Metadata.MessageTimestamp,
110+
EventType: streamcontrol.EventTypeAdBreak,
111+
UserID: "twitch",
112+
Username: "Twitch",
113+
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
114+
Message: fmt.Sprintf("%d seconds", event.DurationSeconds),
115+
FormatType: streamcontrol.TextFormatTypePlain,
114116
})
115117
})
116118
eventSubClient.OnEventChannelBan(func(event twitcheventsub.EventChannelBan, msg twitcheventsub.NotificationMessage) {
117119
h.sendMessage(ctx, streamcontrol.ChatMessage{
118-
CreatedAt: msg.Metadata.MessageTimestamp,
119-
EventType: streamcontrol.EventTypeBan,
120-
UserID: streamcontrol.ChatUserID(event.UserID),
121-
Username: event.UserName,
122-
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
123-
Message: event.Reason,
120+
CreatedAt: msg.Metadata.MessageTimestamp,
121+
EventType: streamcontrol.EventTypeBan,
122+
UserID: streamcontrol.ChatUserID(event.UserID),
123+
Username: event.UserName,
124+
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
125+
Message: event.Reason,
126+
FormatType: streamcontrol.TextFormatTypePlain,
124127
})
125128
})
126129
eventSubClient.OnEventChannelCheer(func(event twitcheventsub.EventChannelCheer, msg twitcheventsub.NotificationMessage) {
@@ -135,36 +138,40 @@ func NewChatHandlerSub(
135138
Currency: streamcontrol.CurrencyBits,
136139
Amount: float64(event.Bits),
137140
},
141+
FormatType: streamcontrol.TextFormatTypePlain,
138142
})
139143
})
140144
eventSubClient.OnEventChannelFollow(func(event twitcheventsub.EventChannelFollow, msg twitcheventsub.NotificationMessage) {
141145
h.sendMessage(ctx, streamcontrol.ChatMessage{
142-
CreatedAt: msg.Metadata.MessageTimestamp,
143-
EventType: streamcontrol.EventTypeFollow,
144-
UserID: streamcontrol.ChatUserID(event.UserID),
145-
Username: event.UserName,
146-
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
147-
Message: "",
146+
CreatedAt: msg.Metadata.MessageTimestamp,
147+
EventType: streamcontrol.EventTypeFollow,
148+
UserID: streamcontrol.ChatUserID(event.UserID),
149+
Username: event.UserName,
150+
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
151+
Message: "",
152+
FormatType: streamcontrol.TextFormatTypePlain,
148153
})
149154
})
150155
eventSubClient.OnEventChannelRaid(func(event twitcheventsub.EventChannelRaid, msg twitcheventsub.NotificationMessage) {
151156
h.sendMessage(ctx, streamcontrol.ChatMessage{
152-
CreatedAt: msg.Metadata.MessageTimestamp,
153-
EventType: streamcontrol.EventTypeRaid,
154-
UserID: streamcontrol.ChatUserID(event.FromBroadcasterUserId),
155-
Username: event.FromBroadcasterUserName,
156-
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
157-
Message: fmt.Sprintf("%d viewers", event.Viewers),
157+
CreatedAt: msg.Metadata.MessageTimestamp,
158+
EventType: streamcontrol.EventTypeRaid,
159+
UserID: streamcontrol.ChatUserID(event.FromBroadcasterUserId),
160+
Username: event.FromBroadcasterUserName,
161+
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
162+
Message: fmt.Sprintf("%d viewers", event.Viewers),
163+
FormatType: streamcontrol.TextFormatTypePlain,
158164
})
159165
})
160166
eventSubClient.OnEventChannelShoutoutReceive(func(event twitcheventsub.EventChannelShoutoutReceive, msg twitcheventsub.NotificationMessage) {
161167
h.sendMessage(ctx, streamcontrol.ChatMessage{
162-
CreatedAt: msg.Metadata.MessageTimestamp,
163-
EventType: streamcontrol.EventTypeChannelShoutoutReceive,
164-
UserID: streamcontrol.ChatUserID(event.FromBroadcasterUserId),
165-
Username: event.FromBroadcasterUserName,
166-
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
167-
Message: fmt.Sprintf("%d viewers", event.ViewerCount),
168+
CreatedAt: msg.Metadata.MessageTimestamp,
169+
EventType: streamcontrol.EventTypeChannelShoutoutReceive,
170+
UserID: streamcontrol.ChatUserID(event.FromBroadcasterUserId),
171+
Username: event.FromBroadcasterUserName,
172+
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
173+
Message: fmt.Sprintf("%d viewers", event.ViewerCount),
174+
FormatType: streamcontrol.TextFormatTypePlain,
168175
})
169176
})
170177
eventSubClient.OnEventChannelSubscribe(func(event twitcheventsub.EventChannelSubscribe, msg twitcheventsub.NotificationMessage) {
@@ -175,12 +182,13 @@ func NewChatHandlerSub(
175182
}
176183
description = append(description, fmt.Sprintf("tier '%s'", event.Tier))
177184
h.sendMessage(ctx, streamcontrol.ChatMessage{
178-
CreatedAt: msg.Metadata.MessageTimestamp,
179-
EventType: streamcontrol.EventTypeSubscribe,
180-
UserID: streamcontrol.ChatUserID(event.UserID),
181-
Username: event.UserName,
182-
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
183-
Message: strings.Join(description, " "),
185+
CreatedAt: msg.Metadata.MessageTimestamp,
186+
EventType: streamcontrol.EventTypeSubscribe,
187+
UserID: streamcontrol.ChatUserID(event.UserID),
188+
Username: event.UserName,
189+
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
190+
Message: strings.Join(description, " "),
191+
FormatType: streamcontrol.TextFormatTypePlain,
184192
})
185193
})
186194
eventSubClient.OnEventChannelSubscriptionMessage(func(event twitcheventsub.EventChannelSubscriptionMessage, msg twitcheventsub.NotificationMessage) {
@@ -194,6 +202,7 @@ func NewChatHandlerSub(
194202
"%d months (%d in total), tier '%s', message: %s",
195203
event.DurationMonths, event.CumulativeMonths, event.Tier, event.Message.Text,
196204
),
205+
FormatType: streamcontrol.TextFormatTypePlain,
197206
})
198207
})
199208
eventSubClient.OnEventChannelSubscriptionGift(func(event twitcheventsub.EventChannelSubscriptionGift, msg twitcheventsub.NotificationMessage) {
@@ -207,26 +216,29 @@ func NewChatHandlerSub(
207216
"gift: %d subs, tier '%s'",
208217
event.Total, event.Tier,
209218
),
219+
FormatType: streamcontrol.TextFormatTypePlain,
210220
})
211221
})
212222
eventSubClient.OnEventStreamOnline(func(event twitcheventsub.EventStreamOnline, msg twitcheventsub.NotificationMessage) {
213223
h.sendMessage(ctx, streamcontrol.ChatMessage{
214-
CreatedAt: msg.Metadata.MessageTimestamp,
215-
EventType: streamcontrol.EventTypeStreamOnline,
216-
UserID: streamcontrol.ChatUserID(event.BroadcasterUserId),
217-
Username: event.BroadcasterUserName,
218-
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
219-
Message: event.StartedAt.Format(time.DateTime),
224+
CreatedAt: msg.Metadata.MessageTimestamp,
225+
EventType: streamcontrol.EventTypeStreamOnline,
226+
UserID: streamcontrol.ChatUserID(event.BroadcasterUserId),
227+
Username: event.BroadcasterUserName,
228+
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
229+
Message: event.StartedAt.Format(time.DateTime),
230+
FormatType: streamcontrol.TextFormatTypePlain,
220231
})
221232
})
222233
eventSubClient.OnEventStreamOffline(func(event twitcheventsub.EventStreamOffline, msg twitcheventsub.NotificationMessage) {
223234
h.sendMessage(ctx, streamcontrol.ChatMessage{
224-
CreatedAt: msg.Metadata.MessageTimestamp,
225-
EventType: streamcontrol.EventTypeStreamOffline,
226-
UserID: streamcontrol.ChatUserID(event.BroadcasterUserId),
227-
Username: event.BroadcasterUserName,
228-
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
229-
Message: "",
235+
CreatedAt: msg.Metadata.MessageTimestamp,
236+
EventType: streamcontrol.EventTypeStreamOffline,
237+
UserID: streamcontrol.ChatUserID(event.BroadcasterUserId),
238+
Username: event.BroadcasterUserName,
239+
MessageID: streamcontrol.ChatMessageID(msg.Metadata.MessageID),
240+
Message: "",
241+
FormatType: streamcontrol.TextFormatTypePlain,
230242
})
231243
})
232244
eventSubClient.OnEventChannelChatMessage(func(chatEvent twitcheventsub.EventChannelChatMessage, msg twitcheventsub.NotificationMessage) {
@@ -236,12 +248,13 @@ func NewChatHandlerSub(
236248
eventType = streamcontrol.EventTypeCheer
237249
}
238250
h.sendMessage(ctx, streamcontrol.ChatMessage{
239-
CreatedAt: msg.Metadata.MessageTimestamp,
240-
EventType: eventType,
241-
UserID: streamcontrol.ChatUserID(chatEvent.ChatterUserId),
242-
Username: chatEvent.ChatterUserName,
243-
MessageID: streamcontrol.ChatMessageID(chatEvent.MessageId),
244-
Message: chatEvent.Message.Text,
251+
CreatedAt: msg.Metadata.MessageTimestamp,
252+
EventType: eventType,
253+
UserID: streamcontrol.ChatUserID(chatEvent.ChatterUserId),
254+
Username: chatEvent.ChatterUserName,
255+
MessageID: streamcontrol.ChatMessageID(chatEvent.MessageId),
256+
Message: chatEvent.Message.Text,
257+
FormatType: streamcontrol.TextFormatTypePlain,
245258
})
246259
})
247260

pkg/streamcontrol/youtube/chat_listener_obsolete.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"html"
89
"math/rand"
910
"net/http"
1011
"net/url"
12+
"regexp"
1113
"strings"
1214
"sync"
1315
"time"
@@ -140,20 +142,45 @@ func (l *ChatListenerOBSOLETE) listenLoop(ctx context.Context) (_err error) {
140142
l.continuationCode = newContinuation
141143

142144
for _, msg := range msgs {
145+
text, format := l.normalizeMessage(ctx, msg.Message)
143146
l.messagesOutChan <- streamcontrol.ChatMessage{
144147
CreatedAt: msg.Timestamp,
145148
EventType: streamcontrol.EventTypeChatMessage,
146149
UserID: l.getUserID(ctx, msg.AuthorID),
147150
Username: msg.AuthorName,
148151
// TODO: find a way to extract the message ID,
149152
// in the mean while we we use a soft key for that:
150-
MessageID: streamcontrol.ChatMessageID(fmt.Sprintf("%s/%s", msg.AuthorName, msg.Message)),
151-
Message: msg.Message,
153+
MessageID: streamcontrol.ChatMessageID(fmt.Sprintf("%s/%s", msg.AuthorName, msg.Message)),
154+
Message: text,
155+
FormatType: format,
152156
}
153157
}
154158
}
155159
}
156160

161+
func (h *ChatListenerOBSOLETE) normalizeMessage(
162+
ctx context.Context,
163+
msg string,
164+
) (_ret0 string, _ret1 streamcontrol.TextFormatType) {
165+
logger.Tracef(ctx, "normalizeMessage(ctx, '%v')", msg)
166+
defer func() { logger.Tracef(ctx, "/normalizeMessage(ctx, '%v'): %v %v", msg, _ret0, _ret1) }()
167+
168+
switch {
169+
case strings.Contains(msg, "https://yt3.ggpht.com/"):
170+
return messageAsHTML(msg), streamcontrol.TextFormatTypeHTML
171+
default:
172+
return msg, streamcontrol.TextFormatTypePlain
173+
}
174+
}
175+
176+
func messageAsHTML(msg string) string {
177+
msg = html.EscapeString(msg)
178+
re := regexp.MustCompile(`https://yt3\.ggpht\.com/[^\s]+`)
179+
return re.ReplaceAllStringFunc(msg, func(link string) string {
180+
return fmt.Sprintf(`<a href="%s">%s</a>`, link, link)
181+
})
182+
}
183+
157184
func (h *ChatListenerOBSOLETE) getUserID(
158185
ctx context.Context,
159186
authorID string,

0 commit comments

Comments
 (0)