mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 20:38:27 +00:00
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:
136
extensions/twitch/src/send.ts
Normal file
136
extensions/twitch/src/send.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Twitch message sending functions with dependency injection support.
|
||||
*
|
||||
* These functions are the primary interface for sending messages to Twitch.
|
||||
* They support dependency injection via the `deps` parameter for testability.
|
||||
*/
|
||||
|
||||
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";
|
||||
|
||||
/**
|
||||
* Result from sending a message to Twitch.
|
||||
*/
|
||||
export interface SendMessageResult {
|
||||
/** Whether the send was successful */
|
||||
ok: boolean;
|
||||
/** The message ID (generated for tracking) */
|
||||
messageId: string;
|
||||
/** Error message if the send failed */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal send function used by the outbound adapter.
|
||||
*
|
||||
* This function has access to the full Clawdbot config and handles
|
||||
* account resolution, markdown stripping, and actual message sending.
|
||||
*
|
||||
* @param channel - The channel name
|
||||
* @param text - The message text
|
||||
* @param cfg - Full Clawdbot configuration
|
||||
* @param accountId - Account ID to use
|
||||
* @param stripMarkdown - Whether to strip markdown (default: true)
|
||||
* @param logger - Logger instance
|
||||
* @returns Result with message ID and status
|
||||
*
|
||||
* @example
|
||||
* const result = await sendMessageTwitchInternal(
|
||||
* "#mychannel",
|
||||
* "Hello Twitch!",
|
||||
* clawdbotConfig,
|
||||
* "default",
|
||||
* true,
|
||||
* console,
|
||||
* );
|
||||
*/
|
||||
export async function sendMessageTwitchInternal(
|
||||
channel: string,
|
||||
text: string,
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string = DEFAULT_ACCOUNT_ID,
|
||||
stripMarkdown: boolean = true,
|
||||
logger: Console = console,
|
||||
): Promise<SendMessageResult> {
|
||||
const account = getAccountConfig(cfg, accountId);
|
||||
if (!account) {
|
||||
const availableIds = Object.keys(cfg.channels?.twitch?.accounts ?? {});
|
||||
return {
|
||||
ok: false,
|
||||
messageId: generateMessageId(),
|
||||
error: `Account not found: ${accountId}. Available accounts: ${availableIds.join(", ") || "none"}`,
|
||||
};
|
||||
}
|
||||
|
||||
const tokenResolution = resolveTwitchToken(cfg, { accountId });
|
||||
if (!isAccountConfigured(account, tokenResolution.token)) {
|
||||
return {
|
||||
ok: false,
|
||||
messageId: generateMessageId(),
|
||||
error:
|
||||
`Account ${accountId} is not properly configured. ` +
|
||||
"Required: username, clientId, and token (config or env for default account).",
|
||||
};
|
||||
}
|
||||
|
||||
const normalizedChannel = channel || account.channel;
|
||||
if (!normalizedChannel) {
|
||||
return {
|
||||
ok: false,
|
||||
messageId: generateMessageId(),
|
||||
error: "No channel specified and no default channel in account config",
|
||||
};
|
||||
}
|
||||
|
||||
const cleanedText = stripMarkdown ? stripMarkdownForTwitch(text) : text;
|
||||
if (!cleanedText) {
|
||||
return {
|
||||
ok: true,
|
||||
messageId: "skipped",
|
||||
};
|
||||
}
|
||||
|
||||
const clientManager = getRegistryClientManager(accountId);
|
||||
if (!clientManager) {
|
||||
return {
|
||||
ok: false,
|
||||
messageId: generateMessageId(),
|
||||
error: `Client manager not found for account: ${accountId}. Please start the Twitch gateway first.`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await clientManager.sendMessage(
|
||||
account,
|
||||
normalizeTwitchChannel(normalizedChannel),
|
||||
cleanedText,
|
||||
cfg,
|
||||
accountId,
|
||||
);
|
||||
|
||||
if (!result.ok) {
|
||||
return {
|
||||
ok: false,
|
||||
messageId: result.messageId ?? generateMessageId(),
|
||||
error: result.error ?? "Send failed",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
messageId: result.messageId ?? generateMessageId(),
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
logger.error(`Failed to send message: ${errorMsg}`);
|
||||
return {
|
||||
ok: false,
|
||||
messageId: generateMessageId(),
|
||||
error: errorMsg,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user