* wip * copy polugin files * wip type changes * refactor: improve Twitch plugin code quality and fix all tests - Extract client manager registry for centralized lifecycle management - Refactor to use early returns and reduce mutations - Fix status check logic for clientId detection - Add comprehensive test coverage for new modules - Remove tests for unimplemented features (index.test.ts, resolver.test.ts) - Fix mock setup issues in test suite (149 tests now passing) - Improve error handling with errorResponse helper in actions.ts - Normalize token handling to eliminate duplication Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * use accountId * delete md file * delte tsconfig * adjust log level * fix probe logic * format * fix monitor * code review fixes * format * no mutation * less mutation * chain debug log * await authProvider setup * use uuid * use spread * fix tests * update docs and remove bot channel fallback * more readme fixes * remove comments + fromat * fix tests * adjust access control logic * format * install * simplify config object * remove duplicate log tags + log received messages * update docs * update tests * format * strip markdown in monitor * remove strip markdown config, enabled by default * default requireMention to true * fix store path arg * fix multi account id + add unit test * fix multi account id + add unit test * make channel required and update docs * remove whisper functionality * remove duplicate connect log * update docs with convert twitch link * make twitch message processing non blocking * schema consistent casing * remove noisy ignore log * use coreLogger --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
155 lines
3.9 KiB
TypeScript
155 lines
3.9 KiB
TypeScript
import type { TwitchAccountConfig, TwitchChatMessage } from "./types.js";
|
|
|
|
/**
|
|
* Result of checking access control for a Twitch message
|
|
*/
|
|
export type TwitchAccessControlResult = {
|
|
allowed: boolean;
|
|
reason?: string;
|
|
matchKey?: string;
|
|
matchSource?: string;
|
|
};
|
|
|
|
/**
|
|
* Check if a Twitch message should be allowed based on account configuration
|
|
*
|
|
* This function implements the access control logic for incoming Twitch messages,
|
|
* checking allowlists, role-based restrictions, and mention requirements.
|
|
*
|
|
* Priority order:
|
|
* 1. If `requireMention` is true, message must mention the bot
|
|
* 2. If `allowFrom` is set, sender must be in the allowlist (by user ID)
|
|
* 3. If `allowedRoles` is set, sender must have at least one of the specified roles
|
|
*
|
|
* Note: You can combine `allowFrom` with `allowedRoles`. If a user is in `allowFrom`,
|
|
* they bypass role checks. This is useful for allowing specific users regardless of role.
|
|
*
|
|
* Available roles:
|
|
* - "moderator": Moderators
|
|
* - "owner": Channel owner/broadcaster
|
|
* - "vip": VIPs
|
|
* - "subscriber": Subscribers
|
|
* - "all": Anyone in the chat
|
|
*/
|
|
export function checkTwitchAccessControl(params: {
|
|
message: TwitchChatMessage;
|
|
account: TwitchAccountConfig;
|
|
botUsername: string;
|
|
}): TwitchAccessControlResult {
|
|
const { message, account, botUsername } = params;
|
|
|
|
if (account.requireMention ?? true) {
|
|
const mentions = extractMentions(message.message);
|
|
if (!mentions.includes(botUsername.toLowerCase())) {
|
|
return {
|
|
allowed: false,
|
|
reason: "message does not mention the bot (requireMention is enabled)",
|
|
};
|
|
}
|
|
}
|
|
|
|
if (account.allowFrom && account.allowFrom.length > 0) {
|
|
const allowFrom = account.allowFrom;
|
|
const senderId = message.userId;
|
|
|
|
if (!senderId) {
|
|
return {
|
|
allowed: false,
|
|
reason: "sender user ID not available for allowlist check",
|
|
};
|
|
}
|
|
|
|
if (allowFrom.includes(senderId)) {
|
|
return {
|
|
allowed: true,
|
|
matchKey: senderId,
|
|
matchSource: "allowlist",
|
|
};
|
|
}
|
|
}
|
|
|
|
if (account.allowedRoles && account.allowedRoles.length > 0) {
|
|
const allowedRoles = account.allowedRoles;
|
|
|
|
// "all" grants access to everyone
|
|
if (allowedRoles.includes("all")) {
|
|
return {
|
|
allowed: true,
|
|
matchKey: "all",
|
|
matchSource: "role",
|
|
};
|
|
}
|
|
|
|
const hasAllowedRole = checkSenderRoles({
|
|
message,
|
|
allowedRoles,
|
|
});
|
|
|
|
if (!hasAllowedRole) {
|
|
return {
|
|
allowed: false,
|
|
reason: `sender does not have any of the required roles: ${allowedRoles.join(", ")}`,
|
|
};
|
|
}
|
|
|
|
return {
|
|
allowed: true,
|
|
matchKey: allowedRoles.join(","),
|
|
matchSource: "role",
|
|
};
|
|
}
|
|
|
|
return {
|
|
allowed: true,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if the sender has any of the allowed roles
|
|
*/
|
|
function checkSenderRoles(params: { message: TwitchChatMessage; allowedRoles: string[] }): boolean {
|
|
const { message, allowedRoles } = params;
|
|
const { isMod, isOwner, isVip, isSub } = message;
|
|
|
|
for (const role of allowedRoles) {
|
|
switch (role) {
|
|
case "moderator":
|
|
if (isMod) return true;
|
|
break;
|
|
case "owner":
|
|
if (isOwner) return true;
|
|
break;
|
|
case "vip":
|
|
if (isVip) return true;
|
|
break;
|
|
case "subscriber":
|
|
if (isSub) return true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Extract @mentions from a Twitch chat message
|
|
*
|
|
* Returns a list of lowercase usernames that were mentioned in the message.
|
|
* Twitch mentions are in the format @username.
|
|
*/
|
|
export function extractMentions(message: string): string[] {
|
|
const mentionRegex = /@(\w+)/g;
|
|
const mentions: string[] = [];
|
|
let match: RegExpExecArray | null;
|
|
|
|
// biome-ignore lint/suspicious/noAssignInExpressions: Standard regex iteration pattern
|
|
while ((match = mentionRegex.exec(message)) !== null) {
|
|
const username = match[1];
|
|
if (username) {
|
|
mentions.push(username.toLowerCase());
|
|
}
|
|
}
|
|
|
|
return mentions;
|
|
}
|