enrich chat with events#1213
Conversation
WalkthroughAdds reply, bits, action, highlighted, and user-notice metadata to the Twitch chat pipeline end-to-end. Go backend models, IRC converters, and a new post-process enrichment step ( ChangesChat metadata: data models, conversion, enrichment, and frontend rendering
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
internal/utils/tdl_test.go (1)
140-170: ⚡ Quick winAdd a focused enrichment test for highlighted-message fallback metadata.
EnrichTwitchChatMetadataFromLiveChathas explicit logic forMessageType == "highlighted_message"with empty params, but this suite doesn’t exercise that branch yet.Suggested test extension
liveComments := []LiveComment{ + { + Message: "highlighted text", + MessageID: "highlighted-message-id", + MessageType: "highlighted_message", + Timestamp: chatStart.Add(3 * time.Second).UnixMicro(), + }, { Message: "hello chat", MessageID: "normal-message-id", Timestamp: chatStart.UnixMicro(), }, @@ - {"_id":"notice-message-id","message":{"body":"Somebody subscribed!","bits_spent":0,"is_action":false}} + {"_id":"notice-message-id","message":{"body":"Somebody subscribed!","bits_spent":0,"is_action":false}}, + {"_id":"highlighted-message-id","message":{"body":"highlighted text","bits_spent":0,"is_action":false}} @@ +if enriched.Comments[3].Message.UserNoticeParams.MsgID != "highlighted-message" { + t.Fatalf("expected highlighted fallback msg ID, got %#v", enriched.Comments[3].Message.UserNoticeParams) +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/utils/tdl_test.go` around lines 140 - 170, The test suite for EnrichTwitchChatMetadataFromLiveChat is missing coverage for the highlighted_message fallback metadata logic with empty params. Add a fourth LiveComment entry to the liveComments array that represents a highlighted message case with an empty params map (or missing params altogether) to ensure this branch is exercised by the test suite.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/app/components/videos/ChatPlayer.tsx`:
- Around line 32-42: The EVENT_LABELS object hardcodes English display text
instead of translation keys, causing event labels to remain in English even in
localized UIs. Change EVENT_LABELS to map event types (sub, resub, subgift,
etc.) to translation key strings instead of English display text. Update the
classifyComment function (around line 296-297 where it assigns from EVENT_LABELS
to comment.ganymede_event_label) to store the translation key directly. Then in
ChatMessage.tsx, render the event label using t(comment.ganymede_event_label) to
resolve the localized text at render time, ensuring event chips display in the
user's language.
In `@internal/utils/tdl_test.go`:
- Around line 218-235: The test in internal/utils/tdl_test.go accesses
enriched.Comments at indices 0, 1, and 2 without first verifying that the slice
contains at least 3 elements, which can cause an index out of bounds panic that
obscures actual test failures. Add a guard check at the beginning of this
assertion block to verify that enriched.Comments has a length of at least 3,
using a t.Fatalf call to report the actual length if the check fails, before
attempting to access the individual comment elements via indexing.
In `@internal/utils/tdl.go`:
- Around line 80-83: The UserNoticParams struct in internal/utils/tdl.go is
serializing JSON keys in kebab-case format (msg-id and system-msg) but the
contracts in internal/chat/chat.go and frontend/app/hooks/useChat.ts expect
snake_case format (msg_id and system_msg). Update the JSON struct tags for the
MsgID field and SystemMsg field in the UserNoticParams struct to use snake_case
underscore format instead of kebab-case to ensure consistent payload shapes
across all services and prevent frontend classification issues.
---
Nitpick comments:
In `@internal/utils/tdl_test.go`:
- Around line 140-170: The test suite for EnrichTwitchChatMetadataFromLiveChat
is missing coverage for the highlighted_message fallback metadata logic with
empty params. Add a fourth LiveComment entry to the liveComments array that
represents a highlighted message case with an empty params map (or missing
params altogether) to ensure this branch is exercised by the test suite.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a7f72a05-90a6-41b2-87c0-1628630cb6fe
📒 Files selected for processing (11)
frontend/app/components/videos/ChatMessage.module.cssfrontend/app/components/videos/ChatMessage.tsxfrontend/app/components/videos/ChatPlayer.tsxfrontend/app/hooks/useChat.tsinternal/chat/chat.gointernal/exec/twitch.gointernal/exec/twitch_live_chat_test.gointernal/tasks/live_chat.gointernal/utils/live_chat.gointernal/utils/tdl.gointernal/utils/tdl_test.go
| const EVENT_LABELS: Record<string, string> = { | ||
| sub: "Sub", | ||
| resub: "Resub", | ||
| subgift: "Gift Sub", | ||
| anonsubgift: "Gift Sub", | ||
| submysterygift: "Gift Bomb", | ||
| raid: "Raid", | ||
| unraid: "Raid", | ||
| ritual: "Ritual", | ||
| announcement: "Announcement", | ||
| }; |
There was a problem hiding this comment.
Localize event labels instead of storing English display text in classification.
EVENT_LABELS currently hardcodes English strings (Line 32), and classifyComment writes those directly into comment.ganymede_event_label (Line 296). This bypasses next-intl, so event chips stay English in localized UIs (same downstream effect where ChatMessage renders "Event" / "bits" literals). Prefer mapping msg_id -> translation key here and resolving text via t(...) at render time.
Suggested direction
-const EVENT_LABELS: Record<string, string> = {
- sub: "Sub",
- resub: "Resub",
+const EVENT_LABEL_KEYS: Record<string, string> = {
+ sub: "chat.event.sub",
+ resub: "chat.event.resub",
...
};
- comment.ganymede_event_label = EVENT_LABELS[noticeID] ?? "Event";
+ comment.ganymede_event_label = EVENT_LABEL_KEYS[noticeID] ?? "chat.event.default";Then in ChatMessage.tsx, render with t(comment.ganymede_event_label) and localize bits label via translations too.
Also applies to: 296-297
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/app/components/videos/ChatPlayer.tsx` around lines 32 - 42, The
EVENT_LABELS object hardcodes English display text instead of translation keys,
causing event labels to remain in English even in localized UIs. Change
EVENT_LABELS to map event types (sub, resub, subgift, etc.) to translation key
strings instead of English display text. Update the classifyComment function
(around line 296-297 where it assigns from EVENT_LABELS to
comment.ganymede_event_label) to store the translation key directly. Then in
ChatMessage.tsx, render the event label using t(comment.ganymede_event_label) to
resolve the localized text at render time, ensuring event chips display in the
user's language.
| if !enriched.EmbeddedData["kept"] { | ||
| t.Fatal("expected unrelated top-level fields to be preserved") | ||
| } | ||
| if enriched.Comments[0].Message.Reply != nil || enriched.Comments[0].Message.UserNoticeParams.MsgID != "" { | ||
| t.Fatalf("expected untouched normal comment, got %#v", enriched.Comments[0].Message) | ||
| } | ||
| if enriched.Comments[1].Message.BitsSpent != 25 || !enriched.Comments[1].Message.IsAction { | ||
| t.Fatalf("expected bits/action metadata, got %#v", enriched.Comments[1].Message) | ||
| } | ||
| if enriched.Comments[1].Message.Reply == nil || enriched.Comments[1].Message.Reply.ParentMsgID != "parent-id" { | ||
| t.Fatalf("expected reply metadata, got %#v", enriched.Comments[1].Message.Reply) | ||
| } | ||
| if enriched.Comments[2].Message.UserNoticeParams.MsgID != "sub" { | ||
| t.Fatalf("expected user notice msg ID, got %#v", enriched.Comments[2].Message.UserNoticeParams) | ||
| } | ||
| if enriched.Comments[2].Message.UserNoticeParams.Params["msg-param-months"] != "1" { | ||
| t.Fatalf("expected user notice params, got %#v", enriched.Comments[2].Message.UserNoticeParams.Params) | ||
| } |
There was a problem hiding this comment.
Guard comment count before indexed assertions.
The test reads enriched.Comments[0..2] without a length check, which can panic and obscure failures when enrichment output changes unexpectedly.
Suggested fix
if !enriched.EmbeddedData["kept"] {
t.Fatal("expected unrelated top-level fields to be preserved")
}
+if len(enriched.Comments) != 3 {
+ t.Fatalf("expected 3 comments, got %d", len(enriched.Comments))
+}
if enriched.Comments[0].Message.Reply != nil || enriched.Comments[0].Message.UserNoticeParams.MsgID != "" {
t.Fatalf("expected untouched normal comment, got %#v", enriched.Comments[0].Message)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if !enriched.EmbeddedData["kept"] { | |
| t.Fatal("expected unrelated top-level fields to be preserved") | |
| } | |
| if enriched.Comments[0].Message.Reply != nil || enriched.Comments[0].Message.UserNoticeParams.MsgID != "" { | |
| t.Fatalf("expected untouched normal comment, got %#v", enriched.Comments[0].Message) | |
| } | |
| if enriched.Comments[1].Message.BitsSpent != 25 || !enriched.Comments[1].Message.IsAction { | |
| t.Fatalf("expected bits/action metadata, got %#v", enriched.Comments[1].Message) | |
| } | |
| if enriched.Comments[1].Message.Reply == nil || enriched.Comments[1].Message.Reply.ParentMsgID != "parent-id" { | |
| t.Fatalf("expected reply metadata, got %#v", enriched.Comments[1].Message.Reply) | |
| } | |
| if enriched.Comments[2].Message.UserNoticeParams.MsgID != "sub" { | |
| t.Fatalf("expected user notice msg ID, got %#v", enriched.Comments[2].Message.UserNoticeParams) | |
| } | |
| if enriched.Comments[2].Message.UserNoticeParams.Params["msg-param-months"] != "1" { | |
| t.Fatalf("expected user notice params, got %#v", enriched.Comments[2].Message.UserNoticeParams.Params) | |
| } | |
| if !enriched.EmbeddedData["kept"] { | |
| t.Fatal("expected unrelated top-level fields to be preserved") | |
| } | |
| if len(enriched.Comments) != 3 { | |
| t.Fatalf("expected 3 comments, got %d", len(enriched.Comments)) | |
| } | |
| if enriched.Comments[0].Message.Reply != nil || enriched.Comments[0].Message.UserNoticeParams.MsgID != "" { | |
| t.Fatalf("expected untouched normal comment, got %#v", enriched.Comments[0].Message) | |
| } | |
| if enriched.Comments[1].Message.BitsSpent != 25 || !enriched.Comments[1].Message.IsAction { | |
| t.Fatalf("expected bits/action metadata, got %#v", enriched.Comments[1].Message) | |
| } | |
| if enriched.Comments[1].Message.Reply == nil || enriched.Comments[1].Message.Reply.ParentMsgID != "parent-id" { | |
| t.Fatalf("expected reply metadata, got %#v", enriched.Comments[1].Message.Reply) | |
| } | |
| if enriched.Comments[2].Message.UserNoticeParams.MsgID != "sub" { | |
| t.Fatalf("expected user notice msg ID, got %#v", enriched.Comments[2].Message.UserNoticeParams) | |
| } | |
| if enriched.Comments[2].Message.UserNoticeParams.Params["msg-param-months"] != "1" { | |
| t.Fatalf("expected user notice params, got %#v", enriched.Comments[2].Message.UserNoticeParams.Params) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/utils/tdl_test.go` around lines 218 - 235, The test in
internal/utils/tdl_test.go accesses enriched.Comments at indices 0, 1, and 2
without first verifying that the slice contains at least 3 elements, which can
cause an index out of bounds panic that obscures actual test failures. Add a
guard check at the beginning of this assertion block to verify that
enriched.Comments has a length of at least 3, using a t.Fatalf call to report
the actual length if the check fails, before attempting to access the individual
comment elements via indexing.
| type UserNoticParams struct { | ||
| MsgID *string `json:"msg-id"` | ||
| MsgID *string `json:"msg-id,omitempty"` | ||
| SystemMsg string `json:"system-msg,omitempty"` | ||
| Params map[string]string `json:"params,omitempty"` |
There was a problem hiding this comment.
Normalize user_notice_params JSON keys to underscore form.
Line 81 and Line 82 still serialize msg-id / system-msg, while the updated contracts in internal/chat/chat.go and frontend/app/hooks/useChat.ts consume msg_id / system_msg. This creates mixed payload shapes and can break frontend classification if enrichment is skipped or partially applied.
Suggested fix
type UserNoticParams struct {
- MsgID *string `json:"msg-id,omitempty"`
- SystemMsg string `json:"system-msg,omitempty"`
+ MsgID *string `json:"msg_id,omitempty"`
+ SystemMsg string `json:"system_msg,omitempty"`
Params map[string]string `json:"params,omitempty"`
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| type UserNoticParams struct { | |
| MsgID *string `json:"msg-id"` | |
| MsgID *string `json:"msg-id,omitempty"` | |
| SystemMsg string `json:"system-msg,omitempty"` | |
| Params map[string]string `json:"params,omitempty"` | |
| type UserNoticParams struct { | |
| MsgID *string `json:"msg_id,omitempty"` | |
| SystemMsg string `json:"system_msg,omitempty"` | |
| Params map[string]string `json:"params,omitempty"` | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/utils/tdl.go` around lines 80 - 83, The UserNoticParams struct in
internal/utils/tdl.go is serializing JSON keys in kebab-case format (msg-id and
system-msg) but the contracts in internal/chat/chat.go and
frontend/app/hooks/useChat.ts expect snake_case format (msg_id and system_msg).
Update the JSON struct tags for the MsgID field and SystemMsg field in the
UserNoticParams struct to use snake_case underscore format instead of kebab-case
to ensure consistent payload shapes across all services and prevent frontend
classification issues.
No description provided.