Skip to content

enrich chat with events#1213

Open
Zibbp wants to merge 1 commit into
mainfrom
enrich-chat-with-events
Open

enrich chat with events#1213
Zibbp wants to merge 1 commit into
mainfrom
enrich-chat-with-events

Conversation

@Zibbp

@Zibbp Zibbp commented Jun 14, 2026

Copy link
Copy Markdown
Owner

No description provided.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds 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 (EnrichTwitchChatMetadataFromLiveChat) propagate this metadata into stored TDL chat files. The React frontend classifies messages by kind and renders reply previews, event labels, bits indicators, and styled event/highlighted rows.

Changes

Chat metadata: data models, conversion, enrichment, and frontend rendering

Layer / File(s) Summary
Data model contracts: Go structs and TypeScript types
internal/utils/live_chat.go, internal/chat/chat.go, internal/utils/tdl.go, frontend/app/hooks/useChat.ts
Named Go types (LiveCommentBadge, LiveCommentEmote, LiveCommentReply, ChatReply) replace anonymous inline structs; Message and UserNoticeParams are extended with bits, action, reply, system-msg, and params fields; TypeScript adds GanymedeChatMessageKind enum and updates Comment, Message, Fragment, UserNoticeParams, and Reply interfaces to match.
IRC-to-LiveComment conversion refactoring and tests
internal/exec/twitch.go, internal/exec/twitch_live_chat_test.go
convertToLiveComment is rewritten via newLiveComment with dedicated helpers for badges, emotes, reply metadata, and user-notice text; convertUserNoticeToLiveComment handles USERNOTICE messages; SaveTwitchLiveChatToFile uses a shared saveLiveComment closure for both OnPrivateMessage and OnUserNoticeMessage; four unit tests cover reply preservation, resub/raid body synthesis, and emote-offset shifting.
LiveComment-to-TDL conversion, post-process enrichment, and tests
internal/utils/tdl.go, internal/tasks/live_chat.go, internal/utils/tdl_test.go
ConvertTwitchLiveChatToTDLChat now populates BitsSpent, IsAction, reply, and user-notice params in each TDL Message; new EnrichTwitchChatMetadataFromLiveChat post-processes a stored chat JSON by merging live-chat metadata into matching comments[*].message entries; ConvertLiveChatWorker calls enrichment after UpdateTwitchChat; two test files validate conversion and enrichment end-to-end.
Frontend comment classification in ChatPlayer
frontend/app/components/videos/ChatPlayer.tsx
Imports GanymedeChatMessageKind, adds top-level EVENT_LABELS notice-ID-to-label map, adds classifyComment callback that mutates each Comment with ganymede_chat_message_kind and ganymede_event_label, calls it during chatTick processing, and adds it to the useCallback dependency array.
ChatMessage CSS and JSX rendering for new message kinds
frontend/app/components/videos/ChatMessage.module.css, frontend/app/components/videos/ChatMessage.tsx
CSS adds transparent left border, border-radius, and padding to .chatMessage and introduces event, highlighted, action, reply-preview, bits-label, and event-body/system/user classes; ChatMessage derives kind flags, builds a centralized rowClassName, extracts system/user text from user_notice_params, refactors fragment rendering into renderFormattedMessage(), and adds conditional JSX for reply previews, event labels, bits labels, and event system/user bodies.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a pull request description that explains the changes, their purpose, and any relevant context for reviewers.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'enrich chat with events' directly describes the main objective of the pull request, accurately reflecting the changes that add event support, message kinds, and related metadata throughout the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enrich-chat-with-events

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
internal/utils/tdl_test.go (1)

140-170: ⚡ Quick win

Add a focused enrichment test for highlighted-message fallback metadata.

EnrichTwitchChatMetadataFromLiveChat has explicit logic for MessageType == "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

📥 Commits

Reviewing files that changed from the base of the PR and between 9b9ee05 and 94f3a9b.

📒 Files selected for processing (11)
  • frontend/app/components/videos/ChatMessage.module.css
  • frontend/app/components/videos/ChatMessage.tsx
  • frontend/app/components/videos/ChatPlayer.tsx
  • frontend/app/hooks/useChat.ts
  • internal/chat/chat.go
  • internal/exec/twitch.go
  • internal/exec/twitch_live_chat_test.go
  • internal/tasks/live_chat.go
  • internal/utils/live_chat.go
  • internal/utils/tdl.go
  • internal/utils/tdl_test.go

Comment on lines +32 to +42
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",
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Comment on lines +218 to +235
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)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

Comment thread internal/utils/tdl.go
Comment on lines 80 to +83
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"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant