mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-23 00:21:28 +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
|
|
@ -234,10 +234,10 @@ function validateTarget(
|
|||
target: Record<string, unknown>,
|
||||
): { ok: true; target: Route["targets"][number] } | { ok: false; error: string } {
|
||||
const platform = target.platform === undefined ? "discord" : target.platform;
|
||||
if (platform !== "discord" && platform !== "telegram") {
|
||||
return { ok: false, error: `${label}.platform must be "discord" or "telegram"` };
|
||||
if (platform !== "discord" && platform !== "telegram" && platform !== "feishu") {
|
||||
return { ok: false, error: `${label}.platform must be "discord", "telegram" or "feishu"` };
|
||||
}
|
||||
if (platform === "telegram") {
|
||||
if (platform === "telegram" || platform === "feishu") {
|
||||
if (typeof target.chatId !== "string" || target.chatId.trim().length === 0)
|
||||
return { ok: false, error: `${label}.chatId is required` };
|
||||
if (target.topicId !== undefined && typeof target.topicId !== "string") {
|
||||
|
|
@ -254,9 +254,9 @@ function validateTarget(
|
|||
ok: true,
|
||||
target: {
|
||||
platform,
|
||||
channelId: platform === "telegram" ? undefined : (target.channelId as string),
|
||||
threadId: platform === "telegram" ? undefined : ((target.threadId as string) ?? undefined),
|
||||
chatId: platform === "telegram" ? (target.chatId as string) : undefined,
|
||||
channelId: platform === "discord" ? (target.channelId as string) : undefined,
|
||||
threadId: platform === "discord" ? ((target.threadId as string) ?? undefined) : undefined,
|
||||
chatId: platform === "telegram" || platform === "feishu" ? (target.chatId as string) : undefined,
|
||||
topicId: platform === "telegram" ? ((target.topicId as string) ?? undefined) : undefined,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
handleOAuthCallback as handleGithubOAuthCallback,
|
||||
getInstallationAccount,
|
||||
} from "../github/oauth";
|
||||
import { removeToken, saveDiscordLink, saveTelegramLink } from "../github/store";
|
||||
import { removeToken, saveDiscordLink, saveTelegramLink, saveFeishuLink } from "../github/store";
|
||||
import { createAdminSession, adminCookie, getAdminSession } from "./session";
|
||||
import {
|
||||
loadGroups,
|
||||
|
|
@ -27,6 +27,7 @@ import {
|
|||
import { clientIp } from "./auth";
|
||||
import { recordAudit } from "../lib/audit";
|
||||
import { sendMessage } from "../drivers/telegram/rest";
|
||||
import { getTenantAccessToken, sendText as sendFeishuText } from "../drivers/feishu/rest";
|
||||
import { cfEnv } from "../cf";
|
||||
import { initConfigStore } from "../config";
|
||||
import type { Env, Group } from "../types";
|
||||
|
|
@ -37,6 +38,8 @@ interface PendingState {
|
|||
discordUserId?: string;
|
||||
telegramUserId?: string;
|
||||
telegramChatId?: string;
|
||||
feishuUserId?: string;
|
||||
feishuChatId?: string;
|
||||
}
|
||||
|
||||
function linkedPage(login: string): string {
|
||||
|
|
@ -337,6 +340,22 @@ export async function handleOAuthCallback(event: H3Event): Promise<unknown> {
|
|||
return { ok: true, telegramUserId: pending.telegramUserId, login: result.login };
|
||||
}
|
||||
|
||||
// Feishu account-linking flow: bind the Feishu user to this GitHub account.
|
||||
if (pending.feishuUserId) {
|
||||
await saveFeishuLink(env.DB, pending.feishuUserId, result.userId);
|
||||
if (pending.feishuChatId) {
|
||||
const tokenRes = await getTenantAccessToken(env);
|
||||
if (tokenRes.ok && tokenRes.token) {
|
||||
await sendFeishuText(
|
||||
tokenRes.token,
|
||||
pending.feishuChatId,
|
||||
`✅ GitHub 账号已绑定:**@${result.login}**。现在可以用 /gh comment 评论了。`,
|
||||
).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
return { ok: true, feishuUserId: pending.feishuUserId, login: result.login };
|
||||
}
|
||||
|
||||
const isBrowser = (getHeader(event, "accept") ?? "").includes("text/html");
|
||||
if (isBrowser) {
|
||||
// Invite accept flow: the redirect target is the invite page, which
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue