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

@ -9,6 +9,13 @@
<span class="route-name">{{ route.name || t("route.untitled") }}</span>
<span class="route-id">{{ route.id }}</span>
<span v-if="route.lang" class="badge lang">{{ route.lang }}</span>
<span
v-for="(tg, i) in route.targets"
:key="i"
class="badge"
:class="tg.platform === 'telegram' ? 'fallback' : 'lang'"
>{{ tg.platform === "telegram" ? "Telegram" : "Discord" }}</span
>
<span v-if="route.fallback" class="badge fallback">{{ t("route.fallback") }}</span>
</div>
<div class="card-actions">
@ -36,14 +43,20 @@
>
</div>
<div class="target">
<span
><b>{{ t("route.channel") }}</b
><code>{{ route.target.channelId }}</code></span
>
<span v-if="route.target.threadId"
><b>{{ t("route.thread") }}</b
><code>{{ route.target.threadId }}</code></span
>
<div v-for="(tg, i) in route.targets" :key="i" class="target-group">
<span class="target-plat"
><b>{{ tg.platform === "telegram" ? t("route.chat") : t("route.channel") }}</b
><code>{{ tg.platform === "telegram" ? tg.chatId : tg.channelId }}</code></span
>
<span v-if="tg.platform === 'telegram' && tg.topicId"
><b>{{ t("route.topic") }}</b
><code>{{ tg.topicId }}</code></span
>
<span v-else-if="tg.platform !== 'telegram' && tg.threadId"
><b>{{ t("route.thread") }}</b
><code>{{ tg.threadId }}</code></span
>
</div>
</div>
</article>
</template>