remove comments + fromat
This commit is contained in:
parent
d8ca472e52
commit
75845606c4
@ -1,7 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(pnpm vitest:*)"
|
||||
"Bash(pnpm vitest:*)",
|
||||
"Bash(git diff:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,11 +30,11 @@ Minimal config:
|
||||
username: "clawdbot",
|
||||
token: "oauth:abc123...",
|
||||
clientId: "your_client_id_here",
|
||||
channel: "vevisk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
channel: "vevisk",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -38,7 +38,6 @@ export function checkTwitchAccessControl(params: {
|
||||
}): TwitchAccessControlResult {
|
||||
const { message, account, botUsername } = params;
|
||||
|
||||
// Check mention requirement first
|
||||
if (account.requireMention) {
|
||||
const mentions = extractMentions(message.message);
|
||||
if (!mentions.includes(botUsername.toLowerCase())) {
|
||||
@ -49,7 +48,6 @@ export function checkTwitchAccessControl(params: {
|
||||
}
|
||||
}
|
||||
|
||||
// Check allowlist (by user ID)
|
||||
if (account.allowFrom && account.allowFrom.length > 0) {
|
||||
const allowFrom = account.allowFrom;
|
||||
const senderId = message.userId;
|
||||
@ -61,7 +59,6 @@ export function checkTwitchAccessControl(params: {
|
||||
};
|
||||
}
|
||||
|
||||
// Check if sender is in allowlist
|
||||
if (!allowFrom.includes(senderId)) {
|
||||
return {
|
||||
allowed: false,
|
||||
@ -69,7 +66,6 @@ export function checkTwitchAccessControl(params: {
|
||||
};
|
||||
}
|
||||
|
||||
// Sender is in allowlist, no need to check role restrictions
|
||||
return {
|
||||
allowed: true,
|
||||
matchKey: senderId,
|
||||
@ -77,7 +73,6 @@ export function checkTwitchAccessControl(params: {
|
||||
};
|
||||
}
|
||||
|
||||
// Check role-based restrictions
|
||||
if (account.allowedRoles && account.allowedRoles.length > 0) {
|
||||
const allowedRoles = account.allowedRoles;
|
||||
|
||||
@ -90,7 +85,6 @@ export function checkTwitchAccessControl(params: {
|
||||
};
|
||||
}
|
||||
|
||||
// Check if sender has any of the allowed roles
|
||||
const hasAllowedRole = checkSenderRoles({
|
||||
message,
|
||||
allowedRoles,
|
||||
@ -110,7 +104,6 @@ export function checkTwitchAccessControl(params: {
|
||||
};
|
||||
}
|
||||
|
||||
// No restrictions configured - allow everyone
|
||||
return {
|
||||
allowed: true,
|
||||
};
|
||||
|
||||
@ -45,7 +45,6 @@ async function processTwitchMessage(params: {
|
||||
}): Promise<void> {
|
||||
const { message, account, accountId, config, runtime, core, statusSink } = params;
|
||||
|
||||
// Resolve route for this message
|
||||
const route = core.channel.routing.resolveAgentRoute({
|
||||
cfg: config as Parameters<typeof core.channel.routing.resolveAgentRoute>[0]["cfg"],
|
||||
channel: "twitch",
|
||||
@ -56,7 +55,6 @@ async function processTwitchMessage(params: {
|
||||
},
|
||||
});
|
||||
|
||||
// Build message body
|
||||
const rawBody = message.message;
|
||||
const body = core.channel.reply.formatAgentEnvelope({
|
||||
channel: "Twitch",
|
||||
@ -68,7 +66,6 @@ async function processTwitchMessage(params: {
|
||||
body: rawBody,
|
||||
});
|
||||
|
||||
// Build context payload
|
||||
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
||||
Body: body,
|
||||
RawBody: rawBody,
|
||||
@ -89,7 +86,6 @@ async function processTwitchMessage(params: {
|
||||
OriginatingTo: `twitch:channel:${message.channel}`,
|
||||
});
|
||||
|
||||
// Record session
|
||||
const storePath = core.channel.session.resolveStorePath(
|
||||
(config as Parameters<typeof core.channel.session.resolveStorePath>[0]["cfg"])?.session?.store,
|
||||
{ agentId: route.agentId },
|
||||
@ -103,14 +99,12 @@ async function processTwitchMessage(params: {
|
||||
},
|
||||
});
|
||||
|
||||
// Resolve markdown table mode
|
||||
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
||||
cfg: config as Parameters<typeof core.channel.text.resolveMarkdownTableMode>[0]["cfg"],
|
||||
channel: "twitch",
|
||||
accountId,
|
||||
});
|
||||
|
||||
// Dispatch reply
|
||||
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg: config as Parameters<
|
||||
@ -191,7 +185,6 @@ export async function monitorTwitchProvider(
|
||||
const core = getTwitchRuntime();
|
||||
let stopped = false;
|
||||
|
||||
// Create logger for client manager
|
||||
const logger = {
|
||||
info: (msg: string) => runtime.log?.(`[twitch] ${msg}`),
|
||||
warn: (msg: string) => runtime.log?.(`[twitch] ${msg}`),
|
||||
@ -199,10 +192,8 @@ export async function monitorTwitchProvider(
|
||||
debug: (msg: string) => runtime.log?.(`[twitch] ${msg}`),
|
||||
};
|
||||
|
||||
// Get or create client manager
|
||||
const clientManager = getOrCreateClientManager(accountId, logger);
|
||||
|
||||
// Establish connection
|
||||
try {
|
||||
await clientManager.getClient(
|
||||
account,
|
||||
@ -216,7 +207,6 @@ export async function monitorTwitchProvider(
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Register message handler
|
||||
const unregisterHandler = clientManager.onMessage(account, async (message) => {
|
||||
if (stopped) return;
|
||||
|
||||
@ -256,13 +246,11 @@ export async function monitorTwitchProvider(
|
||||
}
|
||||
});
|
||||
|
||||
// Stop function
|
||||
const stop = () => {
|
||||
stopped = true;
|
||||
unregisterHandler();
|
||||
};
|
||||
|
||||
// Handle abort signal
|
||||
abortSignal.addEventListener("abort", stop, { once: true });
|
||||
|
||||
return { stop };
|
||||
|
||||
@ -108,12 +108,10 @@ export const twitchOutbound: ChannelOutboundAdapter = {
|
||||
sendText: async (params: ChannelOutboundContext): Promise<OutboundDeliveryResult> => {
|
||||
const { cfg, to, text, accountId, signal } = params;
|
||||
|
||||
// Check for abort signal
|
||||
if (signal?.aborted) {
|
||||
throw new Error("Outbound delivery aborted");
|
||||
}
|
||||
|
||||
// Resolve account
|
||||
const resolvedAccountId = accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const account = getAccountConfig(cfg, resolvedAccountId);
|
||||
if (!account) {
|
||||
@ -124,19 +122,16 @@ export const twitchOutbound: ChannelOutboundAdapter = {
|
||||
);
|
||||
}
|
||||
|
||||
// Get channel (support target parameter override)
|
||||
const channel = to || account.channel;
|
||||
if (!channel) {
|
||||
throw new Error("No channel specified and no default channel in account config");
|
||||
}
|
||||
|
||||
// Get plugin config for markdown stripping
|
||||
const pluginCfg = parsePluginConfig(
|
||||
// biome-ignore lint/suspicious/noExplicitAny: pluginConfig is not part of CoreConfig
|
||||
(cfg as any).pluginConfig ?? {},
|
||||
);
|
||||
|
||||
// Send message
|
||||
const result = await sendMessageTwitchInternal(
|
||||
normalizeTwitchChannel(channel),
|
||||
text,
|
||||
@ -179,15 +174,12 @@ export const twitchOutbound: ChannelOutboundAdapter = {
|
||||
sendMedia: async (params: ChannelOutboundContext): Promise<OutboundDeliveryResult> => {
|
||||
const { text, mediaUrl, signal } = params;
|
||||
|
||||
// Check for abort signal
|
||||
if (signal?.aborted) {
|
||||
throw new Error("Outbound delivery aborted");
|
||||
}
|
||||
|
||||
// Combine text and media URL
|
||||
const message = mediaUrl ? `${text || ""} ${mediaUrl}`.trim() : text;
|
||||
|
||||
// Delegate to sendText
|
||||
if (!twitchOutbound.sendText) {
|
||||
throw new Error("sendText not implemented");
|
||||
}
|
||||
|
||||
@ -41,10 +41,8 @@ export async function probeTwitch(
|
||||
let client: ChatClient | undefined;
|
||||
|
||||
try {
|
||||
// Create auth provider with the token
|
||||
const authProvider = new StaticAuthProvider(account.clientId ?? "", rawToken);
|
||||
|
||||
// Create chat client
|
||||
client = new ChatClient({
|
||||
authProvider,
|
||||
});
|
||||
@ -59,7 +57,6 @@ export async function probeTwitch(
|
||||
const cleanup = () => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
// Remove all listeners
|
||||
connectListener?.unbind();
|
||||
disconnectListener?.unbind();
|
||||
authFailListener?.unbind();
|
||||
@ -84,20 +81,16 @@ export async function probeTwitch(
|
||||
});
|
||||
});
|
||||
|
||||
// Create timeout promise
|
||||
const timeout = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
|
||||
});
|
||||
|
||||
// Set up listeners BEFORE connecting, then race against timeout
|
||||
client.connect();
|
||||
await Promise.race([connectionPromise, timeout]);
|
||||
|
||||
// Clean up connection before returning
|
||||
client.quit();
|
||||
client = undefined;
|
||||
|
||||
// If we got here, connection was successful
|
||||
return {
|
||||
ok: true,
|
||||
connected: true,
|
||||
@ -114,7 +107,6 @@ export async function probeTwitch(
|
||||
elapsedMs: Date.now() - started,
|
||||
};
|
||||
} finally {
|
||||
// Always clean up the client
|
||||
if (client) {
|
||||
try {
|
||||
client.quit();
|
||||
|
||||
@ -51,7 +51,6 @@ export async function resolveTwitchTargets(
|
||||
): Promise<ChannelResolveResult[]> {
|
||||
const log = createLogger(logger);
|
||||
|
||||
// Validate credentials
|
||||
if (!account.clientId || !account.token) {
|
||||
log.error("Missing Twitch client ID or token");
|
||||
return inputs.map((input) => ({
|
||||
@ -61,20 +60,16 @@ export async function resolveTwitchTargets(
|
||||
}));
|
||||
}
|
||||
|
||||
// Normalize token - strip oauth: prefix if present
|
||||
const normalizedToken = normalizeToken(account.token);
|
||||
|
||||
// Create auth provider and API client
|
||||
const authProvider = new StaticAuthProvider(account.clientId, normalizedToken);
|
||||
const apiClient = new ApiClient({ authProvider });
|
||||
|
||||
const results: ChannelResolveResult[] = [];
|
||||
|
||||
// Process each input
|
||||
for (const input of inputs) {
|
||||
const normalized = normalizeUsername(input);
|
||||
|
||||
// Skip empty inputs
|
||||
if (!normalized) {
|
||||
results.push({
|
||||
input,
|
||||
@ -84,12 +79,10 @@ export async function resolveTwitchTargets(
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it looks like a user ID (numeric), validate it exists
|
||||
const looksLikeUserId = /^\d+$/.test(normalized);
|
||||
|
||||
try {
|
||||
if (looksLikeUserId) {
|
||||
// Validate user ID by fetching the user
|
||||
const user = await apiClient.users.getUserById(normalized);
|
||||
|
||||
if (user) {
|
||||
@ -109,7 +102,6 @@ export async function resolveTwitchTargets(
|
||||
log.warn(`User ID ${normalized} not found`);
|
||||
}
|
||||
} else {
|
||||
// Resolve username to user ID
|
||||
const user = await apiClient.users.getUserByName(normalized);
|
||||
|
||||
if (user) {
|
||||
|
||||
@ -56,7 +56,6 @@ export async function sendMessageTwitchInternal(
|
||||
stripMarkdown: boolean = true,
|
||||
logger: Console = console,
|
||||
): Promise<SendMessageResult> {
|
||||
// Resolve account
|
||||
const account = getAccountConfig(cfg, accountId);
|
||||
if (!account) {
|
||||
const availableIds = Object.keys(cfg.channels?.twitch?.accounts ?? {});
|
||||
@ -78,7 +77,6 @@ export async function sendMessageTwitchInternal(
|
||||
};
|
||||
}
|
||||
|
||||
// Normalize channel (parameter override, then account channel)
|
||||
const normalizedChannel = channel || account.channel;
|
||||
if (!normalizedChannel) {
|
||||
return {
|
||||
@ -88,7 +86,6 @@ export async function sendMessageTwitchInternal(
|
||||
};
|
||||
}
|
||||
|
||||
// Strip markdown if enabled
|
||||
const cleanedText = stripMarkdown ? stripMarkdownForTwitch(text) : text;
|
||||
if (!cleanedText) {
|
||||
return {
|
||||
@ -97,7 +94,6 @@ export async function sendMessageTwitchInternal(
|
||||
};
|
||||
}
|
||||
|
||||
// Get client manager from registry
|
||||
const clientManager = getRegistryClientManager(accountId);
|
||||
if (!clientManager) {
|
||||
return {
|
||||
|
||||
@ -35,10 +35,8 @@ export function collectTwitchStatusIssues(
|
||||
for (const entry of accounts) {
|
||||
const accountId = entry.accountId;
|
||||
|
||||
// Skip if not a Twitch account
|
||||
if (!accountId) continue;
|
||||
|
||||
// Get full account config if available
|
||||
let account: ReturnType<typeof getAccountConfig> | null = null;
|
||||
let cfg: Parameters<typeof resolveTwitchToken>[0] | undefined;
|
||||
if (getCfg) {
|
||||
@ -52,7 +50,6 @@ export function collectTwitchStatusIssues(
|
||||
}
|
||||
}
|
||||
|
||||
// Check 1: Account not configured
|
||||
if (!entry.configured) {
|
||||
issues.push({
|
||||
channel: "twitch",
|
||||
@ -61,10 +58,9 @@ export function collectTwitchStatusIssues(
|
||||
message: "Twitch account is not properly configured",
|
||||
fix: "Add required fields: username, token, and clientId to your account configuration",
|
||||
});
|
||||
continue; // Skip further checks if not configured
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check 2: Account disabled
|
||||
if (entry.enabled === false) {
|
||||
issues.push({
|
||||
channel: "twitch",
|
||||
@ -73,10 +69,9 @@ export function collectTwitchStatusIssues(
|
||||
message: "Twitch account is disabled",
|
||||
fix: "Set enabled: true in your account configuration to enable this account",
|
||||
});
|
||||
continue; // Skip further checks if disabled
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check 3: Missing clientId (check if username/token present but no clientId)
|
||||
if (account && account.username && account.token && !account.clientId) {
|
||||
issues.push({
|
||||
channel: "twitch",
|
||||
@ -87,12 +82,10 @@ export function collectTwitchStatusIssues(
|
||||
});
|
||||
}
|
||||
|
||||
// Checks that require account config
|
||||
const tokenResolution = cfg
|
||||
? resolveTwitchToken(cfg as Parameters<typeof resolveTwitchToken>[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({
|
||||
channel: "twitch",
|
||||
@ -103,7 +96,6 @@ export function collectTwitchStatusIssues(
|
||||
});
|
||||
}
|
||||
|
||||
// Check 5: clientSecret provided without refreshToken
|
||||
if (account.clientSecret && !account.refreshToken) {
|
||||
issues.push({
|
||||
channel: "twitch",
|
||||
@ -114,7 +106,6 @@ export function collectTwitchStatusIssues(
|
||||
});
|
||||
}
|
||||
|
||||
// Check 6: Access control warnings
|
||||
if (account.allowFrom && account.allowFrom.length === 0) {
|
||||
issues.push({
|
||||
channel: "twitch",
|
||||
@ -125,7 +116,6 @@ export function collectTwitchStatusIssues(
|
||||
});
|
||||
}
|
||||
|
||||
// Check 7: Invalid role combinations
|
||||
if (
|
||||
account.allowedRoles?.includes("all") &&
|
||||
account.allowFrom &&
|
||||
@ -141,7 +131,6 @@ export function collectTwitchStatusIssues(
|
||||
}
|
||||
}
|
||||
|
||||
// Check 8: Runtime errors
|
||||
if (entry.lastError) {
|
||||
issues.push({
|
||||
channel: "twitch",
|
||||
@ -152,7 +141,6 @@ export function collectTwitchStatusIssues(
|
||||
});
|
||||
}
|
||||
|
||||
// Check 9: Account never connected successfully
|
||||
if (
|
||||
entry.configured &&
|
||||
!entry.running &&
|
||||
@ -169,7 +157,6 @@ export function collectTwitchStatusIssues(
|
||||
});
|
||||
}
|
||||
|
||||
// Check 10: Long-running connection may need reconnection
|
||||
if (entry.running && entry.lastStartAt) {
|
||||
const uptime = Date.now() - entry.lastStartAt;
|
||||
const daysSinceStart = uptime / (1000 * 60 * 60 * 24);
|
||||
|
||||
@ -21,7 +21,8 @@ const mockQuit = vi.fn();
|
||||
const mockUnbind = vi.fn();
|
||||
|
||||
// Event handler storage for testing
|
||||
const messageHandlers: Array<(channel: string, user: string, message: string, msg: any) => void> = [];
|
||||
const messageHandlers: Array<(channel: string, user: string, message: string, msg: any) => void> =
|
||||
[];
|
||||
const whisperHandlers: Array<(user: string, message: string, msg: any) => void> = [];
|
||||
|
||||
// Mock functions that track handlers and return unbind objects
|
||||
|
||||
@ -26,14 +26,11 @@ export class TwitchClientManager {
|
||||
}
|
||||
|
||||
if (account.clientSecret) {
|
||||
// Use RefreshingAuthProvider - can handle tokens with or without refresh tokens
|
||||
const authProvider = new RefreshingAuthProvider({
|
||||
clientId: account.clientId,
|
||||
clientSecret: account.clientSecret,
|
||||
});
|
||||
|
||||
// Use addUserForToken to figure out the user ID from the token
|
||||
// This works whether we have a refresh token or not
|
||||
await authProvider
|
||||
.addUserForToken({
|
||||
accessToken: normalizedToken,
|
||||
@ -52,14 +49,12 @@ export class TwitchClientManager {
|
||||
);
|
||||
});
|
||||
|
||||
// Set up token refresh event listener (only fires if refreshToken is provided)
|
||||
authProvider.onRefresh((userId, token) => {
|
||||
this.logger.info(
|
||||
`[twitch] Access token refreshed for user ${userId} (expires in ${token.expiresIn ? `${token.expiresIn}s` : "unknown"})`,
|
||||
);
|
||||
});
|
||||
|
||||
// Set up token refresh failure listener
|
||||
authProvider.onRefreshFailure((userId, error) => {
|
||||
this.logger.error(
|
||||
`[twitch] Failed to refresh access token for user ${userId}: ${error.message}`,
|
||||
@ -76,7 +71,6 @@ export class TwitchClientManager {
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
// Fall back to StaticAuthProvider for backward compatibility (no clientSecret)
|
||||
this.logger.info(
|
||||
`[twitch] Using StaticAuthProvider for ${account.username} (no clientSecret provided)`,
|
||||
);
|
||||
@ -98,7 +92,6 @@ export class TwitchClientManager {
|
||||
return existing;
|
||||
}
|
||||
|
||||
// Resolve token from config or environment
|
||||
const tokenResolution = resolveTwitchToken(cfg, {
|
||||
accountId,
|
||||
});
|
||||
@ -119,13 +112,10 @@ export class TwitchClientManager {
|
||||
throw new Error("Missing Twitch client ID");
|
||||
}
|
||||
|
||||
// Normalize token - strip oauth: prefix if present (Twurple doesn't need it)
|
||||
const normalizedToken = normalizeToken(tokenResolution.token);
|
||||
|
||||
// Create auth provider
|
||||
const authProvider = await this.createAuthProvider(account, normalizedToken);
|
||||
|
||||
// Create chat client
|
||||
const client = new ChatClient({
|
||||
authProvider,
|
||||
channels: [account.channel],
|
||||
@ -160,10 +150,8 @@ export class TwitchClientManager {
|
||||
},
|
||||
});
|
||||
|
||||
// Set up event handlers
|
||||
this.setupClientHandlers(client, account);
|
||||
|
||||
// Connect
|
||||
client.connect();
|
||||
|
||||
this.clients.set(key, client);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user