feat: Twitch Plugin (#1612)
* 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>
This commit is contained in:
116
extensions/twitch/src/config.ts
Normal file
116
extensions/twitch/src/config.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import type { ClawdbotConfig } from "clawdbot/plugin-sdk";
|
||||
import type { TwitchAccountConfig } from "./types.js";
|
||||
|
||||
/**
|
||||
* Default account ID for Twitch
|
||||
*/
|
||||
export const DEFAULT_ACCOUNT_ID = "default";
|
||||
|
||||
/**
|
||||
* Get account config from core config
|
||||
*
|
||||
* Handles two patterns:
|
||||
* 1. Simplified single-account: base-level properties create implicit "default" account
|
||||
* 2. Multi-account: explicit accounts object
|
||||
*
|
||||
* For "default" account, base-level properties take precedence over accounts.default
|
||||
* For other accounts, only the accounts object is checked
|
||||
*/
|
||||
export function getAccountConfig(
|
||||
coreConfig: unknown,
|
||||
accountId: string,
|
||||
): TwitchAccountConfig | null {
|
||||
if (!coreConfig || typeof coreConfig !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cfg = coreConfig as ClawdbotConfig;
|
||||
const twitch = cfg.channels?.twitch;
|
||||
// Access accounts via unknown to handle union type (single-account vs multi-account)
|
||||
const twitchRaw = twitch as Record<string, unknown> | undefined;
|
||||
const accounts = twitchRaw?.accounts as Record<string, TwitchAccountConfig> | undefined;
|
||||
|
||||
// For default account, check base-level config first
|
||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||
const accountFromAccounts = accounts?.[DEFAULT_ACCOUNT_ID];
|
||||
|
||||
// Base-level properties that can form an implicit default account
|
||||
const baseLevel = {
|
||||
username: typeof twitchRaw?.username === "string" ? twitchRaw.username : undefined,
|
||||
accessToken: typeof twitchRaw?.accessToken === "string" ? twitchRaw.accessToken : undefined,
|
||||
clientId: typeof twitchRaw?.clientId === "string" ? twitchRaw.clientId : undefined,
|
||||
channel: typeof twitchRaw?.channel === "string" ? twitchRaw.channel : undefined,
|
||||
enabled: typeof twitchRaw?.enabled === "boolean" ? twitchRaw.enabled : undefined,
|
||||
allowFrom: Array.isArray(twitchRaw?.allowFrom) ? twitchRaw.allowFrom : undefined,
|
||||
allowedRoles: Array.isArray(twitchRaw?.allowedRoles) ? twitchRaw.allowedRoles : undefined,
|
||||
requireMention:
|
||||
typeof twitchRaw?.requireMention === "boolean" ? twitchRaw.requireMention : undefined,
|
||||
clientSecret:
|
||||
typeof twitchRaw?.clientSecret === "string" ? twitchRaw.clientSecret : undefined,
|
||||
refreshToken:
|
||||
typeof twitchRaw?.refreshToken === "string" ? twitchRaw.refreshToken : undefined,
|
||||
expiresIn: typeof twitchRaw?.expiresIn === "number" ? twitchRaw.expiresIn : undefined,
|
||||
obtainmentTimestamp:
|
||||
typeof twitchRaw?.obtainmentTimestamp === "number"
|
||||
? twitchRaw.obtainmentTimestamp
|
||||
: undefined,
|
||||
};
|
||||
|
||||
// Merge: base-level takes precedence over accounts.default
|
||||
const merged: Partial<TwitchAccountConfig> = {
|
||||
...accountFromAccounts,
|
||||
...baseLevel,
|
||||
} as Partial<TwitchAccountConfig>;
|
||||
|
||||
// Only return if we have at least username
|
||||
if (merged.username) {
|
||||
return merged as TwitchAccountConfig;
|
||||
}
|
||||
|
||||
// Fall through to accounts.default if no base-level username
|
||||
if (accountFromAccounts) {
|
||||
return accountFromAccounts;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// For non-default accounts, only check accounts object
|
||||
if (!accounts || !accounts[accountId]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return accounts[accountId] as TwitchAccountConfig | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all configured account IDs
|
||||
*
|
||||
* Includes both explicit accounts and implicit "default" from base-level config
|
||||
*/
|
||||
export function listAccountIds(cfg: ClawdbotConfig): string[] {
|
||||
const twitch = cfg.channels?.twitch;
|
||||
// Access accounts via unknown to handle union type (single-account vs multi-account)
|
||||
const twitchRaw = twitch as Record<string, unknown> | undefined;
|
||||
const accountMap = twitchRaw?.accounts as Record<string, unknown> | undefined;
|
||||
|
||||
const ids: string[] = [];
|
||||
|
||||
// Add explicit accounts
|
||||
if (accountMap) {
|
||||
ids.push(...Object.keys(accountMap));
|
||||
}
|
||||
|
||||
// Add implicit "default" if base-level config exists and "default" not already present
|
||||
const hasBaseLevelConfig =
|
||||
twitchRaw &&
|
||||
(typeof twitchRaw.username === "string" ||
|
||||
typeof twitchRaw.accessToken === "string" ||
|
||||
typeof twitchRaw.channel === "string");
|
||||
|
||||
if (hasBaseLevelConfig && !ids.includes(DEFAULT_ACCOUNT_ID)) {
|
||||
ids.push(DEFAULT_ACCOUNT_ID);
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
Reference in New Issue
Block a user