feat: add Telegram push support with multi-target routes

Add a platform-aware route target system so a single route can forward
to several destinations at once (e.g. a Discord channel and a Telegram
group). Route.target becomes Route.targets[] with per-entry platform,
channelId/threadId for Discord and chatId/topicId for Telegram; the
legacy single-target format is normalized on load and accepted by the
admin API.

Implement the Telegram driver with HTML rendering and Bot API
sendMessage (chat_id + message_thread_id for topics, retry on 429/5xx),
plus /gh commands served over POST /telegram/webhook: login, logout,
comment, merge and close. The comment/merge/close commands resolve the
issue or PR from the replied-to notification message. OAuth binding now
stores a D1 telegram_links mapping and replies with a confirmation.

Sync the Telegram webhook from the scheduled trigger via setWebhook.
This commit is contained in:
RhenCloud 2026-08-03 05:17:59 +08:00
parent dcbe93be91
commit bd7a8f2632
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
39 changed files with 1165 additions and 162 deletions

View file

@ -34,6 +34,8 @@ const en: Dict = {
"route.noFilters": "no filters",
"route.channel": "CHANNEL",
"route.thread": "THREAD",
"route.chat": "CHAT",
"route.topic": "TOPIC",
"route.fallback": "fallback",
"filter.event": "Event",
"filter.repo": "Repo",
@ -64,13 +66,24 @@ const en: Dict = {
"routeEditor.thread": "Thread ID",
"routeEditor.threadNote": "(optional)",
"routeEditor.threadPlaceholder": "Optional thread ID",
"routeEditor.platform": "Platform",
"routeEditor.chat": "Chat ID",
"routeEditor.chatPlaceholder": "Telegram group chat ID",
"routeEditor.topic": "Topic ID",
"routeEditor.topicNote": "(optional)",
"routeEditor.topicPlaceholder": "Optional topic (message_thread_id)",
"routeEditor.errChat": "Target {n} chat ID is required",
"routeEditor.targets": "Targets",
"routeEditor.targetsNote": "(one or more destinations)",
"routeEditor.addTarget": "+ Add target",
"routeEditor.errTargets": "Add at least one target",
"routeEditor.cancel": "Cancel",
"routeEditor.save": "Save route",
"routeEditor.errFilterMatch": "Filter {n} needs a match value",
"routeEditor.errAddFilter": "Add at least one filter",
"routeEditor.errIdFormat": "ID must be a-z / 0-9 / dashes",
"routeEditor.errName": "Name is required",
"routeEditor.errChannel": "Channel ID is required",
"routeEditor.errChannel": "Target {n} channel ID is required",
"groupEditor.editTitle": "Edit group",
"groupEditor.newTitle": "New group",
"groupEditor.close": "Close",
@ -161,6 +174,8 @@ const zh: Dict = {
"route.noFilters": "无过滤器",
"route.channel": "频道",
"route.thread": "子区",
"route.chat": "群组",
"route.topic": "话题",
"route.fallback": "兜底",
"filter.event": "事件",
"filter.repo": "仓库",
@ -191,13 +206,24 @@ const zh: Dict = {
"routeEditor.thread": "子区 ID",
"routeEditor.threadNote": "(可选)",
"routeEditor.threadPlaceholder": "可选的子区 ID",
"routeEditor.platform": "平台",
"routeEditor.chat": "群组 ID",
"routeEditor.chatPlaceholder": "Telegram 群组聊天 ID",
"routeEditor.topic": "话题 ID",
"routeEditor.topicNote": "(可选)",
"routeEditor.topicPlaceholder": "可选的话题 (message_thread_id)",
"routeEditor.errChat": "第 {n} 个目标群组 ID 为必填项",
"routeEditor.targets": "目标",
"routeEditor.targetsNote": "(一个或多个推送目标)",
"routeEditor.addTarget": "+ 添加目标",
"routeEditor.errTargets": "至少添加一个目标",
"routeEditor.cancel": "取消",
"routeEditor.save": "保存路由",
"routeEditor.errFilterMatch": "第 {n} 个过滤器需要匹配值",
"routeEditor.errAddFilter": "至少添加一个过滤器",
"routeEditor.errIdFormat": "ID 只能是 a-z / 0-9 / 短横线",
"routeEditor.errName": "名称为必填项",
"routeEditor.errChannel": "频道 ID 为必填项",
"routeEditor.errChannel": "第 {n} 个目标频道 ID 为必填项",
"groupEditor.editTitle": "编辑分组",
"groupEditor.newTitle": "新建分组",
"groupEditor.close": "关闭",