mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
feat(feishu): add Feishu inbound webhook, /gh commands and card actions
- add feishu_links D1 table and link store helpers - implement X-Lark-Signature verification, url_verification, /gh login|logout|comment|merge|close and card.action.trigger Merge/Close - render interactive cards with clickable title link, inline links and callback buttons (no whole-card card_link) - bind Feishu account in OAuth callback - document event subscription and required scopes
This commit is contained in:
parent
3437ac5513
commit
4b99f6d33d
38 changed files with 2449 additions and 1412 deletions
|
|
@ -104,7 +104,10 @@ function fmtTime(ts: number): string {
|
|||
}
|
||||
|
||||
function platformLabel(p?: string): string {
|
||||
return p === "telegram" ? "TG" : p === "discord" ? "DC" : "—";
|
||||
if (p === "telegram") return "TG";
|
||||
if (p === "feishu") return "FS";
|
||||
if (p === "discord") return "DC";
|
||||
return "—";
|
||||
}
|
||||
|
||||
function routeEvents(r: Route): string[] {
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@
|
|||
<option value="">{{ t("groupEditor.logDisabled") }}</option>
|
||||
<option value="discord">Discord</option>
|
||||
<option value="telegram">Telegram</option>
|
||||
<option value="feishu">{{ t("groupEditor.logFeishu") }}</option>
|
||||
</select>
|
||||
<template v-if="form.logPlatform === 'discord'">
|
||||
<input
|
||||
|
|
@ -201,7 +202,7 @@
|
|||
:placeholder="t('routeEditor.threadPlaceholder')"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="form.logPlatform === 'telegram'">
|
||||
<template v-else-if="form.logPlatform === 'telegram' || form.logPlatform === 'feishu'">
|
||||
<input
|
||||
v-model="form.logChatId"
|
||||
type="text"
|
||||
|
|
@ -209,6 +210,7 @@
|
|||
:placeholder="t('routeEditor.chatPlaceholder')"
|
||||
/>
|
||||
<input
|
||||
v-if="form.logPlatform === 'telegram'"
|
||||
v-model="form.logTopicId"
|
||||
type="text"
|
||||
class="input mt-2"
|
||||
|
|
@ -263,7 +265,7 @@ const form = reactive({
|
|||
emoji: true,
|
||||
forgeSources: [] as ForgeSource[],
|
||||
lang: "",
|
||||
logPlatform: "" as "" | "discord" | "telegram",
|
||||
logPlatform: "" as "" | "discord" | "telegram" | "feishu",
|
||||
logChannelId: "",
|
||||
logThreadId: "",
|
||||
logChatId: "",
|
||||
|
|
@ -323,11 +325,8 @@ function save(): void {
|
|||
}
|
||||
let logTarget:
|
||||
| { platform: "discord"; channelId: string; threadId?: string }
|
||||
| {
|
||||
platform: "telegram";
|
||||
chatId: string;
|
||||
topicId?: string;
|
||||
}
|
||||
| { platform: "telegram"; chatId: string; topicId?: string }
|
||||
| { platform: "feishu"; chatId: string }
|
||||
| undefined;
|
||||
if (form.logPlatform === "discord") {
|
||||
const channelId = form.logChannelId.trim();
|
||||
|
|
@ -343,6 +342,13 @@ function save(): void {
|
|||
return;
|
||||
}
|
||||
logTarget = { platform: "telegram", chatId, topicId: form.logTopicId.trim() || undefined };
|
||||
} else if (form.logPlatform === "feishu") {
|
||||
const chatId = form.logChatId.trim();
|
||||
if (!chatId) {
|
||||
formError.value = t("groupEditor.errLogFeishuChat");
|
||||
return;
|
||||
}
|
||||
logTarget = { platform: "feishu", chatId };
|
||||
}
|
||||
const installationText = form.installationId.trim();
|
||||
let installationId: number | undefined;
|
||||
|
|
|
|||
|
|
@ -16,13 +16,25 @@
|
|||
v-for="(tg, i) in route.targets"
|
||||
:key="i"
|
||||
class="route-badge"
|
||||
:class="tg.platform === 'telegram' ? 'route-badge-tg' : 'route-badge-dc'"
|
||||
:class="
|
||||
tg.platform === 'telegram'
|
||||
? 'route-badge-tg'
|
||||
: tg.platform === 'feishu'
|
||||
? 'route-badge-fs'
|
||||
: 'route-badge-dc'
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="route-badge-dot"
|
||||
:class="tg.platform === 'telegram' ? 'bg-info' : 'bg-accent'"
|
||||
:class="
|
||||
tg.platform === 'telegram'
|
||||
? 'bg-info'
|
||||
: tg.platform === 'feishu'
|
||||
? 'bg-warn'
|
||||
: 'bg-accent'
|
||||
"
|
||||
></span>
|
||||
{{ tg.platform === "telegram" ? "Telegram" : "Discord" }}
|
||||
{{ tg.platform === "telegram" ? "Telegram" : tg.platform === "feishu" ? t("route.feishu") : "Discord" }}
|
||||
</span>
|
||||
<span v-if="route.fallback" class="route-badge route-badge-fallback">{{
|
||||
t("route.fallback")
|
||||
|
|
@ -47,12 +59,12 @@
|
|||
<div v-for="(tg, i) in route.targets" :key="i" class="route-target">
|
||||
<div class="route-target-row">
|
||||
<span class="route-target-label">
|
||||
<template v-if="tg.platform === 'telegram'">{{ t("route.chat") }}</template>
|
||||
<template v-if="tg.platform === 'telegram' || tg.platform === 'feishu'">{{ t("route.chat") }}</template>
|
||||
<template v-else-if="tg.threadId">{{ t("route.thread") }}</template>
|
||||
<template v-else>{{ t("route.channel") }}</template>
|
||||
</span>
|
||||
<code class="route-target-id">
|
||||
<template v-if="tg.platform === 'telegram'">{{ tg.chatId }}</template>
|
||||
<template v-if="tg.platform === 'telegram' || tg.platform === 'feishu'">{{ tg.chatId }}</template>
|
||||
<template v-else-if="tg.threadId">{{ tg.threadId }}</template>
|
||||
<template v-else>{{ tg.channelId }}</template>
|
||||
</code>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from "~/composables/useFilterNode";
|
||||
|
||||
interface TargetForm {
|
||||
platform: "discord" | "telegram";
|
||||
platform: "discord" | "telegram" | "feishu";
|
||||
channelId: string;
|
||||
threadId: string;
|
||||
chatId: string;
|
||||
|
|
@ -129,6 +129,10 @@ function collect(): Route | null {
|
|||
const chatId = tg.chatId.trim();
|
||||
if (!chatId) continue;
|
||||
targets.push({ platform: "telegram", chatId, topicId: tg.topicId.trim() || undefined });
|
||||
} else if (tg.platform === "feishu") {
|
||||
const chatId = tg.chatId.trim();
|
||||
if (!chatId) continue;
|
||||
targets.push({ platform: "feishu", chatId });
|
||||
} else {
|
||||
const channelId = tg.channelId.trim();
|
||||
if (!channelId) continue;
|
||||
|
|
@ -170,7 +174,7 @@ function save(): void {
|
|||
return;
|
||||
}
|
||||
form.targets.forEach((tg, i) => {
|
||||
if (tg.platform === "telegram" && !tg.chatId.trim()) {
|
||||
if ((tg.platform === "telegram" || tg.platform === "feishu") && !tg.chatId.trim()) {
|
||||
targetError.value = t("routeEditor.errChat", { n: i + 1 });
|
||||
} else if (tg.platform === "discord" && !tg.channelId.trim()) {
|
||||
targetError.value = t("routeEditor.errChannel", { n: i + 1 });
|
||||
|
|
@ -283,7 +287,7 @@ watch(
|
|||
? r.targets.map((tg) => ({
|
||||
...blankTarget(),
|
||||
...tg,
|
||||
platform: tg.platform === "telegram" ? "telegram" : "discord",
|
||||
platform: tg.platform === "telegram" ? "telegram" : tg.platform === "feishu" ? "feishu" : "discord",
|
||||
}))
|
||||
: [blankTarget()];
|
||||
if (r?.ast) {
|
||||
|
|
@ -481,6 +485,7 @@ watch(
|
|||
<select v-model="tg.platform" class="tg-select">
|
||||
<option value="discord">Discord</option>
|
||||
<option value="telegram">Telegram</option>
|
||||
<option value="feishu">{{ t("routeEditor.platformFeishu") }}</option>
|
||||
</select>
|
||||
<template v-if="tg.platform === 'discord'">
|
||||
<input
|
||||
|
|
@ -494,7 +499,7 @@ watch(
|
|||
:placeholder="t('routeEditor.threadPlaceholder')"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-else-if="tg.platform === 'telegram'">
|
||||
<input
|
||||
v-model="tg.chatId"
|
||||
class="input tg-in1"
|
||||
|
|
@ -506,6 +511,13 @@ watch(
|
|||
:placeholder="t('routeEditor.topicPlaceholder')"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<input
|
||||
v-model="tg.chatId"
|
||||
class="input tg-in1"
|
||||
:placeholder="t('routeEditor.feishuChatPlaceholder')"
|
||||
/>
|
||||
</template>
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn danger tg-del"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue