feat(setup): add channel setup flows
Implement setupChannels function with support for Telegram, Discord, Slack, and WhatsApp. Includes WebChat gateway configuration and channel choice loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { EventEmitter } from 'events';
|
||||
import { createPrompter } from './prompts.js';
|
||||
import { ConfigBuilder } from './config.js';
|
||||
import { setupChannels } from './channels.js';
|
||||
|
||||
function mockReadline(inputs: string[]) {
|
||||
let questionIdx = 0;
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
return {
|
||||
async question(query: string) {
|
||||
const answer = inputs[questionIdx++];
|
||||
return answer ?? '';
|
||||
},
|
||||
|
||||
close() {
|
||||
// no-op
|
||||
},
|
||||
|
||||
[Symbol.asyncIterator]() {
|
||||
return this;
|
||||
},
|
||||
|
||||
async next() {
|
||||
return { done: true };
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe('setupChannels', () => {
|
||||
it('configures webchat only (default)', async () => {
|
||||
const rl = mockReadline(['', 'n']);
|
||||
const p = createPrompter(rl);
|
||||
const builder = new ConfigBuilder();
|
||||
await setupChannels(p, builder);
|
||||
const config = builder.build();
|
||||
expect(config.server.port).toBeDefined();
|
||||
expect(config.telegram).toBeUndefined();
|
||||
});
|
||||
|
||||
it('configures telegram channel', async () => {
|
||||
const rl = mockReadline(['', 'y', '1', '123:ABC', '12345, 67890', 'n']);
|
||||
const p = createPrompter(rl);
|
||||
const builder = new ConfigBuilder();
|
||||
await setupChannels(p, builder);
|
||||
const config = builder.build();
|
||||
expect(config.telegram.bot_token).toBe('123:ABC');
|
||||
expect(config.telegram.allowed_chat_ids).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('configures discord channel', async () => {
|
||||
const rl = mockReadline(['', 'y', '2', 'MTIz.token', 'guild1, guild2', 'n']);
|
||||
const p = createPrompter(rl);
|
||||
const builder = new ConfigBuilder();
|
||||
await setupChannels(p, builder);
|
||||
const config = builder.build();
|
||||
expect(config.discord.bot_token).toBe('MTIz.token');
|
||||
expect(config.discord.allowed_guild_ids).toEqual(['guild1', 'guild2']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { Prompter } from './prompts.js';
|
||||
import type { ConfigBuilder } from './config.js';
|
||||
|
||||
async function setupTelegram(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
const botToken = await p.password('Bot token (from @BotFather)');
|
||||
const chatIdsRaw = await p.ask('Allowed chat IDs (comma-separated)');
|
||||
const chatIds = chatIdsRaw.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
||||
if (chatIds.length === 0) {
|
||||
p.println('No valid chat IDs entered. Skipping Telegram.');
|
||||
return;
|
||||
}
|
||||
builder.setTelegram(botToken, chatIds);
|
||||
p.println('✓ Telegram configured');
|
||||
}
|
||||
|
||||
async function setupDiscord(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
const botToken = await p.password('Bot token');
|
||||
const guildIdsRaw = await p.ask('Allowed guild IDs (comma-separated, or * for all)');
|
||||
const guildIds = guildIdsRaw === '*' ? [] : guildIdsRaw.split(',').map(s => s.trim()).filter(Boolean);
|
||||
builder.setDiscord(botToken, guildIds);
|
||||
p.println('✓ Discord configured');
|
||||
}
|
||||
|
||||
async function setupSlack(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
const botToken = await p.password('Bot token (xoxb-...)');
|
||||
const appToken = await p.password('App token (xapp-...)');
|
||||
const signingSecret = await p.password('Signing secret');
|
||||
const channelIdsRaw = await p.ask('Allowed channel IDs (comma-separated, or * for all)');
|
||||
const channelIds = channelIdsRaw === '*' ? [] : channelIdsRaw.split(',').map(s => s.trim()).filter(Boolean);
|
||||
builder.setSlack(botToken, appToken, signingSecret, channelIds);
|
||||
p.println('✓ Slack configured');
|
||||
}
|
||||
|
||||
async function setupWhatsApp(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
p.println('⚠ WhatsApp requires QR code authentication on first connect.');
|
||||
p.println(' It will appear in the terminal when Flynn starts.');
|
||||
const numbersRaw = await p.ask('Allowed phone numbers (comma-separated, or * for all)');
|
||||
const numbers = numbersRaw === '*' ? [] : numbersRaw.split(',').map(s => s.trim()).filter(Boolean);
|
||||
builder.setWhatsApp(numbers);
|
||||
p.println('✓ WhatsApp configured');
|
||||
}
|
||||
|
||||
const CHANNEL_OPTIONS = [
|
||||
{ label: 'Telegram', value: 'telegram' as const },
|
||||
{ label: 'Discord', value: 'discord' as const },
|
||||
{ label: 'More channels...', value: 'more' as const },
|
||||
];
|
||||
|
||||
const MORE_CHANNEL_OPTIONS = [
|
||||
{ label: 'Slack', value: 'slack' as const },
|
||||
{ label: 'WhatsApp', value: 'whatsapp' as const },
|
||||
];
|
||||
|
||||
const CHANNEL_SETUP: Record<string, (p: Prompter, b: ConfigBuilder) => Promise<void>> = {
|
||||
telegram: setupTelegram,
|
||||
discord: setupDiscord,
|
||||
slack: setupSlack,
|
||||
whatsapp: setupWhatsApp,
|
||||
};
|
||||
|
||||
export async function setupChannels(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
p.println();
|
||||
p.println('WebChat is enabled by default via the gateway.');
|
||||
const port = await p.ask('Gateway port', '18800');
|
||||
builder.setGatewayPort(parseInt(port, 10) || 18800);
|
||||
p.println('✓ WebChat enabled — visit http://localhost:' + port + ' after starting');
|
||||
|
||||
let addMore = await p.confirm('Add a messaging channel?', false);
|
||||
|
||||
while (addMore) {
|
||||
p.println();
|
||||
const choice = await p.choose('Channel:', CHANNEL_OPTIONS);
|
||||
|
||||
if (choice === 'more') {
|
||||
const moreChoice = await p.choose('Channel:', MORE_CHANNEL_OPTIONS);
|
||||
const setup = CHANNEL_SETUP[moreChoice];
|
||||
if (setup) await setup(p, builder);
|
||||
} else {
|
||||
const setup = CHANNEL_SETUP[choice];
|
||||
if (setup) await setup(p, builder);
|
||||
}
|
||||
|
||||
p.println();
|
||||
addMore = await p.confirm('Add another channel?', false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user