{ "success": false, "code": 88888, "message": "接口路径不存在 请前往 https://api.vvhan.com/ 查看免费API接口列表"}
试试
试试
试试
{ "success": false, "code": 88888, "message": "接口路径不存在 请前往 https://api.vvhan.com/ 查看免费API接口列表"}
就算鼻血流尽,也不能放弃妄想。
雨,打湿了窗,打湿了行人,打湿了梦境。
我们没有相见,却是此刻最好的相聚。
111
感谢大佬
虽然你不是与我并肩走到最后的人 但感谢你曾照亮了我人生中那段漆黑的路
谢谢,顶起
当你想要放弃的时候,想想是什么让你当初坚持走到了这里。
嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻
感谢
首页
统计
归档
友链
直播
壁纸
关于
Search
1
打开Office时,提示找不到AppVlsvSubsystems64.dll,无法继续执行代码
2,974 阅读
2
微软应用商店安装应用时出现错误,提示错误代码: 0x80070005
2,410 阅读
3
复仇者联盟全集—在线观看
1,836 阅读
4
432元撸腾讯云4年2H4G6M云服务器
1,791 阅读
5
欢迎使用 Typecho
1,749 阅读
技术分享
Cloudflare
问题分享
源码分享
网站美化
值得推荐
视频推荐
音乐推荐
事件推荐
文字推荐
技巧分享
登录
Search
标签搜索
Windows
网站美化
活着
腾讯云
轻量应用服务器
Docker
Python
Typecho
错误
Cloudflare Workers
Cloudflare
服务器
云服务器
薅羊毛
复仇者联盟
在线观看
网站统计
报错
符号表情
个人主页
Too子牙
累计撰写
27
篇文章
累计收到
80
条评论
今日撰写
0
篇文章
首页
栏目
技术分享
Cloudflare
问题分享
源码分享
网站美化
值得推荐
视频推荐
音乐推荐
事件推荐
文字推荐
技巧分享
页面
统计
归档
友链
直播
壁纸
关于
用户登录
登录
搜索到
1
篇与
Telegram 机器人
的结果
2026-03-04
使用 Cloudflare Workers 部署专属于你的 AI 聊天机器人
项目简介ChatGPT-Telegram-Workers 是一个轻量级的 Telegram AI 聊天机器人,可部署在 Cloudflare Workers 上,单文件、无依赖、免费额度够用核心特性:✅ 支持多 AI 平台(Cloudflare AI、OpenAI、Azure、DeepSeek、Gemini、Groq 等)✅ 流式输出、多语言支持✅ 插件系统、自定义命令✅ 支持文生图、模型切换准备工作第一步:创建 Telegram Bot打开 Telegram,搜索 @BotFather发送 /newbot 创建机器人按提示设置名称和用户名设置完后,BotFather 会生成 Token,复制保存 Bot Token(类似 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11)第二步:启用 Cloudflare AI(免费)Cloudflare AI 免费可用,无需付费计划2.1 激活 Workers AI登录 Cloudflare 控制台进入 AI 标签页点击 启用 AI 激活服务2.2 了解 Workers AI 免费额度每天 10000 Neurons(约 1000-2000 次对话,取决于模型)支持 50+ 开源模型无需绑定信用卡详情请查看:https://developers.cloudflare.com/workers-ai/platform/pricing2.3 选择模型推荐文本生成模型(免费额度内可用):@cf/zai-org/glm-4.7-flash - GLM-4.7-Flash(专为超100种语言优化,快速高效,支持多轮工具调用)@cf/openai/gpt-oss-120b - GPT-OSS-120B(OpenAI开放权重的千亿级MoE模型,推理和工具调用能力强,支持代码解释器)@cf/deepseek-ai/deepseek-r1-distill-qwen-32b - DeepSeek-R1-Distill-Qwen-32B(适合深度推理、数学解题、代码生成)@cf/qwen/qwen3-30b-a3b-fp8 - Qwen3-30B-A3B-FP8(适合高并发、通用对话、工具调用)其他实用模型:@cf/stabilityai/stable-diffusion-xl-base-1.0 - Stable Diffusion XL(图像生成)@cf/openai/whisper-large-v3-turbo - OpenAI Whisper(语音识别)@cf/baai/bge-m3 - BGE-base(做向量搜索用)@cf/meta/llama-guard-3-8b - Llama Guard 3(检测有害内容)@cf/qwen/qwen2.5-coder-32b-instruct - Qwen2.5-Coder-32B(代码生成)完整模型列表:https://developers.cloudflare.com/workers-ai/models/第三步:部署 Cloudflare Workers3.1 创建 Worker访问 Workers 和 Pages点击 创建 Worker输入 Worker 名称(如 chatgpt-bot)点击 部署3.2 粘贴代码进入刚创建的 Worker点击 编辑代码打开 https://github.com/tbxark/ChatGPT-Telegram-Workers/blob/master/dist/index.js{collapse}{collapse-item label="index.js"}class AgentShareConfig { AI_PROVIDER = "auto"; AI_IMAGE_PROVIDER = "auto"; SYSTEM_INIT_MESSAGE = null; } class OpenAIConfig { OPENAI_API_KEY = []; OPENAI_CHAT_MODEL = "gpt-4o-mini"; OPENAI_API_BASE = "https://api.openai.com/v1"; OPENAI_API_EXTRA_PARAMS = {}; OPENAI_CHAT_MODELS_LIST = ""; } class DallEConfig { DALL_E_MODEL = "dall-e-3"; DALL_E_IMAGE_SIZE = "1024x1024"; DALL_E_IMAGE_QUALITY = "standard"; DALL_E_IMAGE_STYLE = "vivid"; DALL_E_MODELS_LIST = '["dall-e-3"]'; } class AzureConfig { AZURE_API_KEY = null; AZURE_RESOURCE_NAME = null; AZURE_CHAT_MODEL = "gpt-4o-mini"; AZURE_IMAGE_MODEL = "dall-e-3"; AZURE_API_VERSION = "2024-06-01"; AZURE_CHAT_MODELS_LIST = ""; AZURE_CHAT_EXTRA_PARAMS = {}; } class WorkersConfig { CLOUDFLARE_ACCOUNT_ID = null; CLOUDFLARE_TOKEN = null; WORKERS_CHAT_MODEL = "@cf/qwen/qwen1.5-7b-chat-awq"; WORKERS_IMAGE_MODEL = "@cf/black-forest-labs/flux-1-schnell"; WORKERS_CHAT_MODELS_LIST = ""; WORKERS_IMAGE_MODELS_LIST = ""; WORKERS_CHAT_EXTRA_PARAMS = {}; } class GeminiConfig { GOOGLE_API_KEY = null; GOOGLE_API_BASE = "https://generativelanguage.googleapis.com/v1beta"; GOOGLE_CHAT_MODEL = "gemini-1.5-flash"; GOOGLE_CHAT_MODELS_LIST = ""; GOOGLE_CHAT_EXTRA_PARAMS = {}; } class MistralConfig { MISTRAL_API_KEY = null; MISTRAL_API_BASE = "https://api.mistral.ai/v1"; MISTRAL_CHAT_MODEL = "mistral-tiny"; MISTRAL_CHAT_MODELS_LIST = ""; MISTRAL_CHAT_EXTRA_PARAMS = {}; } class CohereConfig { COHERE_API_KEY = null; COHERE_API_BASE = "https://api.cohere.com/v2"; COHERE_CHAT_MODEL = "command-r-plus"; COHERE_CHAT_MODELS_LIST = ""; COHERE_CHAT_EXTRA_PARAMS = {}; } class AnthropicConfig { ANTHROPIC_API_KEY = null; ANTHROPIC_API_BASE = "https://api.anthropic.com/v1"; ANTHROPIC_CHAT_MODEL = "claude-3-5-haiku-latest"; ANTHROPIC_CHAT_MODELS_LIST = ""; ANTHROPIC_CHAT_EXTRA_PARAMS = {}; } class DeepSeekConfig { DEEPSEEK_API_KEY = null; DEEPSEEK_API_BASE = "https://api.deepseek.com"; DEEPSEEK_CHAT_MODEL = "deepseek-chat"; DEEPSEEK_CHAT_MODELS_LIST = ""; DEEPSEEK_CHAT_EXTRA_PARAMS = {}; } class GroqConfig { GROQ_API_KEY = null; GROQ_API_BASE = "https://api.groq.com/openai/v1"; GROQ_CHAT_MODEL = "groq-chat"; GROQ_CHAT_MODELS_LIST = ""; GROQ_CHAT_EXTRA_PARAMS = {}; } class XAIConfig { XAI_API_KEY = null; XAI_API_BASE = "https://api.x.ai/v1"; XAI_CHAT_MODEL = "grok-2-latest"; XAI_CHAT_MODELS_LIST = ""; XAI_CHAT_EXTRA_PARAMS = {}; } class DefineKeys { DEFINE_KEYS = []; } class EnvironmentConfig { LANGUAGE = "zh-cn"; UPDATE_BRANCH = "master"; CHAT_COMPLETE_API_TIMEOUT = 0; TELEGRAM_API_DOMAIN = "https://api.telegram.org"; TELEGRAM_AVAILABLE_TOKENS = []; DEFAULT_PARSE_MODE = "Markdown"; TELEGRAM_MIN_STREAM_INTERVAL = 0; TELEGRAM_PHOTO_SIZE_OFFSET = 1; TELEGRAM_IMAGE_TRANSFER_MODE = "base64"; MODEL_LIST_COLUMNS = 1; I_AM_A_GENEROUS_PERSON = false; CHAT_WHITE_LIST = []; LOCK_USER_CONFIG_KEYS = [ "OPENAI_API_BASE", "GOOGLE_API_BASE", "MISTRAL_API_BASE", "COHERE_API_BASE", "ANTHROPIC_API_BASE", "DEEPSEEK_API_BASE", "GROQ_API_BASE", "XAI_API_BASE" ]; TELEGRAM_BOT_NAME = []; CHAT_GROUP_WHITE_LIST = []; GROUP_CHAT_BOT_ENABLE = true; GROUP_CHAT_BOT_SHARE_MODE = true; AUTO_TRIM_HISTORY = true; MAX_HISTORY_LENGTH = 20; MAX_TOKEN_LENGTH = -1; HISTORY_IMAGE_PLACEHOLDER = null; HIDE_COMMAND_BUTTONS = []; SHOW_REPLY_BUTTON = false; EXTRA_MESSAGE_CONTEXT = false; EXTRA_MESSAGE_MEDIA_COMPATIBLE = ["image"]; STREAM_MODE = true; SAFE_MODE = true; DEBUG_MODE = false; DEV_MODE = false; } const en = { "env": { "system_init_message": "You are a helpful assistant" }, "command": { "help": { "summary": "The following commands are currently supported:\n", "help": "Get command help", "new": "Start a new conversation", "start": "Get your ID and start a new conversation", "img": "Generate an image, the complete command format is `/img image description`, for example `/img beach at moonlight`", "version": "Get the current version number to determine whether to update", "setenv": "Set user configuration, the complete command format is /setenv KEY=VALUE", "setenvs": 'Batch set user configurations, the full format of the command is /setenvs {"KEY1": "VALUE1", "KEY2": "VALUE2"}', "delenv": "Delete user configuration, the complete command format is /delenv KEY", "clearenv": "Clear all user configuration", "system": "View some system information", "redo": "Redo the last conversation, /redo with modified content or directly /redo", "echo": "Echo the message", "models": "switch chat model" }, "new": { "new_chat_start": "A new conversation has started" } }, "callback_query": { "open_model_list": "Open models list", "select_provider": "Select a provider:", "select_model": "Choose model:", "change_model": "Change model to " } }; const pt = { "env": { "system_init_message": "Você é um assistente útil" }, "command": { "help": { "summary": "Os seguintes comandos são suportados atualmente:\n", "help": "Obter ajuda sobre comandos", "new": "Iniciar uma nova conversa", "start": "Obter seu ID e iniciar uma nova conversa", "img": "Gerar uma imagem, o formato completo do comando é `/img descrição da imagem`, por exemplo `/img praia ao luar`", "version": "Obter o número da versão atual para determinar se é necessário atualizar", "setenv": "Definir configuração do usuário, o formato completo do comando é /setenv CHAVE=VALOR", "setenvs": 'Definir configurações do usuário em lote, o formato completo do comando é /setenvs {"CHAVE1": "VALOR1", "CHAVE2": "VALOR2"}', "delenv": "Excluir configuração do usuário, o formato completo do comando é /delenv CHAVE", "clearenv": "Limpar todas as configurações do usuário", "system": "Ver algumas informações do sistema", "redo": "Refazer a última conversa, /redo com conteúdo modificado ou diretamente /redo", "echo": "Repetir a mensagem", "models": "Mudar o modelo de diálogo" }, "new": { "new_chat_start": "Uma nova conversa foi iniciada" } }, "callback_query": { "open_model_list": "Abra a lista de modelos", "select_provider": "Escolha um fornecedor de modelos.:", "select_model": "Escolha um modelo:", "change_model": "O modelo de diálogo já foi modificado para" } }; const zhHans = { "env": { "system_init_message": "你是一个得力的助手" }, "command": { "help": { "summary": "当前支持以下命令:\n", "help": "获取命令帮助", "new": "发起新的对话", "start": "获取你的ID, 并发起新的对话", "img": "生成一张图片, 命令完整格式为 `/img 图片描述`, 例如`/img 月光下的沙滩`", "version": "获取当前版本号, 判断是否需要更新", "setenv": "设置用户配置,命令完整格式为 /setenv KEY=VALUE", "setenvs": '批量设置用户配置, 命令完整格式为 /setenvs {"KEY1": "VALUE1", "KEY2": "VALUE2"}', "delenv": "删除用户配置,命令完整格式为 /delenv KEY", "clearenv": "清除所有用户配置", "system": "查看当前一些系统信息", "redo": "重做上一次的对话, /redo 加修改过的内容 或者 直接 /redo", "echo": "回显消息", "models": "切换对话模型" }, "new": { "new_chat_start": "新的对话已经开始" } }, "callback_query": { "open_model_list": "打开模型列表", "select_provider": "选择一个模型提供商:", "select_model": "选择一个模型:", "change_model": "对话模型已修改至" } }; const zhHant = { "env": { "system_init_message": "你是一個得力的助手" }, "command": { "help": { "summary": "當前支持的命令如下:\n", "help": "獲取命令幫助", "new": "開始一個新對話", "start": "獲取您的ID並開始一個新對話", "img": "生成圖片,完整命令格式為`/img 圖片描述`,例如`/img 海灘月光`", "version": "獲取當前版本號確認是否需要更新", "setenv": "設置用戶配置,完整命令格式為/setenv KEY=VALUE", "setenvs": '批量設置用户配置, 命令完整格式為 /setenvs {"KEY1": "VALUE1", "KEY2": "VALUE2"}', "delenv": "刪除用戶配置,完整命令格式為/delenv KEY", "clearenv": "清除所有用戶配置", "system": "查看一些系統信息", "redo": "重做上一次的對話 /redo 加修改過的內容 或者 直接 /redo", "echo": "回显消息", "models": "切換對話模式" }, "new": { "new_chat_start": "開始一個新對話" } }, "callback_query": { "open_model_list": "打開模型清單", "select_provider": "選擇一個模型供應商:", "select_model": "選擇一個模型:", "change_model": "對話模型已經修改至" } }; function loadI18n(lang) { switch (lang?.toLowerCase()) { case "cn": case "zh-cn": case "zh-hans": return zhHans; case "zh-tw": case "zh-hk": case "zh-mo": case "zh-hant": return zhHant; case "pt": case "pt-br": return pt; case "en": case "en-us": return en; default: return en; } } class ConfigMerger { static parseArray(raw) { raw = raw.trim(); if (raw === "") { return []; } if (raw.startsWith("[") && raw.endsWith("]")) { try { return JSON.parse(raw); } catch (e) { console.error(e); } } return raw.split(","); } static trim(source, lock) { const config = { ...source }; const keysSet = new Set(source?.DEFINE_KEYS || []); for (const key of lock) { keysSet.delete(key); } keysSet.add("DEFINE_KEYS"); for (const key of Object.keys(config)) { if (!keysSet.has(key)) { delete config[key]; } } return config; } static merge(target, source, exclude) { const sourceKeys = new Set(Object.keys(source)); for (const key of Object.keys(target)) { if (!sourceKeys.has(key)) { continue; } if (exclude && exclude.includes(key)) { continue; } const t = target[key] !== null && target[key] !== void 0 ? typeof target[key] : "string"; if (typeof source[key] !== "string") { target[key] = source[key]; continue; } switch (t) { case "number": target[key] = Number.parseInt(source[key], 10); break; case "boolean": target[key] = (source[key] || "false") === "true"; break; case "string": target[key] = source[key]; break; case "object": if (Array.isArray(target[key])) { target[key] = ConfigMerger.parseArray(source[key]); } else { try { target[key] = JSON.parse(source[key]); } catch (e) { console.error(e); } } break; default: target[key] = source[key]; break; } } } } const BUILD_TIMESTAMP = 1761036146; const BUILD_VERSION = "87adca1"; function createAgentUserConfig() { return Object.assign( {}, new DefineKeys(), new AgentShareConfig(), new OpenAIConfig(), new DallEConfig(), new AzureConfig(), new WorkersConfig(), new GeminiConfig(), new MistralConfig(), new CohereConfig(), new AnthropicConfig(), new DeepSeekConfig(), new GroqConfig(), new XAIConfig() ); } function fixApiBase(base) { return base.replace(/\/+$/, ""); } const ENV_KEY_MAPPER = { CHAT_MODEL: "OPENAI_CHAT_MODEL", API_KEY: "OPENAI_API_KEY", WORKERS_AI_MODEL: "WORKERS_CHAT_MODEL" }; class Environment extends EnvironmentConfig { BUILD_TIMESTAMP = BUILD_TIMESTAMP; BUILD_VERSION = BUILD_VERSION; I18N = loadI18n(); PLUGINS_ENV = {}; USER_CONFIG = createAgentUserConfig(); CUSTOM_COMMAND = {}; PLUGINS_COMMAND = {}; AI_BINDING = null; API_GUARD = null; DATABASE = null; CUSTOM_MESSAGE_RENDER = null; constructor() { super(); this.merge = this.merge.bind(this); } merge(source) { this.AI_BINDING = source.AI; this.DATABASE = source.DATABASE; this.API_GUARD = source.API_GUARD; this.mergeCommands( "CUSTOM_COMMAND_", "COMMAND_DESCRIPTION_", "COMMAND_SCOPE_", source, this.CUSTOM_COMMAND ); this.mergeCommands( "PLUGIN_COMMAND_", "PLUGIN_DESCRIPTION_", "PLUGIN_SCOPE_", source, this.PLUGINS_COMMAND ); const pluginEnvPrefix = "PLUGIN_ENV_"; for (const key of Object.keys(source)) { if (key.startsWith(pluginEnvPrefix)) { const plugin = key.substring(pluginEnvPrefix.length); this.PLUGINS_ENV[plugin] = source[key]; } } ConfigMerger.merge(this, source, [ "BUILD_TIMESTAMP", "BUILD_VERSION", "I18N", "PLUGINS_ENV", "USER_CONFIG", "CUSTOM_COMMAND", "PLUGINS_COMMAND", "DATABASE", "API_GUARD" ]); ConfigMerger.merge(this.USER_CONFIG, source); this.migrateOldEnv(source); this.fixAgentUserConfigApiBase(); this.USER_CONFIG.DEFINE_KEYS = []; this.I18N = loadI18n(this.LANGUAGE.toLowerCase()); } mergeCommands(prefix, descriptionPrefix, scopePrefix, source, target) { for (const key of Object.keys(source)) { if (key.startsWith(prefix)) { const cmd = key.substring(prefix.length); target[`/${cmd}`] = { value: source[key], description: source[`${descriptionPrefix}${cmd}`], scope: source[`${scopePrefix}${cmd}`]?.split(",").map((s) => s.trim()) }; } } } migrateOldEnv(source) { if (source.TELEGRAM_TOKEN && !this.TELEGRAM_AVAILABLE_TOKENS.includes(source.TELEGRAM_TOKEN)) { if (source.BOT_NAME && this.TELEGRAM_AVAILABLE_TOKENS.length === this.TELEGRAM_BOT_NAME.length) { this.TELEGRAM_BOT_NAME.push(source.BOT_NAME); } this.TELEGRAM_AVAILABLE_TOKENS.push(source.TELEGRAM_TOKEN); } if (source.OPENAI_API_DOMAIN && !this.USER_CONFIG.OPENAI_API_BASE) { this.USER_CONFIG.OPENAI_API_BASE = `${source.OPENAI_API_DOMAIN}/v1`; } if (source.WORKERS_AI_MODEL && !this.USER_CONFIG.WORKERS_CHAT_MODEL) { this.USER_CONFIG.WORKERS_CHAT_MODEL = source.WORKERS_AI_MODEL; } if (source.API_KEY && this.USER_CONFIG.OPENAI_API_KEY.length === 0) { this.USER_CONFIG.OPENAI_API_KEY = source.API_KEY.split(","); } if (source.CHAT_MODEL && !this.USER_CONFIG.OPENAI_CHAT_MODEL) { this.USER_CONFIG.OPENAI_CHAT_MODEL = source.CHAT_MODEL; } if (source.GOOGLE_COMPLETIONS_API && !this.USER_CONFIG.GOOGLE_API_BASE) { this.USER_CONFIG.GOOGLE_API_BASE = source.GOOGLE_COMPLETIONS_API.replace(/\/models\/?$/, ""); } if (source.GOOGLE_COMPLETIONS_MODEL && !this.USER_CONFIG.GOOGLE_CHAT_MODEL) { this.USER_CONFIG.GOOGLE_CHAT_MODEL = source.GOOGLE_COMPLETIONS_MODEL; } if (source.AZURE_COMPLETIONS_API && !this.USER_CONFIG.AZURE_CHAT_MODEL) { const url = new URL(source.AZURE_COMPLETIONS_API); this.USER_CONFIG.AZURE_RESOURCE_NAME = url.hostname.split(".").at(0) || null; this.USER_CONFIG.AZURE_CHAT_MODEL = url.pathname.split("/").at(3) || "gpt-4o-mini"; this.USER_CONFIG.AZURE_API_VERSION = url.searchParams.get("api-version") || "2024-06-01"; } if (source.AZURE_DALLE_API && !this.USER_CONFIG.AZURE_IMAGE_MODEL) { const url = new URL(source.AZURE_DALLE_API); this.USER_CONFIG.AZURE_RESOURCE_NAME = url.hostname.split(".").at(0) || null; this.USER_CONFIG.AZURE_IMAGE_MODEL = url.pathname.split("/").at(3) || "dall-e-3"; this.USER_CONFIG.AZURE_API_VERSION = url.searchParams.get("api-version") || "2024-06-01"; } } fixAgentUserConfigApiBase() { const keys = [ "OPENAI_API_BASE", "GOOGLE_API_BASE", "MISTRAL_API_BASE", "COHERE_API_BASE", "ANTHROPIC_API_BASE", "DEEPSEEK_API_BASE", "GROQ_API_BASE", "XAI_API_BASE" ]; for (const key of keys) { const base = this.USER_CONFIG[key]; if (this.USER_CONFIG[key] && typeof base === "string") { this.USER_CONFIG[key] = fixApiBase(base); } } this.TELEGRAM_API_DOMAIN = fixApiBase(this.TELEGRAM_API_DOMAIN); } } const ENV = new Environment(); class ShareContext { botId; botToken; botName = null; chatHistoryKey; lastMessageKey; configStoreKey; groupAdminsKey; constructor(token, update) { const botId = Number.parseInt(token.split(":")[0]); const telegramIndex = ENV.TELEGRAM_AVAILABLE_TOKENS.indexOf(token); if (telegramIndex === -1) { throw new Error("Token not allowed"); } if (ENV.TELEGRAM_BOT_NAME.length > telegramIndex) { this.botName = ENV.TELEGRAM_BOT_NAME[telegramIndex]; } this.botToken = token; this.botId = botId; const id = update.chatID; if (id === void 0 || id === null) { throw new Error("Chat id not found"); } let historyKey = `history:${id}`; let configStoreKey = `user_config:${id}`; if (botId) { historyKey += `:${botId}`; configStoreKey += `:${botId}`; } switch (update.chatType) { case "group": case "supergroup": if (!ENV.GROUP_CHAT_BOT_SHARE_MODE && update.fromUserID) { historyKey += `:${update.fromUserID}`; configStoreKey += `:${update.fromUserID}`; } this.groupAdminsKey = `group_admin:${id}`; break; } if (update.isForum && update.isTopicMessage) { if (update.messageThreadID) { historyKey += `:${update.messageThreadID}`; configStoreKey += `:${update.messageThreadID}`; } } this.chatHistoryKey = historyKey; this.lastMessageKey = `last_message_id:${historyKey}`; this.configStoreKey = configStoreKey; } } class WorkerContext { USER_CONFIG; SHARE_CONTEXT; constructor(USER_CONFIG, SHARE_CONTEXT) { this.USER_CONFIG = USER_CONFIG; this.SHARE_CONTEXT = SHARE_CONTEXT; this.execChangeAndSave = this.execChangeAndSave.bind(this); } static async from(token, update) { const context = new UpdateContext(update); const SHARE_CONTEXT = new ShareContext(token, context); const USER_CONFIG = Object.assign({}, ENV.USER_CONFIG); try { const userConfig = JSON.parse(await ENV.DATABASE.get(SHARE_CONTEXT.configStoreKey)); ConfigMerger.merge(USER_CONFIG, ConfigMerger.trim(userConfig, ENV.LOCK_USER_CONFIG_KEYS) || {}); } catch (e) { console.warn(e); } return new WorkerContext(USER_CONFIG, SHARE_CONTEXT); } async execChangeAndSave(values) { for (const ent of Object.entries(values || {})) { let [key, value] = ent; key = ENV_KEY_MAPPER[key] || key; if (ENV.LOCK_USER_CONFIG_KEYS.includes(key)) { throw new Error(`Key ${key} is locked`); } const configKeys = Object.keys(this.USER_CONFIG || {}) || []; if (!configKeys.includes(key)) { throw new Error(`Key ${key} is not allowed`); } this.USER_CONFIG.DEFINE_KEYS.push(key); ConfigMerger.merge(this.USER_CONFIG, { [key]: value }); console.log("Update user config: ", key, this.USER_CONFIG[key]); } this.USER_CONFIG.DEFINE_KEYS = Array.from(new Set(this.USER_CONFIG.DEFINE_KEYS)); await ENV.DATABASE.put( this.SHARE_CONTEXT.configStoreKey, JSON.stringify(ConfigMerger.trim(this.USER_CONFIG, ENV.LOCK_USER_CONFIG_KEYS)) ); } } class UpdateContext { fromUserID; chatID; chatType; isForum; isTopicMessage; messageThreadID; constructor(update) { if (update.message) { this.fromUserID = update.message.from?.id; this.chatID = update.message.chat.id; this.chatType = update.message.chat.type; this.isForum = update.message.chat.is_forum; this.isTopicMessage = update.message.is_topic_message; this.messageThreadID = update.message.message_thread_id; } else if (update.callback_query) { this.fromUserID = update.callback_query.from.id; this.chatID = update.callback_query.message?.chat.id; this.chatType = update.callback_query.message?.chat.type; this.isForum = update.callback_query.message?.chat.is_forum; } else { console.error("Unknown update type"); } } } class APIClientBase { token; baseURL = ENV.TELEGRAM_API_DOMAIN; constructor(token, baseURL) { this.token = token; if (baseURL) { this.baseURL = baseURL.replace(/\/+$/, ""); } this.request = this.request.bind(this); this.requestJSON = this.requestJSON.bind(this); } uri(method) { return `${this.baseURL}/bot${this.token}/${method}`; } jsonRequest(method, params) { return fetch(this.uri(method), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) }); } formDataRequest(method, params) { const formData = new FormData(); for (const key in params) { const value = params[key]; if (value instanceof File) { formData.append(key, value, value.name); } else if (value instanceof Blob) { formData.append(key, value, "blob"); } else if (typeof value === "string") { formData.append(key, value); } else { formData.append(key, JSON.stringify(value)); } } return fetch(this.uri(method), { method: "POST", body: formData }); } request(method, params) { for (const key in params) { if (params[key] instanceof File || params[key] instanceof Blob) { return this.formDataRequest(method, params); } } return this.jsonRequest(method, params); } async requestJSON(method, params) { return this.request(method, params).then((res) => res.json()); } } function createTelegramBotAPI(token) { const client = new APIClientBase(token); return new Proxy(client, { get(target, prop, receiver) { if (prop in target) { return Reflect.get(target, prop, receiver); } return (...args) => { if (typeof prop === "string" && prop.endsWith("WithReturns")) { const method = prop.slice(0, -11); return Reflect.apply(target.requestJSON, target, [method, ...args]); } return Reflect.apply(target.request, target, [prop, ...args]); }; } }); } const TELEGRAM_AUTH_CHECKER = { shareModeGroup(chatType) { if (isGroupChat(chatType)) { if (!ENV.GROUP_CHAT_BOT_SHARE_MODE) { return null; } return ["administrator", "creator"]; } return null; } }; function isGroupChat(type) { return type === "group" || type === "supergroup"; } function checkMention(content, entities, botName, botId) { let isMention = false; for (const entity of entities) { const entityStr = content.slice(entity.offset, entity.offset + entity.length); switch (entity.type) { case "mention": if (entityStr === `@${botName}`) { isMention = true; content = content.slice(0, entity.offset) + content.slice(entity.offset + entity.length); } break; case "text_mention": if (`${entity.user?.id}` === `${botId}`) { isMention = true; content = content.slice(0, entity.offset) + content.slice(entity.offset + entity.length); } break; case "bot_command": if (entityStr.endsWith(`@${botName}`)) { isMention = true; const newEntityStr = entityStr.replace(`@${botName}`, ""); content = content.slice(0, entity.offset) + newEntityStr + content.slice(entity.offset + entity.length); } break; } } return { isMention, content }; } class GroupMention { handle = async (message, context) => { if (!isGroupChat(message.chat.type)) { return null; } const replyMe = `${message.reply_to_message?.from?.id}` === `${context.SHARE_CONTEXT.botId}`; if (replyMe) { return null; } let botName = context.SHARE_CONTEXT.botName; if (!botName) { const res = await createTelegramBotAPI(context.SHARE_CONTEXT.botToken).getMeWithReturns(); botName = res.result.username || null; context.SHARE_CONTEXT.botName = botName; } if (!botName) { throw new Error("Not set bot name"); } let isMention = false; if (message.text && message.entities) { const res = checkMention(message.text, message.entities, botName, context.SHARE_CONTEXT.botId); isMention = res.isMention; message.text = res.content.trim(); } if (message.caption && message.caption_entities) { const res = checkMention(message.caption, message.caption_entities, botName, context.SHARE_CONTEXT.botId); isMention = res.isMention || isMention; message.caption = res.content.trim(); } if (!isMention) { throw new Error("Not mention"); } return null; }; } async function loadChatRoleWithContext(chatId, speakerId, context) { const { groupAdminsKey } = context.SHARE_CONTEXT; if (!groupAdminsKey) { return null; } let groupAdmin = null; try { groupAdmin = JSON.parse(await ENV.DATABASE.get(groupAdminsKey)); } catch (e) { console.error(e); } if (groupAdmin === null || !Array.isArray(groupAdmin) || groupAdmin.length === 0) { const api = createTelegramBotAPI(context.SHARE_CONTEXT.botToken); const result = await api.getChatAdministratorsWithReturns({ chat_id: chatId }); if (result == null) { return null; } groupAdmin = result.result; await ENV.DATABASE.put( groupAdminsKey, JSON.stringify(groupAdmin), { expiration: Date.now() / 1e3 + 120 } ); } for (let i = 0; i < groupAdmin.length; i++) { const user = groupAdmin[i]; if (`${user.user?.id}` === `${speakerId}`) { return user.status; } } return "member"; } class MessageContext { chat_id; message_id = null; reply_to_message_id = null; parse_mode = null; allow_sending_without_reply = null; disable_web_page_preview = null; constructor(chatID) { this.chat_id = chatID; } static fromMessage(message) { const ctx = new MessageContext(message.chat.id); if (message.chat.type === "group" || message.chat.type === "supergroup") { ctx.reply_to_message_id = message.message_id; ctx.allow_sending_without_reply = true; } else { ctx.reply_to_message_id = null; } return ctx; } static fromCallbackQuery(callbackQuery) { const chat = callbackQuery.message?.chat; if (!chat) { throw new Error("Chat not found"); } const ctx = new MessageContext(chat.id); if (chat.type === "group" || chat.type === "supergroup") { ctx.reply_to_message_id = callbackQuery.message.message_id; ctx.allow_sending_without_reply = true; } else { ctx.reply_to_message_id = null; } return ctx; } } class MessageSender { api; context; constructor(token, context) { this.api = createTelegramBotAPI(token); this.context = context; this.sendRichText = this.sendRichText.bind(this); this.sendPlainText = this.sendPlainText.bind(this); this.sendPhoto = this.sendPhoto.bind(this); } static fromMessage(token, message) { return new MessageSender(token, MessageContext.fromMessage(message)); } static fromCallbackQuery(token, callbackQuery) { return new MessageSender(token, MessageContext.fromCallbackQuery(callbackQuery)); } static fromUpdate(token, update) { if (update.callback_query) { return MessageSender.fromCallbackQuery(token, update.callback_query); } if (update.message) { return MessageSender.fromMessage(token, update.message); } throw new Error("Invalid update"); } update(context) { if (!this.context) { this.context = context; return this; } for (const key in context) { this.context[key] = context[key]; } return this; } async sendMessage(message, context) { if (context?.message_id) { const params = { chat_id: context.chat_id, message_id: context.message_id, parse_mode: context.parse_mode || void 0, text: message }; if (context.disable_web_page_preview) { params.link_preview_options = { is_disabled: true }; } return this.api.editMessageText(params); } else { const params = { chat_id: context.chat_id, parse_mode: context.parse_mode || void 0, text: message }; if (context.reply_to_message_id) { params.reply_parameters = { message_id: context.reply_to_message_id, chat_id: context.chat_id, allow_sending_without_reply: context.allow_sending_without_reply || void 0 }; } if (context.disable_web_page_preview) { params.link_preview_options = { is_disabled: true }; } return this.api.sendMessage(params); } } renderMessage(parse_mode, message) { if (ENV.CUSTOM_MESSAGE_RENDER) { return ENV.CUSTOM_MESSAGE_RENDER(parse_mode, message); } return message; } async sendLongMessage(message, context) { const chatContext = { ...context }; const limit = 4096; if (message.length <= limit) { const resp = await this.sendMessage(this.renderMessage(context.parse_mode, message), chatContext); if (resp.status === 200) { return resp; } } chatContext.parse_mode = null; let lastMessageResponse = null; for (let i = 0; i < message.length; i += limit) { const msg = message.slice(i, Math.min(i + limit, message.length)); if (i > 0) { chatContext.message_id = null; } lastMessageResponse = await this.sendMessage(msg, chatContext); if (lastMessageResponse.status !== 200) { break; } } if (lastMessageResponse === null) { throw new Error("Send message failed"); } return lastMessageResponse; } sendRawMessage(message) { return this.api.sendMessage(message); } editRawMessage(message) { return this.api.editMessageText(message); } sendRichText(message, parseMode = ENV.DEFAULT_PARSE_MODE) { if (!this.context) { throw new Error("Message context not set"); } return this.sendLongMessage(message, { ...this.context, parse_mode: parseMode }); } sendPlainText(message) { if (!this.context) { throw new Error("Message context not set"); } return this.sendLongMessage(message, { ...this.context, parse_mode: null }); } sendPhoto(photo) { if (!this.context) { throw new Error("Message context not set"); } const params = { chat_id: this.context.chat_id, photo }; if (this.context.reply_to_message_id) { params.reply_parameters = { message_id: this.context.reply_to_message_id, chat_id: this.context.chat_id, allow_sending_without_reply: this.context.allow_sending_without_reply || void 0 }; } return this.api.sendPhoto(params); } } class Stream { response; controller; decoder; parser; constructor(response, controller, parser = null) { this.response = response; this.controller = controller; this.decoder = new SSEDecoder(); this.parser = parser || defaultSSEJsonParser; } async *iterMessages() { if (!this.response.body) { this.controller.abort(); throw new Error("Attempted to iterate over a response with no body"); } const lineDecoder = new LineDecoder(); const iter = this.response.body; for await (const chunk of iter) { for (const line of lineDecoder.decode(chunk)) { const sse = this.decoder.decode(line); if (sse) { yield sse; } } } for (const line of lineDecoder.flush()) { const sse = this.decoder.decode(line); if (sse) { yield sse; } } } async *[Symbol.asyncIterator]() { let done = false; try { for await (const sse of this.iterMessages()) { if (done) { continue; } if (!sse) { continue; } const { finish, data } = this.parser(sse); if (finish) { done = finish; continue; } if (data) { yield data; } } done = true; } catch (e) { if (e instanceof Error && e.name === "AbortError") { return; } throw e; } finally { if (!done) { this.controller.abort(); } } } } class SSEDecoder { event; data; constructor() { this.event = null; this.data = []; } decode(line) { if (line.endsWith("\r")) { line = line.substring(0, line.length - 1); } if (!line) { if (!this.event && !this.data.length) { return null; } const sse = { event: this.event, data: this.data.join("\n") }; this.event = null; this.data = []; return sse; } if (line.startsWith(":")) { return null; } let [fieldName, _, value] = this.partition(line, ":"); if (value.startsWith(" ")) { value = value.substring(1); } if (fieldName === "event") { this.event = value; } else if (fieldName === "data") { this.data.push(value); } return null; } partition(str, delimiter) { const index = str.indexOf(delimiter); if (index !== -1) { return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)]; } return [str, "", ""]; } } function defaultSSEJsonParser(sse) { if (sse.data?.startsWith("[DONE]")) { return { finish: true }; } if (sse.data) { try { return { data: JSON.parse(sse.data) }; } catch (e) { console.error(e, sse); } } return {}; } class LineDecoder { buffer; trailingCR; textDecoder; static NEWLINE_CHARS = new Set(["\n", "\r"]); static NEWLINE_REGEXP = /\r\n|[\n\r]/g; constructor() { this.buffer = []; this.trailingCR = false; } decode(chunk) { let text = this.decodeText(chunk); if (this.trailingCR) { text = `\r${text}`; this.trailingCR = false; } if (text.endsWith("\r")) { this.trailingCR = true; text = text.slice(0, -1); } if (!text) { return []; } const trailingNewline = LineDecoder.NEWLINE_CHARS.has(text[text.length - 1] || ""); let lines = text.split(LineDecoder.NEWLINE_REGEXP); if (lines.length === 1 && !trailingNewline) { this.buffer.push(lines[0]); return []; } if (this.buffer.length > 0) { lines = [this.buffer.join("") + lines[0], ...lines.slice(1)]; this.buffer = []; } if (!trailingNewline) { this.buffer = [lines.pop() || ""]; } return lines; } decodeText(bytes) { if (bytes == null) { return ""; } if (typeof bytes === "string") { return bytes; } if (typeof Buffer !== "undefined") { if (bytes instanceof Buffer) { return bytes.toString(); } if (bytes instanceof Uint8Array) { return Buffer.from(bytes).toString(); } throw new Error(`Unexpected: received non-Uint8Array (${bytes.constructor.name}) stream chunk in an environment with a global "Buffer" defined, which this library assumes to be Node. Please report this error.`); } if (typeof TextDecoder !== "undefined") { if (bytes instanceof Uint8Array || bytes instanceof ArrayBuffer) { if (!this.textDecoder) { this.textDecoder = new TextDecoder("utf8"); } return this.textDecoder.decode(bytes, { stream: true }); } throw new Error(`Unexpected: received non-Uint8Array/ArrayBuffer in a web platform. Please report this error.`); } throw new Error("Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error."); } flush() { if (!this.buffer.length && !this.trailingCR) { return []; } const lines = [this.buffer.join("")]; this.buffer = []; this.trailingCR = false; return lines; } } function fixOpenAICompatibleOptions(options) { options = options || {}; options.streamBuilder = options.streamBuilder || function(r, c) { return new Stream(r, c); }; options.contentExtractor = options.contentExtractor || function(d) { return d?.choices?.at(0)?.delta?.content; }; options.fullContentExtractor = options.fullContentExtractor || function(d) { return d.choices?.at(0)?.message.content; }; options.errorExtractor = options.errorExtractor || function(d) { return d.error?.message; }; return options; } function isJsonResponse(resp) { const contentType = resp.headers.get("content-type"); return contentType?.toLowerCase().includes("application/json") ?? false; } function isEventStreamResponse(resp) { const types = ["application/stream+json", "text/event-stream"]; const content = resp.headers.get("content-type")?.toLowerCase() || ""; for (const type of types) { if (content.includes(type)) { return true; } } return false; } async function streamHandler(stream, contentExtractor, onStream) { let contentFull = ""; let lengthDelta = 0; let updateStep = 50; let lastUpdateTime = Date.now(); try { for await (const part of stream) { const textPart = contentExtractor(part); if (!textPart) { continue; } lengthDelta += textPart.length; contentFull = contentFull + textPart; if (lengthDelta > updateStep) { if (ENV.TELEGRAM_MIN_STREAM_INTERVAL > 0) { const delta = Date.now() - lastUpdateTime; if (delta < ENV.TELEGRAM_MIN_STREAM_INTERVAL) { continue; } lastUpdateTime = Date.now(); } lengthDelta = 0; updateStep += 20; await onStream?.(`${contentFull} ...`); } } } catch (e) { contentFull += ` Error: ${e.message}`; } return contentFull; } async function mapResponseToAnswer(resp, controller, options, onStream) { options = fixOpenAICompatibleOptions(options || null); if (onStream && resp.ok && isEventStreamResponse(resp)) { const stream = options.streamBuilder?.(resp, controller || new AbortController()); if (!stream) { throw new Error("Stream builder error"); } return streamHandler(stream, options.contentExtractor, onStream); } if (!isJsonResponse(resp)) { throw new Error(resp.statusText); } const result = await resp.json(); if (!result) { throw new Error("Empty response"); } if (options.errorExtractor?.(result)) { throw new Error(options.errorExtractor?.(result) || "Unknown error"); } return options.fullContentExtractor?.(result) || ""; } async function requestChatCompletions(url, header, body, onStream, options) { const controller = new AbortController(); const { signal } = controller; let timeoutID = null; if (ENV.CHAT_COMPLETE_API_TIMEOUT > 0) { timeoutID = setTimeout(() => controller.abort(), ENV.CHAT_COMPLETE_API_TIMEOUT); } const resp = await fetch(url, { method: "POST", headers: header, body: JSON.stringify(body), signal }); if (timeoutID) { clearTimeout(timeoutID); } return await mapResponseToAnswer(resp, controller, options, onStream); } function extractTextContent(history) { if (typeof history.content === "string") { return history.content; } if (Array.isArray(history.content)) { return history.content.map((item) => { if (item.type === "text") { return item.text; } return ""; }).join(""); } return ""; } function extractImageContent(imageData) { if (imageData instanceof URL) { return { url: imageData.href }; } if (typeof imageData === "string") { if (imageData.startsWith("http")) { return { url: imageData }; } else { return { base64: imageData }; } } if (typeof Buffer !== "undefined") { if (imageData instanceof Uint8Array) { return { base64: Buffer.from(imageData).toString("base64") }; } if (Buffer.isBuffer(imageData)) { return { base64: Buffer.from(imageData).toString("base64") }; } } return {}; } async function convertStringToResponseMessages(input) { const text = typeof input === "string" ? input : await input; return { text, responses: [{ role: "assistant", content: text }] }; } async function loadModelsList(raw, remoteLoader) { if (!raw) { return []; } if (raw.startsWith("[") && raw.endsWith("]")) { try { return JSON.parse(raw); } catch (e) { console.error(e); return []; } } if (raw.startsWith("http") && remoteLoader) { return await remoteLoader(raw); } return [raw]; } function bearerHeader(token, stream) { const res = { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }; if (stream !== void 0) { res.Accept = stream ? "text/event-stream" : "application/json"; } return res; } function getAgentUserConfigFieldName(fieldName) { return fieldName; } class Cache { maxItems; maxAge; cache; constructor() { this.maxItems = 10; this.maxAge = 1e3 * 60 * 60; this.cache = {}; this.set = this.set.bind(this); this.get = this.get.bind(this); } set(key, value) { this.trim(); this.cache[key] = { value, time: Date.now() }; } get(key) { this.trim(); return this.cache[key]?.value; } trim() { let keys = Object.keys(this.cache); for (const key of keys) { if (Date.now() - this.cache[key].time > this.maxAge) { delete this.cache[key]; } } keys = Object.keys(this.cache); if (keys.length > this.maxItems) { keys.sort((a, b) => this.cache[a].time - this.cache[b].time); for (let i = 0; i < keys.length - this.maxItems; i++) { delete this.cache[keys[i]]; } } } } const IMAGE_CACHE = new Cache(); async function fetchImage(url) { const cache = IMAGE_CACHE.get(url); if (cache) { return cache; } return fetch(url).then((resp) => resp.blob()).then((blob) => { IMAGE_CACHE.set(url, blob); return blob; }); } async function urlToBase64String(url) { if (typeof Buffer !== "undefined") { return fetchImage(url).then((blob) => blob.arrayBuffer()).then((buffer) => Buffer.from(buffer).toString("base64")); } else { return fetchImage(url).then((blob) => blob.arrayBuffer()).then((buffer) => btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)))); } } function getImageFormatFromBase64(base64String) { const firstChar = base64String.charAt(0); switch (firstChar) { case "/": return "jpeg"; case "i": return "png"; case "U": return "webp"; default: throw new Error("Unsupported image format"); } } async function imageToBase64String(url) { const base64String = await urlToBase64String(url); const format = getImageFormatFromBase64(base64String); return { data: base64String, format: `image/${format}` }; } function renderBase64DataURI(params) { return `data:${params.format};base64,${params.data}`; } var ImageSupportFormat = ((ImageSupportFormat2) => { ImageSupportFormat2["URL"] = "url"; ImageSupportFormat2["BASE64"] = "base64"; return ImageSupportFormat2; })(ImageSupportFormat || {}); async function renderOpenAIMessage(item, supportImage) { const res = { role: item.role, content: item.content }; if (Array.isArray(item.content)) { const contents = []; for (const content of item.content) { switch (content.type) { case "text": contents.push({ type: "text", text: content.text }); break; case "image": if (supportImage) { const isSupportURL = supportImage.includes("url" ); const isSupportBase64 = supportImage.includes("base64" ); const data = extractImageContent(content.image); if (data.url) { if (ENV.TELEGRAM_IMAGE_TRANSFER_MODE === "base64" && isSupportBase64) { contents.push(await imageToBase64String(data.url).then((data2) => { return { type: "image_url", image_url: { url: renderBase64DataURI(data2) } }; })); } else if (isSupportURL) { contents.push({ type: "image_url", image_url: { url: data.url } }); } } else if (data.base64 && isSupportBase64) { contents.push({ type: "image_base64", image_base64: { base64: data.base64 } }); } } break; } } res.content = contents; } return res; } async function renderOpenAIMessages(prompt, items, supportImage) { const messages = await Promise.all(items.map((r) => renderOpenAIMessage(r, supportImage))); if (prompt) { if (messages.length > 0 && messages[0].role === "system") { messages.shift(); } messages.unshift({ role: "system", content: prompt }); } return messages; } function loadOpenAIModelList(list, base, headers) { if (list === "") { list = `${base}/models`; } return loadModelsList(list, async (url) => { const data = await fetch(url, { headers }).then((res) => res.json()); return data.data?.map((model) => model.id) || []; }); } function agentConfigFieldGetter(fields) { return (ctx) => ({ base: ctx[fields.base], key: ctx[fields.key] || null, model: ctx[fields.model], modelsList: ctx[fields.modelsList], extraParams: ctx[fields.extraParams] || void 0 }); } function createOpenAIRequest(builder, options, hooks) { return async (params, context, onStream) => { const { url, header, body } = await builder(params, context, onStream !== null); if (onStream && hooks?.stream) { const onStreamOriginal = onStream; onStream = (text) => { return onStreamOriginal(hooks.stream(text)); }; } let output = await requestChatCompletions(url, header, body, onStream, options || null); if (hooks?.finish) { output = hooks.finish(output); } return convertStringToResponseMessages(output); }; } function createAgentEnable(valueGetter) { return (ctx) => !!valueGetter(ctx).key; } function createAgentModel(valueGetter) { return (ctx) => valueGetter(ctx).model; } function createAgentModelList(valueGetter) { return (ctx) => { const { base, key, modelsList } = valueGetter(ctx); return loadOpenAIModelList(modelsList, base, bearerHeader(key)); }; } function defaultOpenAIRequestBuilder(valueGetter, completionsEndpoint = "/chat/completions", supportImage = ["url" ]) { return async (params, context, stream) => { const { prompt, messages } = params; const { base, key, model, extraParams } = valueGetter(context); const url = `${base}${completionsEndpoint}`; const header = bearerHeader(key, stream); const body = { ...extraParams || {}, model, stream, messages: await renderOpenAIMessages(prompt, messages, supportImage) }; return { url, header, body }; }; } class OpenAICompatibilityAgent { name; modelKey; enable; model; modelList; request; constructor(name, fields, options, hooks) { this.name = name; this.modelKey = getAgentUserConfigFieldName(fields.model); const valueGetter = agentConfigFieldGetter(fields); this.enable = createAgentEnable(valueGetter); this.model = createAgentModel(valueGetter); this.modelList = createAgentModelList(valueGetter); this.request = createOpenAIRequest(defaultOpenAIRequestBuilder(valueGetter), options, hooks); } } class DeepSeek extends OpenAICompatibilityAgent { constructor() { super("deepseek", { base: "DEEPSEEK_API_BASE", key: "DEEPSEEK_API_KEY", model: "DEEPSEEK_CHAT_MODEL", modelsList: "DEEPSEEK_CHAT_MODELS_LIST", extraParams: "DEEPSEEK_CHAT_EXTRA_PARAMS" }); } } class Groq extends OpenAICompatibilityAgent { constructor() { super("groq", { base: "GROQ_API_BASE", key: "GROQ_API_KEY", model: "GROQ_CHAT_MODEL", modelsList: "GROQ_CHAT_MODELS_LIST", extraParams: "GROQ_CHAT_EXTRA_PARAMS" }); } } class Mistral extends OpenAICompatibilityAgent { constructor() { super("mistral", { base: "MISTRAL_API_BASE", key: "MISTRAL_API_KEY", model: "MISTRAL_CHAT_MODEL", modelsList: "MISTRAL_CHAT_MODELS_LIST", extraParams: "MISTRAL_CHAT_EXTRA_PARAMS" }); } } class XAi extends OpenAICompatibilityAgent { constructor() { super("xai", { base: "XAI_API_BASE", key: "XAI_API_KEY", model: "XAI_CHAT_MODEL", modelsList: "XAI_CHAT_MODELS_LIST", extraParams: "XAI_CHAT_EXTRA_PARAMS" }); } } function anthropicHeader(context) { return { "x-api-key": context.ANTHROPIC_API_KEY || "", "anthropic-version": "2023-06-01", "content-type": "application/json" }; } class Anthropic { name = "anthropic"; modelKey = getAgentUserConfigFieldName("ANTHROPIC_CHAT_MODEL"); enable = (ctx) => !!ctx.ANTHROPIC_API_KEY; model = (ctx) => ctx.ANTHROPIC_CHAT_MODEL; modelList = (ctx) => loadOpenAIModelList(ctx.ANTHROPIC_CHAT_MODELS_LIST, ctx.ANTHROPIC_API_BASE, anthropicHeader(ctx)); static render = async (item) => { const res = { role: item.role, content: item.content }; if (item.role === "system") { return null; } if (Array.isArray(item.content)) { const contents = []; for (const content of item.content) { switch (content.type) { case "text": contents.push({ type: "text", text: content.text }); break; case "image": { const data = extractImageContent(content.image); if (data.url) { contents.push(await imageToBase64String(data.url).then(({ format, data: data2 }) => { return { type: "image", source: { type: "base64", media_type: format, data: data2 } }; })); } else if (data.base64) { contents.push({ type: "image", source: { type: "base64", media_type: "image/jpeg", data: data.base64 } }); } break; } } } res.content = contents; } return res; }; static parser(sse) { switch (sse.event) { case "content_block_delta": try { return { data: JSON.parse(sse.data || "") }; } catch (e) { console.error(e, sse.data); return {}; } case "message_start": case "content_block_start": case "content_block_stop": return {}; case "message_stop": return { finish: true }; default: return {}; } } request = async (params, context, onStream) => { const { prompt, messages } = params; const url = `${context.ANTHROPIC_API_BASE}/messages`; const header = anthropicHeader(context); if (messages.length > 0 && messages[0].role === "system") { messages.shift(); } const body = { ...context.ANTHROPIC_CHAT_EXTRA_PARAMS || {}, system: prompt, model: context.ANTHROPIC_CHAT_MODEL, messages: (await Promise.all(messages.map((item) => Anthropic.render(item)))).filter((i) => i !== null), stream: onStream != null, max_tokens: ENV.MAX_TOKEN_LENGTH > 0 ? ENV.MAX_TOKEN_LENGTH : 2048 }; if (!body.system) { delete body.system; } const options = {}; options.streamBuilder = function(r, c) { return new Stream(r, c, Anthropic.parser); }; options.contentExtractor = function(data) { return data?.delta?.text; }; options.fullContentExtractor = function(data) { return data?.content?.at(0).text; }; options.errorExtractor = function(data) { return data?.error?.message; }; return convertStringToResponseMessages(requestChatCompletions(url, header, body, onStream, options)); }; } function azureHeader(context) { return { "Content-Type": "application/json", "api-key": context.AZURE_API_KEY || "" }; } class AzureChatAI { name = "azure"; modelKey = getAgentUserConfigFieldName("AZURE_CHAT_MODEL"); enable = (ctx) => !!(ctx.AZURE_API_KEY && ctx.AZURE_RESOURCE_NAME); model = (ctx) => ctx.AZURE_CHAT_MODEL; request = async (params, context, onStream) => { const { prompt, messages } = params; const url = `https://${context.AZURE_RESOURCE_NAME}.openai.azure.com/openai/deployments/${context.AZURE_CHAT_MODEL}/chat/completions?api-version=${context.AZURE_API_VERSION}`; const header = azureHeader(context); const body = { ...context.AZURE_CHAT_EXTRA_PARAMS || {}, messages: await renderOpenAIMessages(prompt, messages, [ImageSupportFormat.URL, ImageSupportFormat.BASE64]), stream: onStream != null }; return convertStringToResponseMessages(requestChatCompletions(url, header, body, onStream, null)); }; modelList = async (context) => { if (context.AZURE_CHAT_MODELS_LIST === "") { context.AZURE_CHAT_MODELS_LIST = `https://${context.AZURE_RESOURCE_NAME}.openai.azure.com/openai/models?api-version=${context.AZURE_API_VERSION}`; } return loadModelsList(context.AZURE_CHAT_MODELS_LIST, async (url) => { const data = await fetch(url, { headers: azureHeader(context) }).then((res) => res.json()); return data.data?.map((model) => model.id) || []; }); }; } class AzureImageAI { name = "azure"; modelKey = getAgentUserConfigFieldName("AZURE_IMAGE_MODEL"); enable = (ctx) => !!(ctx.AZURE_API_KEY && ctx.AZURE_RESOURCE_NAME); model = (ctx) => ctx.AZURE_IMAGE_MODEL; modelList = (ctx) => Promise.resolve([ctx.AZURE_IMAGE_MODEL]); request = async (prompt, context) => { const url = `https://${context.AZURE_RESOURCE_NAME}.openai.azure.com/openai/deployments/${context.AZURE_IMAGE_MODEL}/images/generations?api-version=${context.AZURE_API_VERSION}`; const header = azureHeader(context); const body = { prompt, n: 1, size: context.DALL_E_IMAGE_SIZE, style: context.DALL_E_IMAGE_STYLE, quality: context.DALL_E_IMAGE_QUALITY }; const validSize = ["1792x1024", "1024x1024", "1024x1792"]; if (!validSize.includes(body.size)) { body.size = "1024x1024"; } const resp = await fetch(url, { method: "POST", headers: header, body: JSON.stringify(body) }).then((res) => res.json()); if (resp.error?.message) { throw new Error(resp.error.message); } return resp?.data?.at(0)?.url; }; } class Cohere { name = "cohere"; modelKey = getAgentUserConfigFieldName("COHERE_CHAT_MODEL"); enable = (ctx) => !!ctx.COHERE_API_KEY; model = (ctx) => ctx.COHERE_CHAT_MODEL; request = async (params, context, onStream) => { const { prompt, messages } = params; const url = `${context.COHERE_API_BASE}/chat`; const header = bearerHeader(context.COHERE_API_KEY, onStream !== null); const body = { ...context.COHERE_CHAT_EXTRA_PARAMS || {}, messages: await renderOpenAIMessages(prompt, messages, null), model: context.COHERE_CHAT_MODEL, stream: onStream != null }; const options = {}; options.contentExtractor = function(data) { return data?.delta?.message?.content?.text; }; options.fullContentExtractor = function(data) { return data?.messages?.at(0)?.content; }; options.errorExtractor = function(data) { return data?.message; }; return convertStringToResponseMessages(requestChatCompletions(url, header, body, onStream, options)); }; modelList = async (context) => { if (context.COHERE_CHAT_MODELS_LIST === "") { const { protocol, host } = new URL(context.COHERE_API_BASE); context.COHERE_CHAT_MODELS_LIST = `${protocol}://${host}/v2/models`; } return loadModelsList(context.COHERE_CHAT_MODELS_LIST, async (url) => { const data = await fetch(url, { headers: bearerHeader(context.COHERE_API_KEY) }).then((res) => res.json()); return data.models?.filter((model) => model.endpoints?.includes("chat")).map((model) => model.name) || []; }); }; } class Gemini { name = "gemini"; modelKey = getAgentUserConfigFieldName("GOOGLE_CHAT_MODEL"); fieldGetter = agentConfigFieldGetter({ base: "GOOGLE_API_BASE", key: "GOOGLE_API_KEY", model: "GOOGLE_CHAT_MODEL", modelsList: "GOOGLE_CHAT_MODELS_LIST", extraParams: "GOOGLE_CHAT_EXTRA_PARAMS" }); enable = createAgentEnable(this.fieldGetter); model = createAgentModel(this.fieldGetter); request = createOpenAIRequest(defaultOpenAIRequestBuilder(this.fieldGetter, "/openai/chat/completions", [ImageSupportFormat.BASE64])); modelList = async (context) => { if (context.GOOGLE_CHAT_MODELS_LIST === "") { context.GOOGLE_CHAT_MODELS_LIST = `${context.GOOGLE_API_BASE}/models`; } return loadModelsList(context.GOOGLE_CHAT_MODELS_LIST, async (url) => { const data = await fetch(`${url}?key=${context.GOOGLE_API_KEY}`).then((r) => r.json()); return data?.models?.filter((model) => model.supportedGenerationMethods?.includes("generateContent")).map((model) => model.name.split("/").pop()) ?? []; }); }; } function openAIApiKey(context) { const length = context.OPENAI_API_KEY.length; return context.OPENAI_API_KEY[Math.floor(Math.random() * length)]; } class OpenAI { name = "openai"; modelKey = getAgentUserConfigFieldName("OPENAI_CHAT_MODEL"); enable = (ctx) => ctx.OPENAI_API_KEY.length > 0; model = (ctx) => ctx.OPENAI_CHAT_MODEL; modelList = (ctx) => loadOpenAIModelList(ctx.OPENAI_CHAT_MODELS_LIST, ctx.OPENAI_API_BASE, bearerHeader(openAIApiKey(ctx))); request = async (params, context, onStream) => { const { prompt, messages } = params; const url = `${context.OPENAI_API_BASE}/chat/completions`; const header = bearerHeader(openAIApiKey(context)); const body = { ...context.OPENAI_API_EXTRA_PARAMS || {}, model: context.OPENAI_CHAT_MODEL, messages: await renderOpenAIMessages(prompt, messages, [ImageSupportFormat.URL, ImageSupportFormat.BASE64]), stream: onStream != null }; return convertStringToResponseMessages(requestChatCompletions(url, header, body, onStream, null)); }; } class Dalle { name = "openai"; modelKey = getAgentUserConfigFieldName("DALL_E_MODEL"); enable = (ctx) => ctx.OPENAI_API_KEY.length > 0; model = (ctx) => ctx.DALL_E_MODEL; modelList = (ctx) => loadModelsList(ctx.DALL_E_MODELS_LIST); request = async (prompt, context) => { const url = `${context.OPENAI_API_BASE}/images/generations`; const header = bearerHeader(openAIApiKey(context)); const body = { prompt, n: 1, size: context.DALL_E_IMAGE_SIZE, model: context.DALL_E_MODEL }; if (body.model === "dall-e-3") { body.quality = context.DALL_E_IMAGE_QUALITY; body.style = context.DALL_E_IMAGE_STYLE; } const resp = await fetch(url, { method: "POST", headers: header, body: JSON.stringify(body) }).then((res) => res.json()); if (resp.error?.message) { throw new Error(resp.error.message); } return resp?.data?.at(0)?.url; }; } function isWorkerAIEnable(context) { if (ENV.AI_BINDING) { return true; } return !!(context.CLOUDFLARE_ACCOUNT_ID && context.CLOUDFLARE_TOKEN); } function loadWorkersModelList(task, loader) { return async (context) => { let uri = loader(context); if (uri === "") { const id = context.CLOUDFLARE_ACCOUNT_ID; const taskEncoded = encodeURIComponent(task); uri = `https://api.cloudflare.com/client/v4/accounts/${id}/ai/models/search?task=${taskEncoded}`; } return loadModelsList(uri, async (url) => { const header = { Authorization: `Bearer ${context.CLOUDFLARE_TOKEN}` }; const data = await fetch(url, { headers: header }).then((res) => res.json()); return data.result?.map((model) => model.name) || []; }); }; } class WorkersChat { name = "workers"; modelKey = getAgentUserConfigFieldName("WORKERS_CHAT_MODEL"); enable = isWorkerAIEnable; model = (ctx) => ctx.WORKERS_CHAT_MODEL; modelList = loadWorkersModelList("Text Generation", (ctx) => ctx.WORKERS_CHAT_MODELS_LIST); request = async (params, context, onStream) => { const { prompt, messages } = params; const model = context.WORKERS_CHAT_MODEL; const body = { ...context.WORKERS_CHAT_EXTRA_PARAMS || {}, messages: await renderOpenAIMessages(prompt, messages, null), stream: onStream !== null }; const options = {}; options.contentExtractor = function(data) { return data?.response; }; options.fullContentExtractor = function(data) { return data?.result?.response; }; options.errorExtractor = function(data) { return data?.errors?.at(0)?.message; }; if (ENV.AI_BINDING) { const answer = await ENV.AI_BINDING.run(model, body); const response = WorkersChat.outputToResponse(answer, onStream !== null); return convertStringToResponseMessages(mapResponseToAnswer(response, new AbortController(), options, onStream)); } else if (context.CLOUDFLARE_ACCOUNT_ID && context.CLOUDFLARE_TOKEN) { const id = context.CLOUDFLARE_ACCOUNT_ID; const token = context.CLOUDFLARE_TOKEN; const url = `https://api.cloudflare.com/client/v4/accounts/${id}/ai/run/${model}`; const header = bearerHeader(token, onStream !== null); return convertStringToResponseMessages(requestChatCompletions(url, header, body, onStream, options)); } else { throw new Error("Cloudflare account ID and token are required"); } }; static outputToResponse(output, stream) { if (stream && output instanceof ReadableStream) { return new Response(output, { headers: { "content-type": "text/event-stream" } }); } else { return Response.json({ result: output }); } } } class WorkersImage { name = "workers"; modelKey = getAgentUserConfigFieldName("WORKERS_IMAGE_MODEL"); enable = isWorkerAIEnable; model = (ctx) => ctx.WORKERS_IMAGE_MODEL; modelList = loadWorkersModelList("Text-to-Image", (ctx) => ctx.WORKERS_IMAGE_MODELS_LIST); request = async (prompt, context) => { if (ENV.AI_BINDING) { const answer = await ENV.AI_BINDING.run(context.WORKERS_IMAGE_MODEL, { prompt }); const raw = WorkersImage.outputToResponse(answer); return await WorkersImage.responseToImage(raw); } else if (context.CLOUDFLARE_ACCOUNT_ID && context.CLOUDFLARE_TOKEN) { const id = context.CLOUDFLARE_ACCOUNT_ID; const token = context.CLOUDFLARE_TOKEN; const raw = await WorkersImage.fetch(context.WORKERS_IMAGE_MODEL, { prompt }, id, token); return await WorkersImage.responseToImage(raw); } else { throw new Error("Cloudflare account ID and token are required"); } }; static outputToResponse(output) { if (output instanceof ReadableStream) { return new Response(output, { headers: { "content-type": "image/jpg" } }); } else { return Response.json({ result: output }); } } static async responseToImage(output) { if (isJsonResponse(output)) { const { result } = await output.json(); const image = result?.image; if (typeof image !== "string") { throw new TypeError("Invalid image response"); } return WorkersImage.base64StringToBlob(image); } return await output.blob(); } static async base64StringToBlob(base64String) { if (typeof Buffer !== "undefined") { const buffer = Buffer.from(base64String, "base64"); return new Blob([buffer], { type: "image/png" }); } else { const uint8Array = Uint8Array.from(atob(base64String), (c) => c.charCodeAt(0)); return new Blob([uint8Array], { type: "image/png" }); } } static async fetch(model, body, id, token) { return await fetch( `https://api.cloudflare.com/client/v4/accounts/${id}/ai/run/${model}`, { headers: { Authorization: `Bearer ${token}` }, method: "POST", body: JSON.stringify(body) } ); } } const CHAT_AGENTS = [ new OpenAI(), new Anthropic(), new AzureChatAI(), new WorkersChat(), new Cohere(), new Gemini(), new Mistral(), new DeepSeek(), new Groq(), new XAi() ]; function loadChatLLM(context) { for (const llm of CHAT_AGENTS) { if (llm.name === context.AI_PROVIDER) { return llm; } } for (const llm of CHAT_AGENTS) { if (llm.enable(context)) { return llm; } } return null; } const IMAGE_AGENTS = [ new AzureImageAI(), new Dalle(), new WorkersImage() ]; function loadImageGen(context) { for (const imgGen of IMAGE_AGENTS) { if (imgGen.name === context.AI_IMAGE_PROVIDER) { return imgGen; } } for (const imgGen of IMAGE_AGENTS) { if (imgGen.enable(context)) { return imgGen; } } return null; } function tokensCounter() { return (text) => { return text.length; }; } async function loadHistory(key) { let history = []; try { history = JSON.parse(await ENV.DATABASE.get(key)); } catch (e) { console.error(e); } if (!history || !Array.isArray(history)) { history = []; } const counter = tokensCounter(); const trimHistory = (list, initLength, maxLength, maxToken) => { if (maxLength >= 0 && list.length > maxLength) { list = list.splice(list.length - maxLength); } if (maxToken > 0) { let tokenLength = initLength; for (let i = list.length - 1; i >= 0; i--) { const historyItem = list[i]; let length = 0; if (historyItem.content) { length = counter(extractTextContent(historyItem)); } else { historyItem.content = ""; } tokenLength += length; if (tokenLength > maxToken) { list = list.splice(i + 1); break; } } } return list; }; if (ENV.AUTO_TRIM_HISTORY && ENV.MAX_HISTORY_LENGTH > 0) { history = trimHistory(history, 0, ENV.MAX_HISTORY_LENGTH, ENV.MAX_TOKEN_LENGTH); } return history; } async function requestCompletionsFromLLM(params, context, agent, modifier, onStream) { const historyDisable = ENV.AUTO_TRIM_HISTORY && ENV.MAX_HISTORY_LENGTH <= 0; const historyKey = context.SHARE_CONTEXT.chatHistoryKey; if (!historyKey) { throw new Error("History key not found"); } let history = await loadHistory(historyKey); if (modifier) { const modifierData = modifier(history, params || null); history = modifierData.history; params = modifierData.message; } if (!params) { throw new Error("Message is empty"); } const llmParams = { prompt: context.USER_CONFIG.SYSTEM_INIT_MESSAGE || void 0, messages: [...history, params] }; const { text, responses } = await agent.request(llmParams, context.USER_CONFIG, onStream); if (!historyDisable) { const editParams = { ...params }; if (ENV.HISTORY_IMAGE_PLACEHOLDER) { if (Array.isArray(editParams.content)) { const imageCount = editParams.content.filter((i) => i.type === "image").length; const textContent = editParams.content.findLast((i) => i.type === "text"); if (textContent) { editParams.content = editParams.content.filter((i) => i.type !== "image"); textContent.text = textContent.text + ` ${ENV.HISTORY_IMAGE_PLACEHOLDER}`.repeat(imageCount); } } } await ENV.DATABASE.put(historyKey, JSON.stringify([...history, editParams, ...responses])).catch(console.error); } return text; } class AgentListCallbackQueryHandler { prefix; changeAgentPrefix; agentLoader; needAuth = TELEGRAM_AUTH_CHECKER.shareModeGroup; constructor(prefix, changeAgentPrefix, agentLoader) { this.prefix = prefix; this.changeAgentPrefix = changeAgentPrefix; this.agentLoader = agentLoader; this.createKeyboard = this.createKeyboard.bind(this); } static Chat() { return new AgentListCallbackQueryHandler("al:", "ca:", () => { return CHAT_AGENTS.filter((agent) => agent.enable(ENV.USER_CONFIG)).map((agent) => agent.name); }); } static Image() { return new AgentListCallbackQueryHandler("ial:", "ica:", () => { return IMAGE_AGENTS.filter((agent) => agent.enable(ENV.USER_CONFIG)).map((agent) => agent.name); }); } handle = async (query, data, context) => { const names = this.agentLoader(); const sender = MessageSender.fromCallbackQuery(context.SHARE_CONTEXT.botToken, query); const params = { chat_id: query.message?.chat.id || 0, message_id: query.message?.message_id || 0, text: ENV.I18N.callback_query.select_provider, reply_markup: { inline_keyboard: this.createKeyboard(names) } }; return sender.editRawMessage(params); }; createKeyboard(names) { const keyboards = []; for (let i = 0; i < names.length; i += 2) { const row = []; for (let j = 0; j < 2; j++) { const index = i + j; if (index >= names.length) { break; } row.push({ text: names[index], callback_data: `${this.changeAgentPrefix}${JSON.stringify([names[index], 0])}` }); } keyboards.push(row); } return keyboards; } } function changeChatAgentType(conf, agent) { return { ...conf, AI_PROVIDER: agent }; } function changeImageAgentType(conf, agent) { return { ...conf, AI_IMAGE_PROVIDER: agent }; } function loadAgentContext(query, data, context, prefix, agentLoader, changeAgentType) { if (!query.message) { throw new Error("no message"); } const sender = MessageSender.fromCallbackQuery(context.SHARE_CONTEXT.botToken, query); const params = JSON.parse(data.substring(prefix.length)); const agent = Array.isArray(params) ? params.at(0) : null; if (!agent) { throw new Error(`agent not found: ${agent}`); } const conf = changeAgentType(ENV.USER_CONFIG, agent); const theAgent = agentLoader(conf); if (!theAgent?.modelKey) { throw new Error(`modelKey not found: ${agent}`); } return { sender, params, agent: theAgent, conf }; } class ModelListCallbackQueryHandler { prefix; agentListPrefix; changeModelPrefix; agentLoader; changeAgentType; needAuth = TELEGRAM_AUTH_CHECKER.shareModeGroup; constructor(prefix, agentListPrefix, changeModelPrefix, agentLoader, changeAgentType) { this.prefix = prefix; this.agentListPrefix = agentListPrefix; this.changeModelPrefix = changeModelPrefix; this.agentLoader = agentLoader; this.changeAgentType = changeAgentType; this.createKeyboard = this.createKeyboard.bind(this); } static Chat() { return new ModelListCallbackQueryHandler("ca:", "al:", "cm:", loadChatLLM, changeChatAgentType); } static Image() { return new ModelListCallbackQueryHandler("ica:", "ial:", "icm:", loadImageGen, changeImageAgentType); } async handle(query, data, context) { const { sender, params, agent: theAgent, conf } = loadAgentContext(query, data, context, this.prefix, this.agentLoader, this.changeAgentType); const [agent, page] = params; const models = await theAgent.modelList(conf); const message = { chat_id: query.message?.chat.id || 0, message_id: query.message?.message_id || 0, text: `${agent} | ${ENV.I18N.callback_query.select_model}`, reply_markup: { inline_keyboard: await this.createKeyboard(models, agent, page) } }; return sender.editRawMessage(message); } async createKeyboard(models, agent, page) { const keyboard = []; const maxRow = 10; const maxCol = Math.max(1, Math.min(5, ENV.MODEL_LIST_COLUMNS)); const maxPage = Math.ceil(models.length / maxRow / maxCol); let currentRow = []; for (let i = page * maxRow * maxCol; i < models.length; i++) { currentRow.push({ text: models[i], callback_data: `${this.changeModelPrefix}${JSON.stringify([agent, models[i]])}` }); if (i % maxCol === 0) { keyboard.push(currentRow); currentRow = []; } if (keyboard.length >= maxRow) { break; } } if (currentRow.length > 0) { keyboard.push(currentRow); currentRow = []; } keyboard.push([ { text: "<", callback_data: `${this.prefix}${JSON.stringify([agent, Math.max(page - 1, 0)])}` }, { text: `${page + 1}/${maxPage}`, callback_data: `${this.prefix}${JSON.stringify([agent, page])}` }, { text: ">", callback_data: `${this.prefix}${JSON.stringify([agent, Math.min(page + 1, maxPage - 1)])}` }, { text: "⇤", callback_data: this.agentListPrefix } ]); if (models.length > (page + 1) * maxRow * maxCol) { currentRow.push(); } keyboard.push(currentRow); return keyboard; } } function changeChatAgentModel(agent, modelKey, model) { return { AI_PROVIDER: agent, [modelKey]: model }; } function changeImageAgentModel(agent, modelKey, model) { return { AI_IMAGE_PROVIDER: agent, [modelKey]: model }; } class ModelChangeCallbackQueryHandler { prefix; agentLoader; changeAgentType; createAgentChange; needAuth = TELEGRAM_AUTH_CHECKER.shareModeGroup; constructor(prefix, agentLoader, changeAgentType, createAgentChange) { this.prefix = prefix; this.agentLoader = agentLoader; this.changeAgentType = changeAgentType; this.createAgentChange = createAgentChange; } static Chat() { return new ModelChangeCallbackQueryHandler("cm:", loadChatLLM, changeChatAgentType, changeChatAgentModel); } static Image() { return new ModelChangeCallbackQueryHandler("icm:", loadImageGen, changeImageAgentType, changeImageAgentModel); } async handle(query, data, context) { const { sender, params, agent: theAgent } = loadAgentContext(query, data, context, this.prefix, this.agentLoader, this.changeAgentType); const [agent, model] = params; await context.execChangeAndSave(this.createAgentChange(agent, theAgent.modelKey, model)); console.log("Change model:", agent, model); const message = { chat_id: query.message?.chat.id || 0, message_id: query.message?.message_id || 0, text: `${ENV.I18N.callback_query.change_model} ${agent} > ${model}` }; return sender.editRawMessage(message); } } const QUERY_HANDLERS = [ AgentListCallbackQueryHandler.Chat(), AgentListCallbackQueryHandler.Image(), ModelListCallbackQueryHandler.Chat(), ModelListCallbackQueryHandler.Image(), ModelChangeCallbackQueryHandler.Chat(), ModelChangeCallbackQueryHandler.Image() ]; async function handleCallbackQuery(callbackQuery, context) { const sender = MessageSender.fromCallbackQuery(context.SHARE_CONTEXT.botToken, callbackQuery); const answerCallbackQuery = (msg) => { return sender.api.answerCallbackQuery({ callback_query_id: callbackQuery.id, text: msg }); }; try { if (!callbackQuery.message) { return null; } const chatId = callbackQuery.message.chat.id; const speakerId = callbackQuery.from?.id || chatId; const chatType = callbackQuery.message.chat.type; for (const handler of QUERY_HANDLERS) { if (handler.needAuth) { const roleList = handler.needAuth(chatType); if (roleList) { const chatRole = await loadChatRoleWithContext(chatId, speakerId, context); if (chatRole === null) { return answerCallbackQuery("ERROR: Get chat role failed"); } if (!roleList.includes(chatRole)) { return answerCallbackQuery(`ERROR: Permission denied, need ${roleList.join(" or ")}`); } } } if (callbackQuery.data) { if (callbackQuery.data.startsWith(handler.prefix)) { return handler.handle(callbackQuery, callbackQuery.data, context); } } } } catch (e) { console.error("handleCallbackQuery", e); return answerCallbackQuery(`ERROR: ${e.message}`); } return null; } async function chatWithMessage(message, params, context, modifier) { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); try { try { const msg = await sender.sendPlainText("...").then((r) => r.json()); sender.update({ message_id: msg.result.message_id }); } catch (e) { console.error(e); } const api = createTelegramBotAPI(context.SHARE_CONTEXT.botToken); setTimeout(() => api.sendChatAction({ chat_id: message.chat.id, action: "typing" }).catch(console.error), 0); let onStream = null; let nextEnableTime = null; if (ENV.STREAM_MODE) { onStream = async (text) => { try { if (nextEnableTime && nextEnableTime > Date.now()) { return; } const resp = await sender.sendPlainText(text); if (resp.status === 429) { const retryAfter = Number.parseInt(resp.headers.get("Retry-After") || ""); if (retryAfter) { nextEnableTime = Date.now() + retryAfter * 1e3; return; } } nextEnableTime = null; if (resp.ok) { const respJson = await resp.json(); sender.update({ message_id: respJson.result.message_id }); } } catch (e) { console.error(e); } }; } const agent = loadChatLLM(context.USER_CONFIG); if (agent === null) { return sender.sendPlainText("LLM is not enable"); } const answer = await requestCompletionsFromLLM(params, context, agent, modifier, onStream); if (nextEnableTime !== null && nextEnableTime > Date.now()) { await new Promise((resolve) => setTimeout(resolve, (nextEnableTime ?? 0) - Date.now())); } return sender.sendRichText(answer); } catch (e) { let errMsg = `Error: ${e.message}`; if (errMsg.length > 2048) { errMsg = errMsg.substring(0, 2048); } return sender.sendPlainText(errMsg); } } async function extractImageURL(fileId, context) { if (!fileId) { return null; } const api = createTelegramBotAPI(context.SHARE_CONTEXT.botToken); const file = await api.getFileWithReturns({ file_id: fileId }); const filePath = file.result.file_path; if (filePath) { const url = URL.parse(`${ENV.TELEGRAM_API_DOMAIN}/file/bot${context.SHARE_CONTEXT.botToken}/${filePath}`); if (url) { return url; } } return null; } function extractImageFileID(message) { if (message.photo && message.photo.length > 0) { const offset = ENV.TELEGRAM_PHOTO_SIZE_OFFSET; const length = message.photo.length; const sizeIndex = Math.max(0, Math.min(offset >= 0 ? offset : length + offset, length - 1)); return message.photo[sizeIndex]?.file_id; } else if (message.document && message.document.thumbnail) { return message.document.thumbnail.file_id; } return null; } async function extractUserMessageItem(message, context) { let text = message.text || message.caption || ""; const urls = await extractImageURL(extractImageFileID(message), context).then((u) => u ? [u] : []); if (ENV.EXTRA_MESSAGE_CONTEXT && message.reply_to_message && message.reply_to_message.from && `${message.reply_to_message.from.id}` !== `${context.SHARE_CONTEXT.botId}`) { const extraText = message.reply_to_message.text || message.reply_to_message.caption || ""; if (extraText) { text = `${text} The following is the referenced context: ${extraText}`; } if (ENV.EXTRA_MESSAGE_MEDIA_COMPATIBLE.includes("image") && message.reply_to_message.photo) { const url = await extractImageURL(extractImageFileID(message.reply_to_message), context); if (url) { urls.push(url); } } } const params = { role: "user", content: text }; if (urls.length > 0) { const contents = new Array(); if (text) { contents.push({ type: "text", text }); } for (const url of urls) { contents.push({ type: "image", image: url }); } params.content = contents; } return params; } const INTERPOLATE_LOOP_REGEXP = /\{\{#each(?::(\w+))?\s+(\w+)\s+in\s+([\w.[\]]+)\}\}([\s\S]*?)\{\{\/each(?::\1)?\}\}/g; const INTERPOLATE_CONDITION_REGEXP = /\{\{#if(?::(\w+))?\s+([\w.[\]]+)\}\}([\s\S]*?)(?:\{\{#else(?::\1)?\}\}([\s\S]*?))?\{\{\/if(?::\1)?\}\}/g; const INTERPOLATE_VARIABLE_REGEXP = /\{\{([\w.[\]]+)\}\}/g; function evaluateExpression(expr, localData) { if (expr === ".") { return localData["."] ?? localData; } try { return expr.split(".").reduce((value, key) => { if (key.includes("[") && key.includes("]")) { const [arrayKey, indexStr] = key.split("["); const indexExpr = indexStr.slice(0, -1); let index = Number.parseInt(indexExpr, 10); if (Number.isNaN(index)) { index = evaluateExpression(indexExpr, localData); } return value?.[arrayKey]?.[index]; } return value?.[key]; }, localData); } catch (error) { console.error(`Error evaluating expression: ${expr}`, error); return void 0; } } function interpolate(template, data, formatter) { const processConditional = (condition, trueBlock, falseBlock, localData) => { const result = evaluateExpression(condition, localData); return result ? trueBlock : falseBlock || ""; }; const processLoop = (itemName, arrayExpr, loopContent, localData) => { const array = evaluateExpression(arrayExpr, localData); if (!Array.isArray(array)) { console.warn(`Expression "${arrayExpr}" did not evaluate to an array`); return ""; } return array.map((item) => { const itemData = { ...localData, [itemName]: item, ".": item }; return interpolate(loopContent, itemData); }).join(""); }; const processTemplate = (tmpl, localData) => { tmpl = tmpl.replace(INTERPOLATE_LOOP_REGEXP, (_, alias, itemName, arrayExpr, loopContent) => processLoop(itemName, arrayExpr, loopContent, localData)); tmpl = tmpl.replace(INTERPOLATE_CONDITION_REGEXP, (_, alias, condition, trueBlock, falseBlock) => processConditional(condition, trueBlock, falseBlock, localData)); return tmpl.replace(INTERPOLATE_VARIABLE_REGEXP, (_, expr) => { const value = evaluateExpression(expr, localData); if (value === void 0) { return `{{${expr}}}`; } if (formatter) { return formatter(value); } return String(value); }); }; return processTemplate(template, data); } function interpolateObject(obj, data) { if (obj === null || obj === void 0) { return null; } if (typeof obj === "string") { return interpolate(obj, data); } if (Array.isArray(obj)) { return obj.map((item) => interpolateObject(item, data)); } if (typeof obj === "object") { const result = {}; for (const [key, value] of Object.entries(obj)) { result[key] = interpolateObject(value, data); } return result; } return obj; } async function executeRequest(template, data) { const urlRaw = interpolate(template.url, data, encodeURIComponent); const url = new URL(urlRaw); if (template.query) { for (const [key, value] of Object.entries(template.query)) { url.searchParams.append(key, interpolate(value, data)); } } const method = template.method; const headers = Object.fromEntries( Object.entries(template.headers || {}).map(([key, value]) => { return [key, interpolate(value, data)]; }) ); for (const key of Object.keys(headers)) { if (headers[key] === null) { delete headers[key]; } } let body = null; if (template.body) { if (template.body.type === "json") { body = JSON.stringify(interpolateObject(template.body.content, data)); } else if (template.body.type === "form") { body = new URLSearchParams(); for (const [key, value] of Object.entries(template.body.content)) { body.append(key, interpolate(value, data)); } } else { body = interpolate(template.body.content, data); } } const response = await fetch(url, { method, headers, body }); const renderOutput = async (type, temple, response2) => { switch (type) { case "text": return interpolate(temple, await response2.text()); case "blob": throw new Error("Invalid output type"); case "json": default: return interpolate(temple, await response2.json()); } }; if (!response.ok) { const content2 = await renderOutput(template.response?.error?.input_type, template.response.error?.output, response); return { type: template.response.error.output_type, content: content2 }; } if (template.response.content.input_type === "blob") { if (template.response.content.output_type !== "image") { throw new Error("Invalid output type"); } return { type: "image", content: await response.blob() }; } const content = await renderOutput(template.response.content?.input_type, template.response.content?.output, response); return { type: template.response.content.output_type, content }; } function formatInput(input, type) { if (type === "json") { return JSON.parse(input); } else if (type === "space-separated") { return input.trim().split(" ").filter(Boolean); } else if (type === "comma-separated") { return input.split(",").map((item) => item.trim()).filter(Boolean); } else { return input; } } class ImgCommandHandler { command = "/img"; scopes = ["all_private_chats", "all_chat_administrators"]; handle = async (message, subcommand, context) => { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); if (subcommand === "") { const imgAgent = loadImageGen(context.USER_CONFIG); const text = `${ENV.I18N.command.help.img} ${imgAgent?.name || "Nan"} | ${imgAgent?.model(context.USER_CONFIG) || "Nan"}`; const params = { chat_id: message.chat.id, text, reply_markup: { inline_keyboard: [[ { text: ENV.I18N.callback_query.open_model_list, callback_data: "ial:" } ]] } }; return sender.sendRawMessage(params); } try { const api = createTelegramBotAPI(context.SHARE_CONTEXT.botToken); const agent = loadImageGen(context.USER_CONFIG); if (!agent) { return sender.sendPlainText("ERROR: Image generator not found"); } setTimeout(() => api.sendChatAction({ chat_id: message.chat.id, action: "upload_photo" }).catch(console.error), 0); const img = await agent.request(subcommand, context.USER_CONFIG); const resp = await sender.sendPhoto(img); if (!resp.ok) { return sender.sendPlainText(`ERROR: ${resp.statusText} ${await resp.text()}`); } return resp; } catch (e) { return sender.sendPlainText(`ERROR: ${e.message}`); } }; } class HelpCommandHandler { command = "/help"; scopes = ["all_private_chats", "all_chat_administrators"]; handle = async (message, subcommand, context) => { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); let helpMsg = `${ENV.I18N.command.help.summary} `; for (const [k, v] of Object.entries(ENV.I18N.command.help)) { if (k === "summary") { continue; } helpMsg += `/${k}:${v} `; } for (const [k, v] of Object.entries(ENV.CUSTOM_COMMAND)) { if (v.description) { helpMsg += `${k}:${v.description} `; } } for (const [k, v] of Object.entries(ENV.PLUGINS_COMMAND)) { if (v.description) { helpMsg += `${k}:${v.description} `; } } return sender.sendPlainText(helpMsg); }; } class BaseNewCommandHandler { static async handle(showID, message, subcommand, context) { await ENV.DATABASE.delete(context.SHARE_CONTEXT.chatHistoryKey); const text = ENV.I18N.command.new.new_chat_start + (showID ? `(${message.chat.id})` : ""); const params = { chat_id: message.chat.id, text }; if (ENV.SHOW_REPLY_BUTTON && !isGroupChat(message.chat.type)) { params.reply_markup = { keyboard: [[{ text: "/new" }, { text: "/redo" }]], selective: true, resize_keyboard: true, one_time_keyboard: false }; } else { params.reply_markup = { remove_keyboard: true, selective: true }; } return createTelegramBotAPI(context.SHARE_CONTEXT.botToken).sendMessage(params); } } class NewCommandHandler extends BaseNewCommandHandler { command = "/new"; scopes = ["all_private_chats", "all_group_chats", "all_chat_administrators"]; handle = async (message, subcommand, context) => { return BaseNewCommandHandler.handle(false, message, subcommand, context); }; } class StartCommandHandler extends BaseNewCommandHandler { command = "/start"; handle = async (message, subcommand, context) => { return BaseNewCommandHandler.handle(true, message, subcommand, context); }; } class SetEnvCommandHandler { command = "/setenv"; needAuth = TELEGRAM_AUTH_CHECKER.shareModeGroup; handle = async (message, subcommand, context) => { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); const kv = subcommand.indexOf("="); if (kv === -1) { return sender.sendPlainText(ENV.I18N.command.help.setenv); } const key = subcommand.slice(0, kv); const value = subcommand.slice(kv + 1); try { await context.execChangeAndSave({ [key]: value }); return sender.sendPlainText("Update user config success"); } catch (e) { return sender.sendPlainText(`ERROR: ${e.message}`); } }; } class SetEnvsCommandHandler { command = "/setenvs"; needAuth = TELEGRAM_AUTH_CHECKER.shareModeGroup; handle = async (message, subcommand, context) => { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); try { const values = JSON.parse(subcommand); await context.execChangeAndSave(values); return sender.sendPlainText("Update user config success"); } catch (e) { return sender.sendPlainText(`ERROR: ${e.message}`); } }; } class DelEnvCommandHandler { command = "/delenv"; needAuth = TELEGRAM_AUTH_CHECKER.shareModeGroup; handle = async (message, subcommand, context) => { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); if (ENV.LOCK_USER_CONFIG_KEYS.includes(subcommand)) { const msg = `Key ${subcommand} is locked`; return sender.sendPlainText(msg); } try { context.USER_CONFIG[subcommand] = null; context.USER_CONFIG.DEFINE_KEYS = context.USER_CONFIG.DEFINE_KEYS.filter((key) => key !== subcommand); await ENV.DATABASE.put( context.SHARE_CONTEXT.configStoreKey, JSON.stringify(ConfigMerger.trim(context.USER_CONFIG, ENV.LOCK_USER_CONFIG_KEYS)) ); return sender.sendPlainText("Delete user config success"); } catch (e) { return sender.sendPlainText(`ERROR: ${e.message}`); } }; } class ClearEnvCommandHandler { command = "/clearenv"; needAuth = TELEGRAM_AUTH_CHECKER.shareModeGroup; handle = async (message, subcommand, context) => { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); try { await ENV.DATABASE.put( context.SHARE_CONTEXT.configStoreKey, JSON.stringify({}) ); return sender.sendPlainText("Clear user config success"); } catch (e) { return sender.sendPlainText(`ERROR: ${e.message}`); } }; } class VersionCommandHandler { command = "/version"; scopes = ["all_private_chats", "all_chat_administrators"]; handle = async (message, subcommand, context) => { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); const current = { ts: ENV.BUILD_TIMESTAMP, sha: ENV.BUILD_VERSION }; try { const info = `https://raw.githubusercontent.com/TBXark/ChatGPT-Telegram-Workers/${ENV.UPDATE_BRANCH}/dist/buildinfo.json`; const online = await fetch(info).then((r) => r.json()); const timeFormat = (ts) => { return new Date(ts * 1e3).toLocaleString("en-US", {}); }; if (current.ts < online.ts) { const text = `New version detected: ${online.sha}(${timeFormat(online.ts)}) Current version: ${current.sha}(${timeFormat(current.ts)})`; return sender.sendPlainText(text); } else { const text = `Current version: ${current.sha}(${timeFormat(current.ts)}) is up to date`; return sender.sendPlainText(text); } } catch (e) { return sender.sendPlainText(`ERROR: ${e.message}`); } }; } class SystemCommandHandler { command = "/system"; scopes = ["all_private_chats", "all_chat_administrators"]; handle = async (message, subcommand, context) => { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); const chatAgent = loadChatLLM(context.USER_CONFIG); const imageAgent = loadImageGen(context.USER_CONFIG); const agent = { AI_PROVIDER: chatAgent?.name, [chatAgent?.modelKey || "AI_PROVIDER_NOT_FOUND"]: chatAgent?.model(context.USER_CONFIG), AI_IMAGE_PROVIDER: imageAgent?.name, [imageAgent?.modelKey || "AI_IMAGE_PROVIDER_NOT_FOUND"]: imageAgent?.model(context.USER_CONFIG) }; let msg = `<strong>AGENT</strong><pre>${JSON.stringify(agent, null, 2)}</pre>`; if (ENV.DEV_MODE) { const config = ConfigMerger.trim(context.USER_CONFIG, ENV.LOCK_USER_CONFIG_KEYS); msg += ` <strong>USER_CONFIG</strong><pre>${JSON.stringify(config, null, 2)}</pre>`; const secretsSuffix = ["_API_KEY", "_TOKEN", "_ACCOUNT_ID"]; for (const key of Object.keys(context.USER_CONFIG)) { if (secretsSuffix.some((suffix) => key.endsWith(suffix))) { context.USER_CONFIG[key] = "******"; } } msg += ` <strong>CHAT_CONTEXT</strong><pre>${JSON.stringify(sender.context || {}, null, 2)}</pre>`; const shareCtx = { ...context.SHARE_CONTEXT, botToken: "******" }; msg += ` <strong>SHARE_CONTEXT</strong><pre>${JSON.stringify(shareCtx, null, 2)}</pre>`; } return sender.sendRichText(msg, "HTML"); }; } class RedoCommandHandler { command = "/redo"; scopes = ["all_private_chats", "all_group_chats", "all_chat_administrators"]; handle = async (message, subcommand, context) => { const mf = (history, message2) => { let nextMessage = message2; if (!(history && Array.isArray(history) && history.length > 0)) { throw new Error("History not found"); } const historyCopy = structuredClone(history); while (true) { const data = historyCopy.pop(); if (data === void 0 || data === null) { break; } else if (data.role === "user") { nextMessage = data; break; } } if (subcommand) { nextMessage = { role: "user", content: subcommand }; } if (nextMessage === null) { throw new Error("Redo message not found"); } return { history: historyCopy, message: nextMessage }; }; return chatWithMessage(message, null, context, mf); }; } class ModelsCommandHandler { command = "/models"; scopes = ["all_private_chats", "all_group_chats", "all_chat_administrators"]; handle = async (message, subcommand, context) => { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); const chatAgent = loadChatLLM(context.USER_CONFIG); const text = `${chatAgent?.name || "Nan"} | ${chatAgent?.model(context.USER_CONFIG) || "Nan"}`; const params = { chat_id: message.chat.id, text, reply_markup: { inline_keyboard: [[ { text: ENV.I18N.callback_query.open_model_list, callback_data: "al:" } ]] } }; return sender.sendRawMessage(params); }; } class EchoCommandHandler { command = "/echo"; handle = (message, subcommand, context) => { let msg = "<pre>"; msg += JSON.stringify({ message }, null, 2); msg += "</pre>"; return MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message).sendRichText(msg, "HTML"); }; } const SYSTEM_COMMANDS = [ new StartCommandHandler(), new NewCommandHandler(), new RedoCommandHandler(), new ImgCommandHandler(), new SetEnvCommandHandler(), new SetEnvsCommandHandler(), new DelEnvCommandHandler(), new ClearEnvCommandHandler(), new VersionCommandHandler(), new SystemCommandHandler(), new ModelsCommandHandler(), new HelpCommandHandler() ]; async function handleSystemCommand(message, raw, command, context) { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); try { const chatId = message.chat.id; const speakerId = message.from?.id || chatId; const chatType = message.chat.type; if (command.needAuth) { const roleList = command.needAuth(chatType); if (roleList) { const chatRole = await loadChatRoleWithContext(chatId, speakerId, context); if (chatRole === null) { return sender.sendPlainText("ERROR: Get chat role failed"); } if (!roleList.includes(chatRole)) { return sender.sendPlainText(`ERROR: Permission denied, need ${roleList.join(" or ")}`); } } } } catch (e) { return sender.sendPlainText(`ERROR: ${e.message}`); } const subcommand = raw.substring(command.command.length).trim(); try { return await command.handle(message, subcommand, context); } catch (e) { return sender.sendPlainText(`ERROR: ${e.message}`); } } async function handlePluginCommand(message, command, raw, template, context) { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); try { const subcommand = raw.substring(command.length).trim(); if (template.input?.required && !subcommand) { throw new Error("Missing required input"); } const DATA = formatInput(subcommand, template.input?.type); const { type, content } = await executeRequest(template, { DATA, ENV: ENV.PLUGINS_ENV }); switch (type) { case "image": return sender.sendPhoto(content); case "html": return sender.sendRichText(content, "HTML"); case "markdown": return sender.sendRichText(content, "Markdown"); case "text": default: return sender.sendPlainText(content); } } catch (e) { const help = ENV.PLUGINS_COMMAND[command].description; return sender.sendPlainText(`ERROR: ${e.message}${help ? ` ${help}` : ""}`); } } async function handleCommandMessage(message, context) { let text = (message.text || message.caption || "").trim(); if (ENV.CUSTOM_COMMAND[text]) { text = ENV.CUSTOM_COMMAND[text].value; } if (ENV.DEV_MODE) { SYSTEM_COMMANDS.push(new EchoCommandHandler()); } for (const key in ENV.PLUGINS_COMMAND) { if (text === key || text.startsWith(`${key} `)) { let template = ENV.PLUGINS_COMMAND[key].value.trim(); if (template.startsWith("http")) { template = await fetch(template).then((r) => r.text()); } return await handlePluginCommand(message, key, text, JSON.parse(template), context); } } for (const cmd of SYSTEM_COMMANDS) { if (text === cmd.command || text.startsWith(`${cmd.command} `)) { return await handleSystemCommand(message, text, cmd, context); } } return null; } function commandsBindScope() { const scopeCommandMap = { all_private_chats: [], all_group_chats: [], all_chat_administrators: [] }; for (const cmd of SYSTEM_COMMANDS) { if (ENV.HIDE_COMMAND_BUTTONS.includes(cmd.command)) { continue; } if (cmd.scopes) { for (const scope of cmd.scopes) { if (!scopeCommandMap[scope]) { scopeCommandMap[scope] = []; } const desc = ENV.I18N.command.help[cmd.command.substring(1)] || ""; if (desc) { scopeCommandMap[scope].push({ command: cmd.command, description: desc }); } } } } for (const list of [ENV.CUSTOM_COMMAND, ENV.PLUGINS_COMMAND]) { for (const [cmd, config] of Object.entries(list)) { if (config.scope) { for (const scope of config.scope) { if (!scopeCommandMap[scope]) { scopeCommandMap[scope] = []; } scopeCommandMap[scope].push({ command: cmd, description: config.description || "" }); } } } } const result = {}; for (const scope in scopeCommandMap) { result[scope] = { commands: scopeCommandMap[scope], scope: { type: scope } }; } return result; } function commandsDocument() { return SYSTEM_COMMANDS.map((command) => { return { command: command.command, description: ENV.I18N.command.help[command.command.substring(1)] || "" }; }).filter((item) => item.description !== ""); } class EnvChecker { handle = async (update, context) => { if (!ENV.DATABASE) { return MessageSender.fromUpdate(context.SHARE_CONTEXT.botToken, update).sendPlainText("DATABASE Not Set"); } return null; }; } class WhiteListFilter { handle = async (update, context) => { if (ENV.I_AM_A_GENEROUS_PERSON) { return null; } const sender = MessageSender.fromUpdate(context.SHARE_CONTEXT.botToken, update); let chatType = ""; let chatID = 0; if (update.message) { chatType = update.message.chat.type; chatID = update.message.chat.id; } else if (update.callback_query?.message) { chatType = update.callback_query.message.chat.type; chatID = update.callback_query.message.chat.id; } if (!chatType || !chatID) { throw new Error("Invalid chat type or chat id"); } const text = `You are not in the white list, please contact the administrator to add you to the white list. Your chat_id: ${chatID}`; if (chatType === "private") { if (!ENV.CHAT_WHITE_LIST.includes(`${chatID}`)) { return sender.sendPlainText(text); } return null; } if (isGroupChat(chatType)) { if (!ENV.GROUP_CHAT_BOT_ENABLE) { throw new Error("Not support"); } if (!ENV.CHAT_GROUP_WHITE_LIST.includes(`${chatID}`)) { return sender.sendPlainText(text); } return null; } return sender.sendPlainText( `Not support chat type: ${chatType}` ); }; } class Update2MessageHandler { messageHandlers; constructor(messageHandlers) { this.messageHandlers = messageHandlers; } loadMessage(body) { if (body.edited_message) { throw new Error("Ignore edited message"); } if (body.message) { return body?.message; } else { throw new Error("Invalid message"); } } handle = async (update, context) => { const message = this.loadMessage(update); if (!message) { return null; } for (const handler of this.messageHandlers) { const result = await handler.handle(message, context); if (result) { return result; } } return null; }; } class CallbackQueryHandler { handle = async (update, context) => { if (update.callback_query) { return handleCallbackQuery(update.callback_query, context); } return null; }; } class SaveLastMessage { handle = async (message, context) => { if (!ENV.DEBUG_MODE) { return null; } const lastMessageKey = `last_message:${context.SHARE_CONTEXT.chatHistoryKey}`; await ENV.DATABASE.put(lastMessageKey, JSON.stringify(message), { expirationTtl: 3600 }); return null; }; } class OldMessageFilter { handle = async (message, context) => { if (!ENV.SAFE_MODE) { return null; } let idList = []; try { idList = JSON.parse(await ENV.DATABASE.get(context.SHARE_CONTEXT.lastMessageKey).catch(() => "[]")) || []; } catch (e) { console.error(e); } if (idList.includes(message.message_id)) { throw new Error("Ignore old message"); } else { idList.push(message.message_id); if (idList.length > 100) { idList.shift(); } await ENV.DATABASE.put(context.SHARE_CONTEXT.lastMessageKey, JSON.stringify(idList)); } return null; }; } class MessageFilter { handle = async (message, context) => { if (message.text) { return null; } if (message.caption) { return null; } if (message.photo) { return null; } throw new Error("Not supported message type"); }; } class CommandHandler { handle = async (message, context) => { if (message.text || message.caption) { return await handleCommandMessage(message, context); } return null; }; } class ChatHandler { handle = async (message, context) => { const params = await extractUserMessageItem(message, context); return chatWithMessage(message, params, context, null); }; } const SHARE_HANDLER = [ new EnvChecker(), new WhiteListFilter(), new CallbackQueryHandler(), new Update2MessageHandler([ new MessageFilter(), new GroupMention(), new OldMessageFilter(), new SaveLastMessage(), new CommandHandler(), new ChatHandler() ]) ]; async function handleUpdate(token, update) { const context = await WorkerContext.from(token, update); for (const handler of SHARE_HANDLER) { try { const result = await handler.handle(update, context); if (result) { return result; } } catch (e) { return new Response(JSON.stringify({ message: e.message, stack: e.stack }), { status: 500 }); } } return null; } function renderHTML(body) { return ` <html lang="en"> <head> <title>ChatGPT-Telegram-Workers</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content="ChatGPT-Telegram-Workers"> <meta name="author" content="TBXark"> <style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #212529; text-align: left; background-color: #fff; } h1 { margin-top: 0; margin-bottom: 0.5rem; } p { margin-top: 0; margin-bottom: 1rem; } a { color: #007bff; text-decoration: none; background-color: transparent; } a:hover { color: #0056b3; text-decoration: underline; } strong { font-weight: bolder; } </style> </head> <body> ${body} </body> </html> `; } function errorToString(e) { return JSON.stringify({ message: e.message, stack: e.stack }); } function makeResponse200(resp) { if (resp === null) { return new Response("NOT HANDLED", { status: 200 }); } if (resp.status === 200) { return resp; } else { return new Response(resp.body, { status: 200, headers: { "Original-Status": `${resp.status}`, ...resp.headers } }); } } class Router { routes; base; errorHandler = async (req, error) => new Response(errorToString(error), { status: 500 }); constructor({ base = "", routes = [], ...other } = {}) { this.routes = routes; this.base = base; Object.assign(this, other); this.fetch = this.fetch.bind(this); this.route = this.route.bind(this); this.get = this.get.bind(this); this.post = this.post.bind(this); this.put = this.put.bind(this); this.delete = this.delete.bind(this); this.patch = this.patch.bind(this); this.head = this.head.bind(this); this.options = this.options.bind(this); this.all = this.all.bind(this); } parseQueryParams(searchParams) { const query = {}; searchParams.forEach((v, k) => { query[k] = k in query ? [...Array.isArray(query[k]) ? query[k] : [query[k]], v] : v; }); return query; } normalizePath(path) { return path.replace(/\/+(\/|$)/g, "$1"); } createRouteRegex(path) { return new RegExp(`^${path.replace(/\\/g, "\\\\").replace(/(\/?\.?):(\w+)\+/g, "($1(?<$2>*))").replace(/(\/?\.?):(\w+)/g, "($1(?<$2>[^$1/]+?))").replace(/\./g, "\\.").replace(/(\/?)\*/g, "($1.*)?")}/*$`); } async fetch(request, ...args) { try { const url = new URL(request.url); const reqMethod = request.method.toUpperCase(); request.query = this.parseQueryParams(url.searchParams); for (const [method, regex, handlers, path] of this.routes) { let match = null; if ((method === reqMethod || method === "ALL") && (match = url.pathname.match(regex))) { request.params = match?.groups || {}; request.route = path; for (const handler of handlers) { const response = await handler(request, ...args); if (response != null) { return response; } } } } return new Response("Not Found", { status: 404 }); } catch (e) { return this.errorHandler(request, e); } } route(method, path, ...handlers) { const route = this.normalizePath(this.base + path); const regex = this.createRouteRegex(route); this.routes.push([method.toUpperCase(), regex, handlers, route]); return this; } get(path, ...handlers) { return this.route("GET", path, ...handlers); } post(path, ...handlers) { return this.route("POST", path, ...handlers); } put(path, ...handlers) { return this.route("PUT", path, ...handlers); } delete(path, ...handlers) { return this.route("DELETE", path, ...handlers); } patch(path, ...handlers) { return this.route("PATCH", path, ...handlers); } head(path, ...handlers) { return this.route("HEAD", path, ...handlers); } options(path, ...handlers) { return this.route("OPTIONS", path, ...handlers); } all(path, ...handlers) { return this.route("ALL", path, ...handlers); } } const helpLink = "https://github.com/TBXark/ChatGPT-Telegram-Workers/blob/master/doc/en/DEPLOY.md"; const issueLink = "https://github.com/TBXark/ChatGPT-Telegram-Workers/issues"; const initLink = "./init"; const footer = ` <br/> <p>For more information, please visit <a href="${helpLink}">${helpLink}</a></p> <p>If you have any questions, please visit <a href="${issueLink}">${issueLink}</a></p> `; async function bindWebHookAction(request) { const result = {}; const domain = new URL(request.url).host; const hookMode = ENV.API_GUARD ? "safehook" : "webhook"; const scope = commandsBindScope(); for (const token of ENV.TELEGRAM_AVAILABLE_TOKENS) { const api = createTelegramBotAPI(token); const url = `https://${domain}/telegram/${token.trim()}/${hookMode}`; const id = token.split(":")[0]; result[id] = {}; result[id].webhook = await api.setWebhook({ url }).then((res) => res.json()).catch((e) => errorToString(e)); for (const [s, data] of Object.entries(scope)) { result[id][s] = await api.setMyCommands(data).then((res) => res.json()).catch((e) => errorToString(e)); } } let html = `<h1>ChatGPT-Telegram-Workers</h1>`; html += `<h2>${domain}</h2>`; if (ENV.TELEGRAM_AVAILABLE_TOKENS.length === 0) { html += `<p style="color: red">Please set the <strong> TELEGRAM_AVAILABLE_TOKENS </strong> environment variable in Cloudflare Workers.</p> `; } else { for (const [key, res] of Object.entries(result)) { html += `<h3>Bot: ${key}</h3>`; for (const [s, data] of Object.entries(res)) { html += `<p style="color: ${data.ok ? "green" : "red"}">${s}: ${JSON.stringify(data)}</p>`; } } } html += footer; const HTML = renderHTML(html); return new Response(HTML, { status: 200, headers: { "Content-Type": "text/html" } }); } async function telegramWebhook(request) { try { const { token } = request.params; const body = await request.json(); return makeResponse200(await handleUpdate(token, body)); } catch (e) { console.error(e); return new Response(errorToString(e), { status: 200 }); } } async function telegramSafeHook(request) { try { if (ENV.API_GUARD === void 0 || ENV.API_GUARD === null) { return telegramWebhook(request); } console.log("API_GUARD is enabled"); const url = new URL(request.url); url.pathname = url.pathname.replace("/safehook", "/webhook"); const newRequest = new Request(url, request); return makeResponse200(await ENV.API_GUARD.fetch(newRequest)); } catch (e) { console.error(e); return new Response(errorToString(e), { status: 200 }); } } async function defaultIndexAction() { const HTML = renderHTML(` <h1>ChatGPT-Telegram-Workers</h1> <br/> <p>Deployed Successfully!</p> <p> Version (ts:${ENV.BUILD_TIMESTAMP},sha:${ENV.BUILD_VERSION})</p> <br/> <p>You must <strong><a href="${initLink}"> >>>>> click here <<<<< </a></strong> to bind the webhook.</p> <br/> <p>After binding the webhook, you can use the following commands to control the bot:</p> ${commandsDocument().map((item) => `<p><strong>${item.command}</strong> - ${item.description}</p>`).join("")} <br/> <p>You can get bot information by visiting the following URL:</p> <p><strong>/telegram/:token/bot</strong> - Get bot information</p> ${footer} `); return new Response(HTML, { status: 200, headers: { "Content-Type": "text/html" } }); } function createRouter() { const router = new Router(); router.get("/", defaultIndexAction); router.get("/init", bindWebHookAction); router.post("/telegram/:token/webhook", telegramWebhook); router.post("/telegram/:token/safehook", telegramSafeHook); router.all("*", () => new Response("Not Found", { status: 404 })); return router; } const Workers = { async fetch(request, env) { try { ENV.merge(env); return createRouter().fetch(request); } catch (e) { console.error(e); return new Response(JSON.stringify({ message: e.message, stack: e.stack }), { status: 500 }); } } }; export { Workers as default }; //# sourceMappingURL=index.js.map{/collapse-item}{/collapse}复制全部代码,粘贴到编辑器点击 部署第四步:配置环境变量4.1 创建 KV 命名空间进入 Workers 首页 → KV点击 创建命名空间输入名字(如 chatgpt-db),点击 添加4.2 绑定 KV 到 Worker回到你的 Worker → 设置 → 变量找到 KV 命名空间绑定点击 编辑变量添加变量:变量名称: DATABASEKV 命名空间: 选择刚才创建的 chatgpt-db点击 保存4.3 添加必需的环境变量在 变量和密钥 部分点击 添加变量,添加以下变量:变量名值说明TELEGRAM_AVAILABLE_TOKENS你的 Bot TokenTelegram 机器人令牌CLOUDFLARE_ACCOUNT_ID你的账户 IDCloudflare 账户 IDCLOUDFLARE_API_TOKEN你的 API 令牌Cloudflare API 令牌AI_MODEL@cf/meta/llama-3-8b-instruct使用的 AI 模型获取账户 ID 和 API 令牌:账户 ID:控制台右侧栏显示,或访问 https://dash.cloudflare.com/api-tokensAPI 令牌:访问 https://dash.cloudflare.com/profile/api-tokens点击 创建令牌选择 编辑 Cloudflare Workers 模板权限:Account.Account ID 和 Workers 脚本 设为 编辑点击 继续以摘要 → 创建令牌复制保存令牌4.4 可选变量变量名说明SYSTEM_PROMPT自定义系统提示词PLUGINS启用插件(如 search,calculator)ALLOWED_USERS限制使用用户 ID,留空则公开第五步:初始化 Webhook在浏览器访问:https://chatgpt-bot.yourname.workers.dev/init替换为你的 Worker 实际域名。页面显示 Success 即表示:✅ Webhook 已自动绑定✅ 命令已自动注册使用指南基本命令在 Telegram 中与机器人对话:/start - 启动机器人/new - 开始新对话/help - 查看帮助/models - 切换模型/image - 生成图片/reset - 重置对话上下文群聊支持如需在群聊中使用,添加环境变量: GROUP_CHAT_BOT_TOKENS = 你的 BotToken常见问题Q: 机器人没反应?检查 /init 页面是否显示 Success访问 https://api.telegram.org/bot<TOKEN>/getWebhookInfo 查看 Webhook 状态Q: 提示 AI 调用失败?检查 CLOUDFLARE_ACCOUNT_ID 和 CLOUDFLARE_API_TOKEN 是否正确确认 AI 服务已激活(AI)查看 Worker 日志(Workers → 点击 Worker → 日志 )Q: 如何更换 AI 模型?修改 AI_MODEL 为其他可用模型参考:https://developers.cloudflare.com/workers-ai/models/Q: 费用多少?Workers:免费计划每天 10 万次请求Workers AI:免费每天 10000 neurons(约 1000-2000 次对话)超出后:按量计费,约 $0.0001/1K neurons个人使用:基本免费额度够用参考链接项目地址:https://github.com/TBXark/ChatGPT-Telegram-WorkersCloudflare AI 模型:https://developers.cloudflare.com/workers-ai/models/Workers AI 定价:https://developers.cloudflare.com/workers-ai/platform/pricing/配置文档:https://github.com/tbxark/ChatGPT-Telegram-Workers/blob/master/doc/cn/CONFIG.md
2026年03月04日
235 阅读
0 评论
0 点赞