Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 31, 2025

Adds opt-in chat monitoring across Twitch and Kick channels with keyword-based notifications. Users can monitor up to 50 channels and receive real-time alerts when specified keywords appear in chat.

Architecture

Shared Storage

  • Chat monitor data stored in IndexedDB following PR Refactor shared data architecture and enable cross-platform streamer UI #126 pattern
  • SharedStorageService class provides typed utility functions for accessing shared storage
  • Typed methods: getSharedStorageKey<K>(key), updateSharedStorageKey<K>(key, value) with automatic TypeScript inference
  • Chat monitor configuration stored as chatMonitor key within SharedStorageData
  • Shared across both Twitch and Kick platforms
  • Worker API actions: getSharedStorageData, setSharedStorageData
  • Callback system via onStorageChange() for automatic updates

Background Service

  • WebSocket IRC connectors for Twitch (TMI) and Kick (Pusher) with automatic reconnection
  • Keyword matching with case-insensitive detection
  • Message queue stores last 100 matches
  • Auto-updates when storage changes via callback system

Settings Integration

  • Chat monitor settings component in General tab (similar to Watchtime List)
  • Shared component works across both platforms using SharedStorageService
  • Toggle, channel list (platform + name), and keyword array
  • Array field validation enforces 50 channel limit
  • Real-time updates to background worker on save

UI Components

  • Monitor button adjacent to settings button on both platforms
  • Popup menu displays recent matches with highlighted keywords, timestamps, and platform/channel context
  • Notification badge appears on new matches via background message relay

Background-to-Content Communication

  • Worker bridge relays background messages to content scripts
  • Content scripts receive messages via WorkerService.onBackgroundMessage()
  • Avoids direct chrome.runtime.onMessage usage in page context

Example Usage

User enables monitor in settings:

{
  chatMonitor: {
    enabled: true,
    channels: [
      { platform: "twitch", channel: "xqc" },
      { platform: "kick", channel: "trainwreckstv" }
    ],
    keywords: ["essa", "!drops"]
  }
}

Access via SharedStorageService:

// Get chat monitor data with full typing
const chatMonitor = await sharedStorageService.getSharedStorageKey("chatMonitor");

// Update chat monitor data with type validation
await sharedStorageService.updateSharedStorageKey("chatMonitor", newData);

Background service connects to IRC, detects keywords, sends to content script:

// Background worker
chatMonitorService.start(channels, keywords);

// On match
chrome.runtime.sendMessage({
  action: "chatMonitorPing",
  payload: { 
    platform, 
    channel, 
    username, 
    message, 
    keyword, 
    timestamp
  }
});

// Content script receives via worker bridge
workerService.onBackgroundMessage((message) => {
  if (message.action === "chatMonitorPing") {
    // Show notification
  }
});

Implementation Notes

  • Feature disabled by default (opt-in)
  • IRC connections use anonymous authentication (no OAuth required)
  • Twitch connector sends PING every 60s, Kick every 30s
  • Exponential backoff for reconnection (max 5 attempts)
  • Worker API actions: getChatMonitorMatches, clearChatMonitorMatches, getChatMonitorStatus, getSharedStorageData, setSharedStorageData
  • Storage updates trigger automatic chat monitor refresh via callback system
  • Background messages relayed through worker bridge to avoid chrome.runtime issues in content scripts
  • SharedStorageService provides type-safe access pattern matching SettingsService
Original prompt

This section details on the original issue you should resolve

<issue_title>feature: chat monitor</issue_title>
<issue_description>we need to implement a "chat monitor" and it should work like this:

  • in settings on both platforms we need a component (like the watchtime list) with shared storage. here i can add streamer names and their platforms, for example platform: kick, channel: igorovh (let's set a limit of 50 channels for now)
  • the whole chat monitor must be opt-in/opt-out (turned off by default)
  • we need a background script that connects to twitch and kick irc. it should handle error messages if the connection fails.
  • we also need a keyword list in settings. if someone on the connected chats sends a message containing a keyword (e.g. "essa"), it triggers a ping.
  • the ping should be sent via our system to transfer events from background to content scripts. we receive the ping on the page (not background) and display it to the user.
  • create a new component for the ping menu located next to the settings button.</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI self-assigned this Dec 31, 2025
Copilot AI changed the title [WIP] Add chat monitor feature with settings and keywords Implement chat monitor for cross-platform keyword detection Dec 31, 2025
Copilot AI requested a review from igorovh December 31, 2025 03:59
@igorovh
Copy link
Member

igorovh commented Jan 2, 2026

@copilot

  1. dont do it like this, we cant store it in settings, because settings are seperated by platform and it is a shared feature - we have to like create custom component in settings that idk, maybe redirects to extension page and all of this will be here
  2. we also need to create like silent pings, when we've got this scenario: user is watching streamer X then a keyword matches on streamer X then we want to send silent ping, because user sees the chat
  3. i got error that chrome.storage.onChanged.addListener is undefined and i dont like how it is handled
  4. we should create something like shared storage, it is already created in Refactor shared data architecture and enable cross-platform streamer UI #126 can you lookup and take only the shared storage logic from this pull request?

Co-authored-by: igorovh <37638480+igorovh@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 2, 2026

@copilot

  1. dont do it like this, we cant store it in settings, because settings are seperated by platform and it is a shared feature - we have to like create custom component in settings that idk, ...

I've addressed all 4 points in commit ddc5429:

  1. Shared storage instead of platform settings - Created ChatMonitorStorageService using the same IndexedDB pattern from PR Refactor shared data architecture and enable cross-platform streamer UI #126. Chat monitor data is now stored in a shared database, not separated by platform.

  2. Silent pings - Background worker tracks active tabs and adds silent: true flag to keyword matches when user is viewing that channel. The flag is included in the chatMonitorPing message payload.

  3. Removed chrome.storage.onChanged - The background worker no longer uses chrome.storage.onChanged.addListener(). Instead, it automatically updates the chat monitor service when setChatMonitorStorageData is called.

  4. Shared storage from PR Refactor shared data architecture and enable cross-platform streamer UI #126 - Adapted the same patterns: ChatMonitorStorageDatabase, ChatMonitorStorageService, handlers, and types following the structure from that PR.

The chat monitor settings are now shown in the General tab (similar to Watchtime List) and work across both Twitch and Kick.

Copilot AI changed the title Implement chat monitor for cross-platform keyword detection Implement chat monitor for cross-platform keyword detection with shared storage Jan 2, 2026
Comment on lines 148 to 167
getChatMonitorMatches: {
payload: GetChatMonitorMatchesPayload;
response: GetChatMonitorMatchesResponse;
};
clearChatMonitorMatches: {
payload: never;
response: ClearChatMonitorMatchesResponse;
};
getChatMonitorStatus: {
payload: never;
response: GetChatMonitorStatusResponse;
};
getChatMonitorStorageData: {
payload: never;
response: any; // ChatMonitorStorageData
};
setChatMonitorStorageData: {
payload: SetChatMonitorStorageDataPayload;
response: SetChatMonitorStorageDataResponse;
};
Copy link
Member

Choose a reason for hiding this comment

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

dont do it like this, lets do it as getSharedStorage, and then key chatMonitor

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2008585. Now using getSharedStorageData/setSharedStorageData with chatMonitor as a key within SharedStorageData, following the same pattern as PR #126.

};
getChatMonitorStorageData: {
payload: never;
response: any; // ChatMonitorStorageData
Copy link
Member

Choose a reason for hiding this comment

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

dont use any

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2008585. Replaced all any types with proper types: ChatMonitorKeywordMatch[], Logger, and SharedStorageData.

export type GetSettingsResponse = PlatformSettings;
export type UpdateSettingsResponse = { success: true };

export interface GetChatMonitorMatchesPayload {
Copy link
Member

Choose a reason for hiding this comment

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

make all of the this as SharedStorage and then inside this let it be a chatMonitor

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2008585. Created SharedStorageData type with chatMonitor nested inside, following the pattern from PR #126.

const result = await handler.handle(payload);

// After setting chat monitor storage data, update the chat monitor service
if (action === "setChatMonitorStorageData") {
Copy link
Member

Choose a reason for hiding this comment

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

create "webhooks"/"callbacks" system, that when it is refresh it will call callback and then chat monitor logic will know that something changed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2008585. Implemented callback system via SharedStorageService.onStorageChange(). Background worker registers a callback that's automatically invoked when storage changes, eliminating the need to check action names.

Comment on lines 77 to 103
private updateTabInfo(tabId: number, url: string) {
// Parse URL to detect platform and channel
try {
const parsedUrl = new URL(url);
if (parsedUrl.hostname.includes("twitch.tv")) {
const pathParts = parsedUrl.pathname.split("/").filter(Boolean);
if (pathParts.length > 0 && pathParts[0] !== "directory" && pathParts[0] !== "videos") {
this.currentTabs.set(tabId, {
platform: "twitch",
channel: pathParts[0],
});
return;
}
} else if (parsedUrl.hostname.includes("kick.com")) {
const pathParts = parsedUrl.pathname.split("/").filter(Boolean);
if (pathParts.length > 0) {
this.currentTabs.set(tabId, {
platform: "kick",
channel: pathParts[0],
});
return;
}
}
} catch (error) {
// Invalid URL, ignore
}
}
Copy link
Member

Choose a reason for hiding this comment

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

i dont like this, i think the chat monitor module should like be firing event every X seconds and then we will know what tab are active and used


export class GetChatMonitorMatchesHandler extends MessageHandler {
constructor(
logger: any,
Copy link
Member

Choose a reason for hiding this comment

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

dont use any, we can use Logger type here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2008585. Changed logger parameter type from any to Logger in all chat monitor handlers.


export class SetChatMonitorStorageDataHandler extends MessageHandler {
constructor(
logger: any,
Copy link
Member

Choose a reason for hiding this comment

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

same

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2008585. Handler now uses Logger type instead of any.

Comment on lines 76 to 82
chrome.runtime.onMessage.addListener((message) => {
if (message.action === "chatMonitorPing") {
// Show notification or update badge
this.showNotificationBadge();
}
});
}
Copy link
Member

Choose a reason for hiding this comment

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

we cannot do this here, content scripts does not have access to chrome.runtime, we need to do it via background bridge (which is already created) i would also like that there be like number of notifcations in the logo

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 6778295. Added background message relay through worker bridge:

  • Worker bridge listens for chrome.runtime.onMessage (content script context)
  • Dispatches enhancer-background-message custom event to page context
  • Added WorkerService.onBackgroundMessage() for modules to listen
  • Chat monitor button modules now use worker service instead of chrome.runtime

For the notification count in logo - this will be implemented as part of the module restructuring in the next commit.

import styled from "styled-components";
import TwitchModule from "../../twitch.module.ts";

export default class ChatMonitorButtonModule extends TwitchModule {
Copy link
Member

Choose a reason for hiding this comment

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

maybe move it to the settings-button module and rename this module to navbar-icons-module wdyt, we could also seperate logic in files in this module direcotry for settings and chat monitor

Copilot AI and others added 2 commits January 2, 2026 04:16
…ta/setSharedStorageData

Co-authored-by: igorovh <37638480+igorovh@users.noreply.github.com>
…background messages

Co-authored-by: igorovh <37638480+igorovh@users.noreply.github.com>
Copilot AI requested a review from igorovh January 2, 2026 04:22
Comment on lines 196 to 215
const saveData = async (newData: ChatMonitorStorageData) => {
setSaving(true);
try {
// First get the full shared storage
const fullStorage = await workerService.send("getSharedStorageData", {});
if (fullStorage?.data) {
// Update only the chatMonitor portion
const updatedStorage: SharedStorageData = {
...fullStorage.data,
chatMonitor: newData,
};
await workerService.send("setSharedStorageData", { data: updatedStorage });
setData(newData);
}
} catch (error) {
console.error("Failed to save chat monitor data:", error);
} finally {
setSaving(false);
}
};
Copy link
Member

Choose a reason for hiding this comment

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

we could create a SharedStorage object like the SettingsService and then create "utils" function that to get this data faster, like getSharedStorageKey('chatMonitor') but it need to be fully typed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented in d8fc45f. Created SharedStorageService with typed utility functions following the SettingsService pattern:

  • getSharedStorageKey<K>(key) - fully typed key access
  • updateSharedStorageKey<K>(key, value) - fully typed key updates

Chat monitor settings component now uses:

const data = await sharedStorageService.getSharedStorageKey("chatMonitor");
await sharedStorageService.updateSharedStorageKey("chatMonitor", newData);

TypeScript automatically infers the correct types for each key.

Co-authored-by: igorovh <37638480+igorovh@users.noreply.github.com>
Copilot AI requested a review from igorovh January 2, 2026 15:04
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.

feature: chat monitor

2 participants