import {bot_instance_context, BotContext} from "./bot_instance_init"; import {bot_config, bot_status} from "../../types/bot/instance/bot_runtime"; import {validate_code_map} from "../../utils/validate_code_map"; import {init_workflow} from "../workflow/init"; import {update_one_bot_status} from "../../service/server/update/bot_status.ts"; import type {Socket} from "socket.io-client"; const bot_context_map: Map> = new Map(); const bot_action_lock_map = new Map(); type ContextObserver = (context: bot_instance_context | undefined) => void; const bot_context_observers = new Map>(); export function get_bot_context(bot_id: number): bot_instance_context | undefined { return bot_context_map.get(bot_id); } function notify_context_observer(callback: ContextObserver, context: bot_instance_context | undefined) { try { callback(context); } catch { console.error('Bot context observer failed'); } } function notify_bot_context(bot_id: number) { for (const callback of Array.from(bot_context_observers.get(bot_id) ?? [])) { notify_context_observer(callback, bot_context_map.get(bot_id)); } } export function observe_bot_context(bot_id: number, callback: ContextObserver): () => void { let observers = bot_context_observers.get(bot_id); if (!observers) { observers = new Set(); bot_context_observers.set(bot_id, observers); } observers.add(callback); notify_context_observer(callback, bot_context_map.get(bot_id)); return () => { observers.delete(callback); if (observers.size === 0 && bot_context_observers.get(bot_id) === observers) { bot_context_observers.delete(bot_id); } }; } function remove_bot_context(bot_id: number, context: bot_instance_context) { if (bot_context_map.get(bot_id) === context) { bot_context_map.delete(bot_id); notify_bot_context(bot_id); } } export async function start_bot_context(main_server_socket: Socket, bot_config: bot_config, node_id: number) { if (!Number.isSafeInteger(node_id) || node_id <= 0 || !bot_config || typeof bot_config !== "object" || Array.isArray(bot_config) || !bot_config.instance_config || typeof bot_config.instance_config !== "object" || Array.isArray(bot_config.instance_config) || !Number.isSafeInteger(bot_config.instance_config.bot_id) || bot_config.instance_config.bot_id <= 0 || !Number.isSafeInteger(bot_config.instance_config.user_id) || bot_config.instance_config.user_id <= 0 || !Array.isArray(bot_config.plugins) || !Array.isArray(bot_config.workflow) || !bot_config.plugins.every(item => item && typeof item === "object" && !Array.isArray(item)) || !bot_config.workflow.every(item => item && typeof item === "object" && !Array.isArray(item))) { return validate_code_map.web.invalid_parameter; } const bot_id = bot_config.instance_config.bot_id; if (bot_action_lock_map.get(bot_id) ?? false) { return validate_code_map.bot_context.processing; } bot_action_lock_map.set(bot_id, true); let new_context: BotContext | undefined; let context_started = false; try { const temp_context = bot_context_map.get(bot_id); if (temp_context !== undefined) { if (temp_context.bot_status === "online" || temp_context.bot_status === "connecting" || temp_context.bot_status === "reconnecting" || temp_context.bot_status === "ready") { const result = await temp_context.connect(); return result === validate_code_map.bot_context.ok ? validate_code_map.bot_context.context_created : result; } else if (temp_context.bot_status === "disconnecting") { return validate_code_map.bot_context.processing; } else { await temp_context.clear_this_context(); remove_bot_context(bot_id, temp_context); } } const context = new_context = new BotContext(bot_config.instance_config); context.bot_config.plugins = structuredClone(bot_config.plugins); context.bot_config.workflow = structuredClone(bot_config.workflow); context.on("status_update", (status) => { void update_one_bot_status(main_server_socket, node_id, bot_id, bot_config.instance_config.user_id, status) .catch(error => console.error(`Bot ${bot_id} status update failed`, error)); }); const origin_clean = context.clear_this_context.bind(context); context.clear_this_context = async () => { try { await origin_clean(); } finally { remove_bot_context(bot_id, context); } }; const validation_result = context.validate_config(bot_config.instance_config); if (validation_result !== validate_code_map.bot_context.ok) { console.error(`Bot ${bot_id} configuration validation failed, code: ${validation_result}`); return validation_result; } await context.plugin_manager.init(bot_config.plugins); await init_workflow(context, bot_config.workflow); bot_context_map.set(bot_id, context); notify_bot_context(bot_id); const result = await context.connect(); if (result !== validate_code_map.bot_context.ok && result !== validate_code_map.bot_context.instance_connected && result !== validate_code_map.bot_context.instance_connecting) { console.error(`Bot ${bot_id} startup failed, code: ${result}`); return result; } context_started = true; return result === validate_code_map.bot_context.ok ? validate_code_map.bot_context.context_created : result; } catch (error) { console.error(`Bot ${bot_id} startup failed: ${error instanceof Error ? error.message : String(error)}`); return validate_code_map.bot_context.error; } finally { if (new_context && !context_started) { try { await new_context.clear_this_context(); } catch (error) { console.error(`Bot ${bot_id} cleanup failed`, error); } } bot_action_lock_map.delete(bot_id); } } export async function get_bot_context_status() : Promise<{ bot_id: number; status: bot_status; }[]> { return Array.from(bot_context_map).map(([bot_id, context]) => { return { bot_id, status: context.bot_status, } }); } export async function get_bot_context_status_by_ids(bot_id: number[]) : Promise<{ bot_id: number; status: bot_status; }[]> { return bot_id.map((id) => { return { bot_id: id, status: bot_context_map.get(id)?.bot_status ?? "offline", } }); } export async function stop_bot_context(bot_id: number) { if (bot_action_lock_map.get(bot_id) ?? false) { return validate_code_map.bot_context.processing; } bot_action_lock_map.set(bot_id, true); try { if (!bot_context_map.has(bot_id)) { return validate_code_map.bot_context.instance_end_by_user; } const context = bot_context_map.get(bot_id); if (context !== undefined) { await context.clear_this_context(); } if (context) remove_bot_context(bot_id, context); return validate_code_map.bot_context.instance_end_by_user; } catch (error) { return validate_code_map.bot_context.error; } finally { bot_action_lock_map.delete(bot_id); } }