Files
flynn/src/daemon/channels.ts
T
2026-02-17 10:54:43 -08:00

324 lines
12 KiB
TypeScript

import type { Config } from '../config/index.js';
import type { HookEngine } from '../hooks/index.js';
import { ChannelRegistry, TelegramAdapter, WebChatAdapter, DiscordAdapter, SlackAdapter, WhatsAppAdapter, MatrixAdapter, SignalAdapter, MattermostAdapter, TeamsAdapter, GoogleChatAdapter, BlueBubblesAdapter, LineAdapter, FeishuAdapter, ZaloAdapter, PairingManager } from '../channels/index.js';
import { CronScheduler, WebhookHandler, GmailWatcher, buildPresetCronJobs } from '../automation/index.js';
import type { GatewayServer } from '../gateway/index.js';
export interface ChannelsDeps {
config: Config;
channelRegistry: ChannelRegistry;
hookEngine: HookEngine;
pairingManager?: PairingManager;
gateway: GatewayServer;
}
export interface ChannelsResult {
cronScheduler?: CronScheduler;
webhookHandler?: WebhookHandler;
gmailWatcher?: GmailWatcher;
}
function resolveChannelMinioShare(config: Config): {
enabled: boolean;
endpoint: string;
accessKey: string;
secretKey: string;
bucket: string;
prefix: string;
secure: boolean;
expires?: string;
mcPath?: string;
} | undefined {
const minio = config.backup.minio;
if (!minio.enabled || !minio.endpoint || !minio.access_key || !minio.secret_key || !minio.bucket) {
return undefined;
}
return {
enabled: true,
endpoint: minio.endpoint,
accessKey: minio.access_key,
secretKey: minio.secret_key,
bucket: minio.bucket,
prefix: minio.prefix,
secure: minio.secure,
};
}
function resolveChannelMinioShareWithOverrides(
base: ReturnType<typeof resolveChannelMinioShare>,
override: {
enabled: boolean;
endpoint?: string;
access_key?: string;
secret_key?: string;
bucket?: string;
prefix: string;
secure: boolean;
expires: string;
mc_path?: string;
} | undefined,
): {
enabled: boolean;
endpoint: string;
accessKey: string;
secretKey: string;
bucket: string;
prefix: string;
secure: boolean;
expires?: string;
mcPath?: string;
} | undefined {
if (!override?.enabled) {
return base;
}
const endpoint = override.endpoint ?? base?.endpoint;
const accessKey = override.access_key ?? base?.accessKey;
const secretKey = override.secret_key ?? base?.secretKey;
const bucket = override.bucket ?? base?.bucket;
if (!endpoint || !accessKey || !secretKey || !bucket) {
return undefined;
}
return {
enabled: true,
endpoint,
accessKey,
secretKey,
bucket,
prefix: override.prefix,
secure: override.secure,
expires: override.expires,
mcPath: override.mc_path,
};
}
export function registerChannels(deps: ChannelsDeps): ChannelsResult {
const { config, channelRegistry, hookEngine, pairingManager, gateway } = deps;
const channelMinioShare = resolveChannelMinioShare(config);
// Register Telegram adapter (if configured)
if (config.telegram) {
const telegramAdapter = new TelegramAdapter({
botToken: config.telegram.bot_token,
allowedChatIds: config.telegram.allowed_chat_ids,
requireMention: config.telegram.require_mention,
hookEngine,
pairingManager,
});
channelRegistry.register(telegramAdapter);
}
// Register Discord adapter (if configured)
if (config.discord) {
const discordAdapter = new DiscordAdapter({
botToken: config.discord.bot_token,
allowedGuildIds: config.discord.allowed_guild_ids.length > 0 ? config.discord.allowed_guild_ids : undefined,
allowedChannelIds: config.discord.allowed_channel_ids.length > 0 ? config.discord.allowed_channel_ids : undefined,
requireMention: config.discord.require_mention,
pairingManager,
});
channelRegistry.register(discordAdapter);
}
// Register Slack adapter (if configured)
if (config.slack) {
const slackAdapter = new SlackAdapter({
botToken: config.slack.bot_token,
appToken: config.slack.app_token,
signingSecret: config.slack.signing_secret,
allowedChannelIds: config.slack.allowed_channel_ids.length > 0 ? config.slack.allowed_channel_ids : undefined,
requireMention: config.slack.require_mention,
pairingManager,
});
channelRegistry.register(slackAdapter);
}
// Register WhatsApp adapter (if configured)
if (config.whatsapp) {
const whatsappAdapter = new WhatsAppAdapter({
allowedNumbers: config.whatsapp.allowed_numbers.length > 0 ? config.whatsapp.allowed_numbers : undefined,
allowedGroupIds: config.whatsapp.allowed_group_ids.length > 0 ? config.whatsapp.allowed_group_ids : undefined,
requireMention: config.whatsapp.require_mention,
dataDir: config.whatsapp.data_dir,
pairingManager,
allowNoSandbox: config.whatsapp.no_sandbox,
});
channelRegistry.register(whatsappAdapter);
}
// Register Matrix adapter (if configured)
if (config.matrix) {
const matrixAdapter = new MatrixAdapter({
homeserverUrl: config.matrix.homeserver_url,
accessToken: config.matrix.access_token,
allowedRoomIds: config.matrix.allowed_room_ids.length > 0 ? config.matrix.allowed_room_ids : undefined,
requireMention: config.matrix.require_mention,
syncTimeoutMs: config.matrix.sync_timeout_ms,
displayName: config.matrix.display_name,
pairingManager,
});
channelRegistry.register(matrixAdapter);
}
// Register Signal adapter (if configured)
if (config.signal) {
const signalAdapter = new SignalAdapter({
account: config.signal.account,
signalCliPath: config.signal.signal_cli_path,
allowedNumbers: config.signal.allowed_numbers.length > 0 ? config.signal.allowed_numbers : undefined,
allowedGroupIds: config.signal.allowed_group_ids.length > 0 ? config.signal.allowed_group_ids : undefined,
requireMention: config.signal.require_mention,
mentionName: config.signal.mention_name,
pollIntervalMs: config.signal.poll_interval_ms,
sendTimeoutMs: config.signal.send_timeout_ms,
pairingManager,
});
channelRegistry.register(signalAdapter);
}
// Register Mattermost adapter (if configured)
if (config.mattermost) {
const mattermostAdapter = new MattermostAdapter({
serverUrl: config.mattermost.server_url,
botToken: config.mattermost.bot_token,
allowedChannelIds: config.mattermost.allowed_channel_ids.length > 0 ? config.mattermost.allowed_channel_ids : undefined,
requireMention: config.mattermost.require_mention,
mentionName: config.mattermost.mention_name,
pollIntervalMs: config.mattermost.poll_interval_ms,
pairingManager,
});
channelRegistry.register(mattermostAdapter);
}
// Register Microsoft Teams adapter (if configured)
if (config.teams) {
const teamsAdapter = new TeamsAdapter({
appId: config.teams.app_id,
appPassword: config.teams.app_password,
allowedConversationIds: config.teams.allowed_conversation_ids.length > 0 ? config.teams.allowed_conversation_ids : undefined,
requireMention: config.teams.require_mention,
});
channelRegistry.register(teamsAdapter);
gateway.setTeamsHandler(teamsAdapter);
}
// Register Google Chat adapter (if configured)
if (config.google_chat) {
const googleChatAdapter = new GoogleChatAdapter({
serviceAccountKeyFile: config.google_chat.service_account_key_file,
serviceAccountJson: config.google_chat.service_account_json,
webhookToken: config.google_chat.webhook_token,
allowedSpaceNames: config.google_chat.allowed_space_names.length > 0 ? config.google_chat.allowed_space_names : undefined,
requireMention: config.google_chat.require_mention,
});
channelRegistry.register(googleChatAdapter);
gateway.setGoogleChatHandler(googleChatAdapter);
}
// Register BlueBubbles adapter (if configured)
if (config.bluebubbles) {
const blueBubblesAdapter = new BlueBubblesAdapter({
endpoint: config.bluebubbles.endpoint,
apiKey: config.bluebubbles.api_key,
webhookToken: config.bluebubbles.webhook_token,
allowedChatGuids: config.bluebubbles.allowed_chat_guids.length > 0 ? config.bluebubbles.allowed_chat_guids : undefined,
requireMention: config.bluebubbles.require_mention,
mentionName: config.bluebubbles.mention_name,
});
channelRegistry.register(blueBubblesAdapter);
gateway.setBlueBubblesHandler(blueBubblesAdapter);
}
// Register LINE adapter (if configured)
if (config.line) {
const lineMinioShare = resolveChannelMinioShareWithOverrides(channelMinioShare, config.line.minio);
const effectiveLineMinioShare = lineMinioShare && !config.line.minio.enabled
? { ...lineMinioShare, prefix: `${lineMinioShare.prefix.replace(/\/+$/, '')}/channels/line` }
: lineMinioShare;
const lineAdapter = new LineAdapter({
channelAccessToken: config.line.channel_access_token,
channelSecret: config.line.channel_secret,
allowedSourceIds: config.line.allowed_source_ids.length > 0 ? config.line.allowed_source_ids : undefined,
requireMention: config.line.require_mention,
mentionName: config.line.mention_name,
minio: effectiveLineMinioShare,
});
channelRegistry.register(lineAdapter);
gateway.setLineHandler(lineAdapter);
}
// Register Feishu adapter (if configured)
if (config.feishu) {
const feishuAdapter = new FeishuAdapter({
appId: config.feishu.app_id,
appSecret: config.feishu.app_secret,
webhookToken: config.feishu.webhook_token,
allowedChatIds: config.feishu.allowed_chat_ids.length > 0 ? config.feishu.allowed_chat_ids : undefined,
requireMention: config.feishu.require_mention,
mentionName: config.feishu.mention_name,
endpoint: config.feishu.endpoint,
});
channelRegistry.register(feishuAdapter);
gateway.setFeishuHandler(feishuAdapter);
}
// Register Zalo adapter (if configured)
if (config.zalo) {
const zaloMinioShare = resolveChannelMinioShareWithOverrides(channelMinioShare, config.zalo.minio);
const effectiveZaloMinioShare = zaloMinioShare && !config.zalo.minio.enabled
? { ...zaloMinioShare, prefix: `${zaloMinioShare.prefix.replace(/\/+$/, '')}/channels/zalo` }
: zaloMinioShare;
const zaloAdapter = new ZaloAdapter({
oaAccessToken: config.zalo.oa_access_token,
endpoint: config.zalo.endpoint,
webhookToken: config.zalo.webhook_token,
allowedUserIds: config.zalo.allowed_user_ids.length > 0 ? config.zalo.allowed_user_ids : undefined,
requireMention: config.zalo.require_mention,
mentionName: config.zalo.mention_name,
minio: effectiveZaloMinioShare,
});
channelRegistry.register(zaloAdapter);
gateway.setZaloHandler(zaloAdapter);
}
// Register WebChat adapter (wraps the gateway)
const webChatAdapter = new WebChatAdapter({ gateway });
channelRegistry.register(webChatAdapter);
// Register cron scheduler adapter (if any cron jobs configured)
let cronScheduler: CronScheduler | undefined;
const presetCronJobs = buildPresetCronJobs(config);
const cronJobs = [...config.automation.cron, ...presetCronJobs];
if (cronJobs.length > 0) {
cronScheduler = new CronScheduler(cronJobs, channelRegistry, config.automation.delivery_mode);
channelRegistry.register(cronScheduler);
console.log(`Registered ${cronJobs.length} cron job(s)`);
}
// Register webhook handler adapter (if any webhooks configured)
let webhookHandler: WebhookHandler | undefined;
if (config.automation.webhooks.length > 0) {
webhookHandler = new WebhookHandler(
config.automation.webhooks,
channelRegistry,
config.automation.delivery_mode,
config.server.max_request_body_bytes,
);
channelRegistry.register(webhookHandler);
gateway.setWebhookHandler(webhookHandler);
console.log(`Registered ${config.automation.webhooks.length} webhook(s)`);
}
// Register Gmail watcher adapter (if configured and enabled)
let gmailWatcher: GmailWatcher | undefined;
if (config.automation.gmail?.enabled) {
gmailWatcher = new GmailWatcher(config.automation.gmail, channelRegistry);
channelRegistry.register(gmailWatcher);
gateway.setGmailHandler(gmailWatcher);
console.log('Registered Gmail watcher');
}
return { cronScheduler, webhookHandler, gmailWatcher };
}