diff --git a/extensions/twitch/src/client-manager-registry.ts b/extensions/twitch/src/client-manager-registry.ts index 6218af1ce..24b81cc5c 100644 --- a/extensions/twitch/src/client-manager-registry.ts +++ b/extensions/twitch/src/client-manager-registry.ts @@ -79,10 +79,7 @@ export async function removeClientManager(accountId: string): Promise { } // Disconnect the client manager - await entry.manager.disconnect({ - username: accountId, - channel: accountId, - } as Parameters[0]); + await entry.manager.disconnectAll(); // Remove from registry registry.delete(accountId); diff --git a/extensions/twitch/src/plugin.ts b/extensions/twitch/src/plugin.ts index 8f4b5c948..96afb99cf 100644 --- a/extensions/twitch/src/plugin.ts +++ b/extensions/twitch/src/plugin.ts @@ -16,6 +16,7 @@ import { probeTwitch } from "./probe.js"; import { resolveTwitchTargets } from "./resolver.js"; import { collectTwitchStatusIssues } from "./status.js"; import { removeClientManager } from "./client-manager-registry.js"; +import { resolveTwitchToken } from "./token.js"; import type { ChannelAccountSnapshot, ChannelCapabilities, @@ -30,8 +31,13 @@ import type { /** * Check if an account is properly configured. */ -function isConfigured(account: TwitchAccountConfig | null | undefined): boolean { - return Boolean(account?.token && account?.username && account?.clientId); +function isConfigured( + account: TwitchAccountConfig | null | undefined, + cfg: ClawdbotConfig, + accountId: string, +): boolean { + const tokenResolution = resolveTwitchToken(cfg, { accountId }); + return Boolean(account?.username && account?.clientId && tokenResolution.token); } /** @@ -105,7 +111,7 @@ export const twitchPlugin: ChannelPlugin = { /** Check if an account is configured */ isConfigured: (_account: unknown, cfg: ClawdbotConfig): boolean => { const account = getAccountConfig(cfg, DEFAULT_ACCOUNT_ID); - return isConfigured(account); + return isConfigured(account, cfg, DEFAULT_ACCOUNT_ID); }, /** Check if an account is enabled */ @@ -115,7 +121,7 @@ export const twitchPlugin: ChannelPlugin = { describeAccount: (account: TwitchAccountConfig | undefined) => ({ accountId: DEFAULT_ACCOUNT_ID, enabled: account?.enabled !== false, - configured: account ? isConfigured(account) : false, + configured: account ? isConfigured(account, cfg, DEFAULT_ACCOUNT_ID) : false, }), }, @@ -205,10 +211,13 @@ export const twitchPlugin: ChannelPlugin = { runtime?: ChannelAccountSnapshot; probe?: unknown; }): ChannelAccountSnapshot => { + const accountMap = cfg.channels?.twitch?.accounts ?? {}; + const resolvedAccountId = + Object.entries(accountMap).find(([, value]) => value === account)?.[0] ?? DEFAULT_ACCOUNT_ID; return { accountId: DEFAULT_ACCOUNT_ID, enabled: account?.enabled !== false, - configured: isConfigured(account), + configured: isConfigured(account, cfg, resolvedAccountId), running: runtime?.running ?? false, lastStartAt: runtime?.lastStartAt ?? null, lastStopAt: runtime?.lastStopAt ?? null, diff --git a/extensions/twitch/src/probe.ts b/extensions/twitch/src/probe.ts index 9b908a9b0..8f3cbfcb2 100644 --- a/extensions/twitch/src/probe.ts +++ b/extensions/twitch/src/probe.ts @@ -1,6 +1,7 @@ import { StaticAuthProvider } from "@twurple/auth"; import { ChatClient } from "@twurple/chat"; import type { TwitchAccountConfig } from "./types.js"; +import { normalizeToken } from "./utils/twitch.js"; /** * Result of probing a Twitch account @@ -35,7 +36,7 @@ export async function probeTwitch( }; } - const rawToken = account.token.trim(); + const rawToken = normalizeToken(account.token.trim()); let client: ChatClient | undefined; diff --git a/extensions/twitch/src/send.ts b/extensions/twitch/src/send.ts index 10128b206..910a64ed8 100644 --- a/extensions/twitch/src/send.ts +++ b/extensions/twitch/src/send.ts @@ -8,6 +8,7 @@ import { DEFAULT_ACCOUNT_ID, getAccountConfig } from "./config.js"; import { getClientManager as getRegistryClientManager } from "./client-manager-registry.js"; import type { ClawdbotConfig } from "clawdbot/plugin-sdk"; +import { resolveTwitchToken } from "./token.js"; import { stripMarkdownForTwitch } from "./utils/markdown.js"; import { generateMessageId, isAccountConfigured, normalizeTwitchChannel } from "./utils/twitch.js"; @@ -66,11 +67,14 @@ export async function sendMessageTwitchInternal( }; } - if (!isAccountConfigured(account)) { + const tokenResolution = resolveTwitchToken(cfg, { accountId }); + if (!isAccountConfigured(account, tokenResolution.token)) { return { ok: false, messageId: generateMessageId(), - error: `Account ${accountId} is not properly configured. Required: username, token, clientId`, + error: + `Account ${accountId} is not properly configured. ` + + "Required: username, clientId, and token (config or env for default account).", }; } diff --git a/extensions/twitch/src/status.ts b/extensions/twitch/src/status.ts index fc1ae0d89..8164c431d 100644 --- a/extensions/twitch/src/status.ts +++ b/extensions/twitch/src/status.ts @@ -6,6 +6,7 @@ import { getAccountConfig } from "./config.js"; import type { ChannelAccountSnapshot, ChannelStatusIssue } from "./types.js"; +import { resolveTwitchToken } from "./token.js"; import { isAccountConfigured } from "./utils/twitch.js"; /** @@ -39,9 +40,10 @@ export function collectTwitchStatusIssues( // Get full account config if available let account: ReturnType | null = null; + let cfg: Parameters[0] | undefined; if (getCfg) { try { - const cfg = getCfg() as { + cfg = getCfg() as { channels?: { twitch?: { accounts?: Record } }; }; account = getAccountConfig(cfg, accountId); @@ -86,7 +88,10 @@ export function collectTwitchStatusIssues( } // Checks that require account config - if (account && isAccountConfigured(account)) { + const tokenResolution = cfg + ? resolveTwitchToken(cfg as Parameters[0], { accountId }) + : { token: "", source: "none" }; + if (account && isAccountConfigured(account, tokenResolution.token)) { // Check 4: Token format warning (normalized, but may indicate config issue) if (account.token?.startsWith("oauth:")) { issues.push({ diff --git a/extensions/twitch/src/utils/twitch.ts b/extensions/twitch/src/utils/twitch.ts index d9d17633e..bbbbe9ada 100644 --- a/extensions/twitch/src/utils/twitch.ts +++ b/extensions/twitch/src/utils/twitch.ts @@ -65,10 +65,14 @@ export function normalizeToken(token: string): string { * @param account - The Twitch account config to check * @returns true if the account has required credentials */ -export function isAccountConfigured(account: { - username?: string; - token?: string; - clientId?: string; -}): boolean { - return Boolean(account?.username && account?.token && account?.clientId); +export function isAccountConfigured( + account: { + username?: string; + token?: string; + clientId?: string; + }, + resolvedToken?: string | null, +): boolean { + const token = resolvedToken ?? account?.token; + return Boolean(account?.username && token && account?.clientId); }