diff --git a/package.json b/package.json index d79deed8..2dd62037 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,6 @@ "db:migrate": "pnpm --filter @memoh/db migrate", "db:generate": "pnpm --filter @memoh/db generate", "db:studio": "pnpm --filter @memoh/db studio", - "telegram:start": "pnpm --filter @memoh/platform-telegram start", - "telegram:dev": "pnpm --filter @memoh/platform-telegram dev", "cli": "pnpm --filter @memoh/client start", "docs:dev": "pnpm --filter @memoh/docs dev", "docs:build": "pnpm --filter @memoh/docs build", diff --git a/packages/api/package.json b/packages/api/package.json index 1f1c2e90..5ed6bf6c 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -21,6 +21,8 @@ "@memoh/db": "workspace:*", "@memoh/memory": "workspace:*", "@memoh/shared": "workspace:*", + "@memoh/platform": "workspace:*", + "@memoh/platform-telegram": "workspace:*", "drizzle-orm": "^0.45.1", "elysia": "latest", "node-cron": "^4.2.1", diff --git a/packages/api/src/modules/platform/index.ts b/packages/api/src/modules/platform/index.ts index 39f177cd..dbd1a044 100644 --- a/packages/api/src/modules/platform/index.ts +++ b/packages/api/src/modules/platform/index.ts @@ -7,6 +7,7 @@ import { DeletePlatformModel, UpdatePlatformConfigModel, SetPlatformActiveModel, + getPlatformConfigSchema, } from './model' import { getPlatforms, @@ -34,7 +35,6 @@ export const platformModule = new Elysia({ await activePlatform({ id: platform.id, name: platform.name, - endpoint: platform.endpoint, config: platform.config as Record, active: platform.active, }) @@ -140,13 +140,21 @@ export const platformModule = new Elysia({ try { const { id } = params const { config } = body as { config: Record } - const updatedPlatform = await updatePlatformConfig(id, config) - if (!updatedPlatform) { + + // Get the platform to validate config against its schema + const platform = await getPlatformById(id) + if (!platform) { return { success: false, error: 'Platform not found', } } + + // Validate config against platform-specific schema + const configSchema = getPlatformConfigSchema(platform.name) + const validatedConfig = configSchema.parse(config) as Record + + const updatedPlatform = await updatePlatformConfig(id, validatedConfig) return { success: true, data: updatedPlatform, diff --git a/packages/api/src/modules/platform/model.ts b/packages/api/src/modules/platform/model.ts index 85b21cc0..7ea52209 100644 --- a/packages/api/src/modules/platform/model.ts +++ b/packages/api/src/modules/platform/model.ts @@ -1,10 +1,59 @@ import { z } from 'zod' +// Platform-specific config schemas +export const TelegramConfigSchema = z.object({ + botToken: z.string().min(1, 'Bot token is required'), +}) + +// Registry of platform config schemas +// When adding a new platform, add its config schema here +export const platformConfigSchemas: Record = { + telegram: TelegramConfigSchema, + // Add more platforms here as they are implemented + // discord: DiscordConfigSchema, + // slack: SlackConfigSchema, +} + +// Helper function to get config schema for a platform +export const getPlatformConfigSchema = (platformName: string): z.ZodSchema => { + const schema = platformConfigSchemas[platformName] + if (!schema) { + throw new Error(`Unknown platform: ${platformName}. Supported platforms: ${Object.keys(platformConfigSchemas).join(', ')}`) + } + return schema +} + +// Base platform schema with dynamic config validation const PlatformSchema = z.object({ name: z.string().min(1, 'Platform name is required'), - endpoint: z.string().min(1, 'Endpoint is required'), config: z.record(z.string(), z.unknown()), active: z.boolean().optional().default(true), +}).superRefine((data, ctx) => { + // Validate that the platform name is supported + if (!platformConfigSchemas[data.name]) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Unknown platform: ${data.name}. Supported platforms: ${Object.keys(platformConfigSchemas).join(', ')}`, + path: ['name'], + }) + return + } + + // Validate the config against the platform-specific schema + try { + const configSchema = getPlatformConfigSchema(data.name) + configSchema.parse(data.config) + } catch (error) { + if (error instanceof z.ZodError) { + error.issues.forEach((issue: z.ZodIssue) => { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: issue.message, + path: ['config', ...issue.path], + }) + }) + } + } }) export type PlatformInput = z.infer @@ -32,6 +81,8 @@ export const DeletePlatformModel = { }), } +// For updating config, we need to know the platform name to validate +// This will be used with additional validation in the route handler export const UpdatePlatformConfigModel = { params: z.object({ id: z.string(), diff --git a/packages/api/src/modules/platform/service.ts b/packages/api/src/modules/platform/service.ts index c5f452ec..eafbac6e 100644 --- a/packages/api/src/modules/platform/service.ts +++ b/packages/api/src/modules/platform/service.ts @@ -3,15 +3,12 @@ import { platform } from '@memoh/db/schema' import { Platform } from '@memoh/shared' import { eq, sql, desc, asc } from 'drizzle-orm' import { calculateOffset, createPaginatedResult, type PaginatedResult } from '../../utils/pagination' -import path from 'node:path' +import { BasePlatform } from '@memoh/platform' +import { TelegramPlatform } from '@memoh/platform-telegram' -/** - * 平台列表返回类型 - */ type PlatformListItem = { id: string name: string - endpoint: string config: Record active: boolean createdAt: Date @@ -72,7 +69,6 @@ export const createPlatform = async (data: Omit) => { .insert(platform) .values({ name: data.name, - endpoint: data.endpoint, config: data.config, active: data.active ?? true, }) @@ -81,7 +77,6 @@ export const createPlatform = async (data: Omit) => { await activePlatform({ id: newPlatform.id, name: newPlatform.name, - endpoint: newPlatform.endpoint, config: newPlatform.config as Record, active: newPlatform.active, }) @@ -92,7 +87,6 @@ export const createPlatform = async (data: Omit) => { export const updatePlatform = async (id: string, data: Partial>) => { const updateData: { name?: string - endpoint?: string config?: Record active?: boolean updatedAt: Date @@ -101,7 +95,6 @@ export const updatePlatform = async (id: string, data: Partial = { + telegram: TelegramPlatform, +} + +export const platforms = new Map() + export const activePlatform = async (platform: Platform) => { - await fetch(path.join(platform.endpoint, '/start'), { - method: 'POST', - body: JSON.stringify(platform.config), - headers: { - 'Content-Type': 'application/json', - }, - }) + const Constructor = platformConstructors[platform.name] + if (!Constructor) { + throw new Error('Platform constructor not found') + } + const platformInstance = new Constructor() + await platformInstance.start(platform.config) + platforms.set(platform.name, platformInstance) } export const inactivePlatform = async (platform: Platform) => { - await fetch(path.join(platform.endpoint, '/stop'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - }) + const platformInstance = platforms.get(platform.name) + if (!platformInstance) { + throw new Error('Platform not found') + } + await platformInstance.stop() + platforms.delete(platform.name) } export const setActivePlatform = async (id: string, active: boolean) => { @@ -162,7 +161,6 @@ export const setActivePlatform = async (id: string, active: boolean) => { const platformData: Platform = { id: currentPlatform.id, name: currentPlatform.name, - endpoint: currentPlatform.endpoint, config: currentPlatform.config as Record, active: active, } @@ -187,11 +185,9 @@ export const sendMessageToPlatform = async (name: string, options: { if (!currentPlatform) { throw new Error('Platform not found') } - await fetch(path.join(currentPlatform.endpoint, '/send'), { - method: 'POST', - body: JSON.stringify(options), - headers: { - 'Content-Type': 'application/json', - }, - }) + const platformInstance = platforms.get(currentPlatform.name) + if (!platformInstance) { + throw new Error('Platform not found') + } + await platformInstance.send(options) } diff --git a/packages/cli/src/cli/commands/platform.ts b/packages/cli/src/cli/commands/platform.ts index e37233b0..abd5c5c8 100644 --- a/packages/cli/src/cli/commands/platform.ts +++ b/packages/cli/src/cli/commands/platform.ts @@ -6,6 +6,7 @@ import { table } from 'table' import * as platformCore from '../../core/platform' import { formatError } from '../../utils' import { getApiUrl } from '../../core/client' +import { PLATFORM_DEFINITIONS, type PlatformDefinition } from '../../types' export function platformCommands(program: Command) { program @@ -23,11 +24,10 @@ export function platformCommands(program: Command) { } const tableData = [ - ['ID', 'Name', 'Endpoint', 'Active', 'Created'], + ['ID', 'Name', 'Active', 'Created'], ...platforms.map((item) => [ item.id.substring(0, 8) + '...', item.name, - item.endpoint, item.active ? chalk.green('✓ Active') : chalk.dim('✗ Inactive'), new Date(item.createdAt).toLocaleDateString(), ]), @@ -55,83 +55,73 @@ export function platformCommands(program: Command) { program .command('create') .description('Create platform configuration') - .option('-n, --name ', 'Platform name') - .option('-e, --endpoint ', 'Platform endpoint URL') - .option('-c, --config ', 'Platform config (JSON string)') - .option('-a, --active', 'Set platform as active', true) - .action(async (options) => { + .action(async () => { try { - let { name, endpoint, config, active } = options + // Step 1: Select platform type + const { platformType } = await inquirer.prompt([ + { + type: 'list', + name: 'platformType', + message: 'Select platform type:', + choices: PLATFORM_DEFINITIONS.map((def) => ({ + name: `${def.displayName} - ${def.description}`, + value: def.name, + })), + }, + ]) - if (!name || !endpoint) { - const answers = await inquirer.prompt([ + const platformDef = PLATFORM_DEFINITIONS.find((def) => def.name === platformType) + if (!platformDef) { + console.error(chalk.red('Invalid platform type')) + process.exit(1) + } + + // Step 2: Collect platform-specific config + console.log(chalk.cyan(`\nConfiguring ${platformDef.displayName}...\n`)) + + const configAnswers: Record = {} + for (const field of platformDef.configFields) { + const answer = await inquirer.prompt([ { - type: 'input', - name: 'name', - message: 'Platform name:', - when: !name, - validate: (value: string) => { - if (value.trim()) return true - return 'Platform name is required' - }, - }, - { - type: 'input', - name: 'endpoint', - message: 'Platform endpoint URL:', - when: !endpoint, - validate: (value: string) => { - if (value.trim()) return true - return 'Endpoint is required' - }, - }, - { - type: 'input', - name: 'config', - message: 'Platform config (JSON string):', - default: '{}', - when: !config, - }, - { - type: 'confirm', - name: 'active', - message: 'Set as active?', - default: true, - when: active === undefined, + type: field.type || 'input', + name: field.name, + message: field.message, + default: field.default, + validate: field.validate || ((value: string) => { + if (field.required && !value?.toString().trim()) { + return `${field.name} is required` + } + return true + }), }, ]) - - name = name || answers.name - endpoint = endpoint || answers.endpoint - config = config || answers.config - active = active ?? answers.active + configAnswers[field.name] = answer[field.name] } - // Parse config JSON - let configObj: Record = {} - if (config) { - try { - configObj = JSON.parse(config) - } catch { - console.error(chalk.red('Invalid JSON config')) - process.exit(1) - } - } + // Step 3: Confirm active status + const { active } = await inquirer.prompt([ + { + type: 'confirm', + name: 'active', + message: 'Set as active?', + default: true, + }, + ]) const spinner = ora('Creating platform configuration...').start() const platform = await platformCore.createPlatform({ - name, - endpoint, - config: configObj, + name: platformType, + config: configAnswers, active, }) spinner.succeed(chalk.green('Platform configuration created successfully')) - console.log(chalk.blue(`Name: ${platform.name}`)) - console.log(chalk.blue(`Endpoint: ${platform.endpoint}`)) + console.log(chalk.blue(`\nPlatform: ${platformDef.displayName}`)) + console.log(chalk.blue(`Type: ${platform.name}`)) console.log(chalk.blue(`Active: ${platform.active ? 'Yes' : 'No'}`)) console.log(chalk.blue(`ID: ${platform.id}`)) + console.log(chalk.dim(`\nConfig: ${JSON.stringify(platform.config, null, 2)}`)) } catch (error) { console.error(chalk.red(formatError(error))) process.exit(1) @@ -145,10 +135,11 @@ export function platformCommands(program: Command) { const spinner = ora('Fetching platform configuration...').start() try { const platform = await platformCore.getPlatform(id) + const platformDef = PLATFORM_DEFINITIONS.find((def) => def.name === platform.name) spinner.succeed(chalk.green('Platform Configuration')) console.log(chalk.blue(`ID: ${platform.id}`)) - console.log(chalk.blue(`Name: ${platform.name}`)) - console.log(chalk.blue(`Endpoint: ${platform.endpoint}`)) + console.log(chalk.blue(`Platform: ${platformDef?.displayName || platform.name}`)) + console.log(chalk.blue(`Type: ${platform.name}`)) console.log(chalk.blue(`Active: ${platform.active ? 'Yes' : 'No'}`)) console.log(chalk.blue(`Config: ${JSON.stringify(platform.config, null, 2)}`)) console.log(chalk.blue(`Created At: ${new Date(platform.createdAt).toLocaleString('en-US')}`)) @@ -163,8 +154,7 @@ export function platformCommands(program: Command) { program .command('update ') .description('Update platform configuration') - .option('-n, --name ', 'Platform name') - .option('-e, --endpoint ', 'Platform endpoint URL') + .option('-n, --name ', 'Platform type (e.g., telegram)') .option('-c, --config ', 'Platform config (JSON string)') .option('-a, --active ', 'Set active status (true/false)') .action(async (id, options) => { @@ -172,7 +162,6 @@ export function platformCommands(program: Command) { const updates: Record = {} if (options.name) updates.name = options.name - if (options.endpoint) updates.endpoint = options.endpoint if (options.config) { try { updates.config = JSON.parse(options.config) @@ -193,8 +182,9 @@ export function platformCommands(program: Command) { const spinner = ora('Updating platform configuration...').start() const platform = await platformCore.updatePlatform(id, updates as any) spinner.succeed(chalk.green('Platform configuration updated successfully')) - console.log(chalk.blue(`Name: ${platform.name}`)) - console.log(chalk.blue(`Endpoint: ${platform.endpoint}`)) + const platformDef = PLATFORM_DEFINITIONS.find((def) => def.name === platform.name) + console.log(chalk.blue(`Platform: ${platformDef?.displayName || platform.name}`)) + console.log(chalk.blue(`Type: ${platform.name}`)) console.log(chalk.blue(`Active: ${platform.active ? 'Yes' : 'No'}`)) } catch (error) { console.error(chalk.red(formatError(error))) @@ -204,23 +194,62 @@ export function platformCommands(program: Command) { program .command('update-config ') - .description('Update platform config only') - .requiredOption('-c, --config ', 'Platform config (JSON string)') + .description('Update platform config interactively or via JSON') + .option('-c, --config ', 'Platform config (JSON string)') .action(async (id, options) => { try { let configObj: Record - try { - configObj = JSON.parse(options.config) - } catch { - console.error(chalk.red('Invalid JSON config')) - process.exit(1) + + if (options.config) { + // Use provided JSON config + try { + configObj = JSON.parse(options.config) + } catch { + console.error(chalk.red('Invalid JSON config')) + process.exit(1) + } + } else { + // Interactive mode - get current platform first + const spinner = ora('Fetching platform...').start() + const platform = await platformCore.getPlatform(id) + spinner.stop() + + const platformDef = PLATFORM_DEFINITIONS.find((def) => def.name === platform.name) + if (!platformDef) { + console.error(chalk.red(`Unknown platform type: ${platform.name}`)) + process.exit(1) + } + + console.log(chalk.cyan(`\nUpdating config for ${platformDef.displayName}...\n`)) + + configObj = {} + for (const field of platformDef.configFields) { + const currentValue = (platform.config as Record)[field.name] + const answer = await inquirer.prompt([ + { + type: field.type || 'input', + name: field.name, + message: field.message, + default: currentValue || field.default, + validate: field.validate || ((value: string) => { + if (field.required && !value?.toString().trim()) { + return `${field.name} is required` + } + return true + }), + }, + ]) + configObj[field.name] = answer[field.name] + } } const spinner = ora('Updating platform config...').start() const platform = await platformCore.updatePlatformConfig(id, configObj) spinner.succeed(chalk.green('Platform config updated successfully')) - console.log(chalk.blue(`Name: ${platform.name}`)) - console.log(chalk.blue(`Config: ${JSON.stringify(platform.config, null, 2)}`)) + const platformDef = PLATFORM_DEFINITIONS.find((def) => def.name === platform.name) + console.log(chalk.blue(`\nPlatform: ${platformDef?.displayName || platform.name}`)) + console.log(chalk.blue(`Type: ${platform.name}`)) + console.log(chalk.dim(`Config: ${JSON.stringify(platform.config, null, 2)}`)) } catch (error) { console.error(chalk.red(formatError(error))) process.exit(1) @@ -263,8 +292,9 @@ export function platformCommands(program: Command) { try { const platform = await platformCore.activatePlatform(id) spinner.succeed(chalk.green('Platform activated successfully')) - console.log(chalk.blue(`Name: ${platform.name}`)) - console.log(chalk.blue(`Endpoint: ${platform.endpoint}`)) + const platformDef = PLATFORM_DEFINITIONS.find((def) => def.name === platform.name) + console.log(chalk.blue(`Platform: ${platformDef?.displayName || platform.name}`)) + console.log(chalk.blue(`Type: ${platform.name}`)) console.log(chalk.blue(`Active: ${platform.active ? chalk.green('Yes') : 'No'}`)) } catch (error) { spinner.fail(chalk.red('Operation failed')) @@ -281,8 +311,9 @@ export function platformCommands(program: Command) { try { const platform = await platformCore.inactivatePlatform(id) spinner.succeed(chalk.green('Platform deactivated successfully')) - console.log(chalk.blue(`Name: ${platform.name}`)) - console.log(chalk.blue(`Endpoint: ${platform.endpoint}`)) + const platformDef = PLATFORM_DEFINITIONS.find((def) => def.name === platform.name) + console.log(chalk.blue(`Platform: ${platformDef?.displayName || platform.name}`)) + console.log(chalk.blue(`Type: ${platform.name}`)) console.log(chalk.blue(`Active: ${platform.active ? 'Yes' : chalk.dim('No')}`)) } catch (error) { spinner.fail(chalk.red('Operation failed')) diff --git a/packages/cli/src/core/platform.ts b/packages/cli/src/core/platform.ts index 47bf2381..de1a74de 100644 --- a/packages/cli/src/core/platform.ts +++ b/packages/cli/src/core/platform.ts @@ -3,7 +3,6 @@ import type { Platform, ApiResponse } from '../types' export interface CreatePlatformParams { name: string - endpoint: string config: Record active?: boolean } @@ -11,7 +10,6 @@ export interface CreatePlatformParams { export interface PlatformListItem { id: string name: string - endpoint: string config: Record active: boolean createdAt: string @@ -48,7 +46,6 @@ export async function createPlatform(params: CreatePlatformParams): Promise = { name: params.name, - endpoint: params.endpoint, config: params.config, active: params.active ?? true, } diff --git a/packages/cli/src/types/index.ts b/packages/cli/src/types/index.ts index 9c9d0b87..9fb8985b 100644 --- a/packages/cli/src/types/index.ts +++ b/packages/cli/src/types/index.ts @@ -72,13 +72,70 @@ export interface Schedule { export interface Platform { id: string name: string - endpoint: string config: Record active: boolean createdAt: string updatedAt: string } +// Platform configuration definitions +export interface PlatformConfigField { + name: string + message: string + type?: 'input' | 'password' | 'number' + required?: boolean + default?: string | number + validate?: (value: string) => boolean | string +} + +export interface PlatformDefinition { + name: string + displayName: string + description: string + configFields: PlatformConfigField[] +} + +// Platform configurations +export const PLATFORM_DEFINITIONS: PlatformDefinition[] = [ + { + name: 'telegram', + displayName: 'Telegram', + description: 'Telegram Bot Platform', + configFields: [ + { + name: 'botToken', + message: 'Bot Token:', + type: 'password', + required: true, + validate: (value: string) => { + if (!value.trim()) return 'Bot token is required' + return true + }, + }, + ], + }, + // Future platforms can be added here + // { + // name: 'discord', + // displayName: 'Discord', + // description: 'Discord Bot Platform', + // configFields: [ + // { + // name: 'botToken', + // message: 'Bot Token:', + // type: 'password', + // required: true, + // }, + // { + // name: 'clientId', + // message: 'Client ID:', + // type: 'input', + // required: true, + // }, + // ], + // }, +] + export interface MCPConnection { id: string type: string diff --git a/packages/db/src/platform.ts b/packages/db/src/platform.ts index d080bd56..d19bd652 100644 --- a/packages/db/src/platform.ts +++ b/packages/db/src/platform.ts @@ -3,7 +3,7 @@ import { boolean, jsonb, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-c export const platform = pgTable('platform', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull(), - endpoint: text('endpoint').notNull(), + // endpoint: text('endpoint').notNull(), config: jsonb('config').notNull(), active: boolean('active').notNull().default(true), createdAt: timestamp('created_at').notNull().defaultNow(), diff --git a/packages/platform-telegram/package.json b/packages/platform-telegram/package.json index 774eafe9..34c84746 100644 --- a/packages/platform-telegram/package.json +++ b/packages/platform-telegram/package.json @@ -10,7 +10,6 @@ "memoh-tg-bot": "./src/bot.ts" }, "scripts": { - "start": "bun run src/bot.ts", "dev": "bun run --watch src/bot.ts" }, "keywords": [], @@ -20,6 +19,7 @@ "dependencies": { "@memoh/client": "workspace:*", "@memoh/platform": "workspace:*", + "@memoh/shared": "workspace:*", "dotenv": "^16.4.7", "ioredis": "^5.9.1", "telegraf": "^4.16.3", diff --git a/packages/platform-telegram/src/bot.ts b/packages/platform-telegram/src/bot.ts deleted file mode 100644 index a9b1e85a..00000000 --- a/packages/platform-telegram/src/bot.ts +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bun - -/** - * Telegram Bot Standalone Entry Point - * - * This file allows running the Telegram bot as a standalone process - */ - -import { TelegramPlatform } from './index' - -async function main() { - const botToken = process.env.BOT_TOKEN - const redisUrl = process.env.REDIS_URL - const apiUrl = process.env.API_BASE_URL - - if (!botToken) { - console.error('❌ BOT_TOKEN environment variable is required') - process.exit(1) - } - - console.log('🚀 Starting Telegram bot...') - console.log(`📡 API URL: ${apiUrl || 'http://localhost:7002'}`) - console.log(`💾 Redis URL: ${redisUrl || 'redis://localhost:6379'}`) - - const platform = new TelegramPlatform() - - try { - platform.serve() - - console.log('✅ Bot is running...') - console.log('Press Ctrl+C to stop') - - // Graceful shutdown - process.once('SIGINT', async () => { - console.log('\n🛑 Stopping bot...') - await platform.stop() - process.exit(0) - }) - - process.once('SIGTERM', async () => { - console.log('\n🛑 Stopping bot...') - await platform.stop() - process.exit(0) - }) - } catch (error) { - console.error('❌ Failed to start bot:', error) - process.exit(1) - } -} - -main() - diff --git a/packages/platform-telegram/src/index.ts b/packages/platform-telegram/src/index.ts index 1a6d7c65..c3962374 100644 --- a/packages/platform-telegram/src/index.ts +++ b/packages/platform-telegram/src/index.ts @@ -1,65 +1,38 @@ import { Telegraf, type Context } from 'telegraf' -import { BasePlatform, SendSchema } from '@memoh/platform' +import { BasePlatform, PlatformMessage } from '@memoh/platform' import { handleLogin, handleLogout, handleWhoami, requireAuth } from './auth' import { chatStreamAsync, type StreamEvent } from '@memoh/client' import { getTokenStorage } from './storage' -import z from 'zod' import Redis from 'ioredis' +import { Platform } from '@memoh/shared' export interface TelegramPlatformConfig { botToken: string - redisUrl?: string - apiUrl?: string } + export class TelegramPlatform extends BasePlatform { name = 'telegram' - description = 'Telegram Bot platform for Memoh' - config = z.object({ - botToken: z.string(), - }) - port = 7101 + description = 'Telegram Bot platform' private bot?: Telegraf redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379') - // private storage?: TelegramRedisStorage - override async start(config: z.infer): Promise { - const botToken = config.botToken as string - if (!botToken) { - throw new Error('Bot token is required') - } - - // // Initialize storage - // this.storage = new TelegramRedisStorage({ - // redisUrl: config.redisUrl as string, - // apiUrl: config.apiUrl as string, - // }) - - // Initialize bot + async start({ botToken }: TelegramPlatformConfig): Promise { this.bot = new Telegraf(botToken) - // Register commands this.registerCommands() - // Start bot this.bot.launch() - console.log('✅ Telegram bot started successfully') } async stop(): Promise { if (this.bot) { this.bot.stop('SIGTERM') - console.log('🛑 Telegram bot stopped') } - - // if (this.storage) { - // await this.storage.close() - // console.log('🛑 Redis connection closed') - // } } - async send({ userId, message }: z.infer): Promise { + async send({ message, userId }: PlatformMessage): Promise { const pattern = 'memoh:telegram:*:userId' let cursor = '0' let telegramUserId: string | null = null @@ -74,11 +47,9 @@ export class TelegramPlatform extends BasePlatform { ) cursor = nextCursor - // 检查每个 key 的值是否匹配目标 userId for (const key of keys) { const storedUserId = await this.redis.get(key) if (storedUserId === userId) { - // 从 key 中提取 telegramUserId: memoh:telegram:{telegramUserId}:userId const match = key.match(/^memoh:telegram:(.+):userId$/) if (match) { telegramUserId = match[1] diff --git a/packages/platform/src/index.ts b/packages/platform/src/index.ts index bd14691e..a29ed966 100644 --- a/packages/platform/src/index.ts +++ b/packages/platform/src/index.ts @@ -1,70 +1,18 @@ -import { Elysia } from 'elysia' -import { cors } from '@elysiajs/cors' -import { z } from 'zod' - -export const SendSchema = z.object({ - message: z.string(), - userId: z.string(), -}) +export interface PlatformMessage { + message: string + userId: string +} export class BasePlatform { name: string = 'base' description: string = 'Base platform' started: boolean = false - port: number = 7003 - - config = z.record(z.string(), z.unknown()) // eslint-disable-next-line @typescript-eslint/no-unused-vars - async start(config: z.infer): Promise {} + async start(config: unknown): Promise {} async stop(): Promise {} // eslint-disable-next-line @typescript-eslint/no-unused-vars - async send(data: z.infer): Promise {} - - serve>(config?: T): void { - new Elysia() - .use(cors({ - origin: '*', - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'], - credentials: true, - })) - .onStart(() => { - if (config) { - this.started = true - this.start(config) - } - }) - .post('/start', async ({ body }) => { - if (!this.started) { - this.start(body) - this.started = true - } - return { - success: true, - } - }, { - body: this.config, - }) - .post('/stop', async () => { - if (this.started) { - this.stop() - this.started = false - } - return { - success: true, - } - }) - .post('/send', async ({ body }) => { - await this.send(body) - return { - success: true, - } - }, { - body: SendSchema, - }) - .listen(this.port) - } + async send(data: PlatformMessage): Promise {} } \ No newline at end of file diff --git a/packages/shared/src/platform.ts b/packages/shared/src/platform.ts index de71401c..bc371c73 100644 --- a/packages/shared/src/platform.ts +++ b/packages/shared/src/platform.ts @@ -1,7 +1,7 @@ export interface Platform { id: string name: string - endpoint: string + // endpoint: string config: Record active: boolean } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a91aa001..db0dcfdd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -132,6 +132,12 @@ importers: '@memoh/memory': specifier: workspace:* version: link:../memory + '@memoh/platform': + specifier: workspace:* + version: link:../platform + '@memoh/platform-telegram': + specifier: workspace:* + version: link:../platform-telegram '@memoh/shared': specifier: workspace:* version: link:../shared @@ -270,6 +276,9 @@ importers: '@memoh/platform': specifier: workspace:* version: link:../platform + '@memoh/shared': + specifier: workspace:* + version: link:../shared dotenv: specifier: ^16.4.7 version: 16.6.1