chore: auto-fix lint & formatting [skip ci]

This commit is contained in:
github-actions[bot] 2026-08-02 21:19:33 +00:00
parent bd7a8f2632
commit 568e206643
10 changed files with 98 additions and 50 deletions

View file

@ -312,5 +312,4 @@ function save(): void {
} }
emit("save", route); emit("save", route);
} }
</script> </script>

View file

@ -22,7 +22,9 @@
<span class="dot" :class="l.ok ? 'ok' : 'bad'"></span> <span class="dot" :class="l.ok ? 'ok' : 'bad'"></span>
<span class="log-route">{{ l.routeId }}</span> <span class="log-route">{{ l.routeId }}</span>
<span class="log-event">{{ l.event }}</span> <span class="log-event">{{ l.event }}</span>
<span v-if="l.status" class="log-status" :class="l.ok ? 'ok' : 'bad'">{{ l.status }}</span> <span v-if="l.status" class="log-status" :class="l.ok ? 'ok' : 'bad'">{{
l.status
}}</span>
<span class="log-time">{{ fmtTime(l.ts) }}</span> <span class="log-time">{{ fmtTime(l.ts) }}</span>
</div> </div>
<div class="log-meta"> <div class="log-meta">

View file

@ -7,7 +7,7 @@ WebHooker requires several secrets to function. For local development, store the
### Required Secrets ### Required Secrets
| Variable | Description | | Variable | Description |
| ----------------------- | ----------------------------------------------- | | ----------------------- | ------------------------------------------------------------------ |
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from your GitHub App settings | | `GITHUB_WEBHOOK_SECRET` | Webhook secret from your GitHub App settings |
| `GITHUB_APP_ID` | Numeric ID of your GitHub App | | `GITHUB_APP_ID` | Numeric ID of your GitHub App |
| `GITHUB_PRIVATE_KEY` | App private key (PEM format, with `\n` escapes) | | `GITHUB_PRIVATE_KEY` | App private key (PEM format, with `\n` escapes) |
@ -19,7 +19,7 @@ WebHooker requires several secrets to function. For local development, store the
### Optional Secrets ### Optional Secrets
| Variable | Description | Default | | Variable | Description | Default |
| ------------------------ | ----------------------------------------------------------------------------- | --------------------------------- | | ------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------- |
| `DISCORD_PUBLIC_KEY` | Discord application public key (Developer Portal) — required for interactions | Unset → interactions return `401` | | `DISCORD_PUBLIC_KEY` | Discord application public key (Developer Portal) — required for interactions | Unset → interactions return `401` |
| `DISCORD_APPLICATION_ID` | Discord application id; auto-resolved when omitted | Auto-resolved | | `DISCORD_APPLICATION_ID` | Discord application id; auto-resolved when omitted | Auto-resolved |
| `TELEGRAM_WEBHOOK_SECRET` | Secret token for `POST /telegram/webhook` verification (X-Telegram-Bot-Api-Secret-Token) | Disabled (no verification) | | `TELEGRAM_WEBHOOK_SECRET` | Secret token for `POST /telegram/webhook` verification (X-Telegram-Bot-Api-Secret-Token) | Disabled (no verification) |

View file

@ -7,7 +7,7 @@ WebHooker 需要多个密钥才能运行。本地开发时存储在 `.dev.vars`
### 必需密钥 ### 必需密钥
| 变量 | 说明 | | 变量 | 说明 |
| ----------------------- | ---------------------------------- | | ----------------------- | -------------------------------------------------------- |
| `GITHUB_WEBHOOK_SECRET` | GitHub App 设置中的 Webhook 密钥 | | `GITHUB_WEBHOOK_SECRET` | GitHub App 设置中的 Webhook 密钥 |
| `GITHUB_APP_ID` | GitHub App 的数字 ID | | `GITHUB_APP_ID` | GitHub App 的数字 ID |
| `GITHUB_PRIVATE_KEY` | App 私钥PEM 格式,用 `\n` 转义) | | `GITHUB_PRIVATE_KEY` | App 私钥PEM 格式,用 `\n` 转义) |
@ -19,7 +19,7 @@ WebHooker 需要多个密钥才能运行。本地开发时存储在 `.dev.vars`
### 可选密钥 ### 可选密钥
| 变量 | 说明 | 默认值 | | 变量 | 说明 | 默认值 |
| ------------------------ | ------------------------------------------------------ | ----------------------- | | ------------------------- | -------------------------------------------------------------------- | ----------------------- |
| `BASE_URL` | OAuth 回调的公开 URL | `http://localhost:8787` | | `BASE_URL` | OAuth 回调的公开 URL | `http://localhost:8787` |
| `ADMIN_USER_IDS` | 允许访问 WebUI 的 GitHub 用户 ID或登录名逗号分隔 | 未设置时 WebUI 关闭 | | `ADMIN_USER_IDS` | 允许访问 WebUI 的 GitHub 用户 ID或登录名逗号分隔 | 未设置时 WebUI 关闭 |
| `DISCORD_PUBLIC_KEY` | Discord 应用的公钥(开发者门户获取),交互功能必需 | 未设置时交互返回 401 | | `DISCORD_PUBLIC_KEY` | Discord 应用的公钥(开发者门户获取),交互功能必需 | 未设置时交互返回 401 |

View file

@ -38,14 +38,18 @@ function createMockDB(): D1Database {
prepare: (sql: string) => ({ prepare: (sql: string) => ({
bind: (...args: unknown[]) => ({ bind: (...args: unknown[]) => ({
run: async (): Promise<{ success: boolean }> => { run: async (): Promise<{ success: boolean }> => {
const m = sql.match(/INSERT OR REPLACE INTO telegram_links \(telegram_user_id, github_user_id\) VALUES \(\?, \?\)/); const m = sql.match(
/INSERT OR REPLACE INTO telegram_links \(telegram_user_id, github_user_id\) VALUES \(\?, \?\)/,
);
if (m) links.set(String(args[0]), String(args[1])); if (m) links.set(String(args[0]), String(args[1]));
const del = sql.match(/DELETE FROM telegram_links WHERE telegram_user_id = \?/); const del = sql.match(/DELETE FROM telegram_links WHERE telegram_user_id = \?/);
if (del) links.delete(String(args[0])); if (del) links.delete(String(args[0]));
return { success: true }; return { success: true };
}, },
all: async (): Promise<{ results: Array<Record<string, unknown>> }> => { all: async (): Promise<{ results: Array<Record<string, unknown>> }> => {
const sel = sql.match(/SELECT github_user_id FROM telegram_links WHERE telegram_user_id = \?/); const sel = sql.match(
/SELECT github_user_id FROM telegram_links WHERE telegram_user_id = \?/,
);
if (sel) { if (sel) {
const val = links.get(String(args[0])); const val = links.get(String(args[0]));
return { results: val ? [{ github_user_id: val }] : [] }; return { results: val ? [{ github_user_id: val }] : [] };
@ -146,7 +150,9 @@ describe("telegram-commands /gh comment", () => {
mockFetch((url, init) => { mockFetch((url, init) => {
calls.push({ url, body: String(init!.body) }); calls.push({ url, body: String(init!.body) });
if (String(url).endsWith("/sendMessage")) { if (String(url).endsWith("/sendMessage")) {
return new Response(JSON.stringify({ ok: true, result: { message_id: 1 } }), { status: 200 }); return new Response(JSON.stringify({ ok: true, result: { message_id: 1 } }), {
status: 200,
});
} }
return new Response( return new Response(
JSON.stringify({ html_url: "https://github.com/acme/widget/issues/7#issuecomment-9" }), JSON.stringify({ html_url: "https://github.com/acme/widget/issues/7#issuecomment-9" }),
@ -185,7 +191,9 @@ describe("telegram-updates webhook", () => {
}); });
it("accepts requests with the correct secret token", async () => { it("accepts requests with the correct secret token", async () => {
mockFetch(() => new Response(JSON.stringify({ ok: true, result: { message_id: 1 } }), { status: 200 })); mockFetch(
() => new Response(JSON.stringify({ ok: true, result: { message_id: 1 } }), { status: 200 }),
);
const env = createEnv(); const env = createEnv();
const res = await handleTelegramWebhookRequest( const res = await handleTelegramWebhookRequest(
new Request("https://example.com/telegram/webhook", { new Request("https://example.com/telegram/webhook", {

View file

@ -23,14 +23,16 @@ describe("telegram renderNeutralMessage", () => {
footer: "acme/widget", footer: "acme/widget",
}; };
const out = renderNeutralMessage(message); const out = renderNeutralMessage(message);
expect(out).toContain('<b><a href="https://github.com/acme/widget">acme/widget: Add feature</a></b>'); expect(out).toContain(
'<b><a href="https://github.com/acme/widget">acme/widget: Add feature</a></b>',
);
expect(out).toContain("<b>Status</b>: success"); expect(out).toContain("<b>Status</b>: success");
expect(out).toContain("<i>acme/widget</i>"); expect(out).toContain("<i>acme/widget</i>");
}); });
it("escapes HTML special characters", () => { it("escapes HTML special characters", () => {
const out = renderNeutralMessage({ const out = renderNeutralMessage({
title: "a <b> & \"c\"", title: 'a <b> & "c"',
fields: [{ name: "body", value: "<script>alert(1)</script>" }], fields: [{ name: "body", value: "<script>alert(1)</script>" }],
}); });
expect(out).not.toContain("<b>acme"); expect(out).not.toContain("<b>acme");
@ -76,7 +78,8 @@ describe("telegram-rest sendMessage", () => {
}); });
it("returns error on non-ok response", async () => { it("returns error on non-ok response", async () => {
mockFetch(() => mockFetch(
() =>
new Response(JSON.stringify({ ok: false, description: "chat not found" }), { status: 400 }), new Response(JSON.stringify({ ok: false, description: "chat not found" }), { status: 400 }),
); );
const result = await sendMessage("t", "-100123", "hello"); const result = await sendMessage("t", "-100123", "hello");

View file

@ -72,7 +72,7 @@ export async function dispatchEvent(config: Config, event: WebhookEvent, env: En
target: targetStr, target: targetStr,
deliveryId: event.deliveryId, deliveryId: event.deliveryId,
actor: (event.payload.sender as { login?: string } | undefined)?.login, actor: (event.payload.sender as { login?: string } | undefined)?.login,
action: (event.payload.action as string | undefined), action: event.payload.action as string | undefined,
}; };
const started = Date.now(); const started = Date.now();

View file

@ -54,7 +54,12 @@ function extractTarget(msg: TelegramMessage, prOnly = false): Target | null {
return null; return null;
} }
async function reply(env: Env, chatId: string, topicId: string | undefined, text: string): Promise<void> { async function reply(
env: Env,
chatId: string,
topicId: string | undefined,
text: string,
): Promise<void> {
await sendMessage(env.TELEGRAM_TOKEN ?? "", chatId, text, topicId); await sendMessage(env.TELEGRAM_TOKEN ?? "", chatId, text, topicId);
} }
@ -73,7 +78,8 @@ async function cmdLogin(env: Env, msg: TelegramMessage): Promise<void> {
const topicId = msg.message_thread_id != null ? String(msg.message_thread_id) : undefined; const topicId = msg.message_thread_id != null ? String(msg.message_thread_id) : undefined;
const clientId = env.GITHUB_CLIENT_ID; const clientId = env.GITHUB_CLIENT_ID;
if (!clientId) return reply(env, chatId, topicId, "服务器未配置 GitHub OAuthGITHUB_CLIENT_ID。"); if (!clientId)
return reply(env, chatId, topicId, "服务器未配置 GitHub OAuthGITHUB_CLIENT_ID。");
const state = crypto.randomUUID().replace(/-/g, ""); const state = crypto.randomUUID().replace(/-/g, "");
await env.KV.put( await env.KV.put(
@ -87,7 +93,12 @@ async function cmdLogin(env: Env, msg: TelegramMessage): Promise<void> {
{ expirationTtl: 600 }, { expirationTtl: 600 },
); );
const url = getOAuthURL(clientId, state); const url = getOAuthURL(clientId, state);
await reply(env, chatId, topicId, `点击链接授权 GitHub即可用**本人身份**评论10 分钟内有效):\n${url}`); await reply(
env,
chatId,
topicId,
`点击链接授权 GitHub即可用**本人身份**评论10 分钟内有效):\n${url}`,
);
} }
async function cmdLogout(env: Env, msg: TelegramMessage): Promise<void> { async function cmdLogout(env: Env, msg: TelegramMessage): Promise<void> {
@ -165,7 +176,12 @@ async function cmdMergeClose(env: Env, msg: TelegramMessage, op: "merge" | "clos
await closePullRequestAsUser(env.KV, githubUserId, target.owner, target.repo, target.number); await closePullRequestAsUser(env.KV, githubUserId, target.owner, target.repo, target.number);
} }
const label = op === "merge" ? "合并" : "关闭"; const label = op === "merge" ? "合并" : "关闭";
await reply(env, chatId, topicId, `✅ 已${label} PR ${target.owner}/${target.repo}#${target.number}`); await reply(
env,
chatId,
topicId,
`✅ 已${label} PR ${target.owner}/${target.repo}#${target.number}`,
);
} catch (err) { } catch (err) {
await reply(env, chatId, topicId, errText(err)); await reply(env, chatId, topicId, errText(err));
} }

View file

@ -52,7 +52,10 @@ export async function sendMessage(
if (!res.ok) { if (!res.ok) {
lastError = data?.description ?? `HTTP ${res.status}`; lastError = data?.description ?? `HTTP ${res.status}`;
log.error({ status: res.status, err: lastError, chatId, attempts: attempt + 1 }, "Telegram API error"); log.error(
{ status: res.status, err: lastError, chatId, attempts: attempt + 1 },
"Telegram API error",
);
return { return {
ok: false, ok: false,
error: lastError, error: lastError,
@ -72,11 +75,23 @@ export async function sendMessage(
lastError = err instanceof Error ? err.message : String(err); lastError = err instanceof Error ? err.message : String(err);
log.error({ err, chatId, attempts: attempt + 1 }, "Failed to send Telegram message"); log.error({ err, chatId, attempts: attempt + 1 }, "Failed to send Telegram message");
if (attempt === 2) { if (attempt === 2) {
return { ok: false, error: lastError, errorCode: "NETWORK", status: lastStatus, attempts: attempt + 1 }; return {
ok: false,
error: lastError,
errorCode: "NETWORK",
status: lastStatus,
attempts: attempt + 1,
};
} }
await new Promise((r) => setTimeout(r, 500 * (attempt + 1))); await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
} }
} }
return { ok: false, error: lastError || "Failed to send Telegram message", errorCode: "RETRIES", status: lastStatus, attempts: 3 }; return {
ok: false,
error: lastError || "Failed to send Telegram message",
errorCode: "RETRIES",
status: lastStatus,
attempts: 3,
};
} }

View file

@ -104,7 +104,9 @@ export async function saveTelegramLink(
githubUserId: string, githubUserId: string,
): Promise<void> { ): Promise<void> {
await db await db
.prepare("INSERT OR REPLACE INTO telegram_links (telegram_user_id, github_user_id) VALUES (?, ?)") .prepare(
"INSERT OR REPLACE INTO telegram_links (telegram_user_id, github_user_id) VALUES (?, ?)",
)
.bind(telegramUserId, githubUserId) .bind(telegramUserId, githubUserId)
.run(); .run();
} }
@ -121,5 +123,8 @@ export async function getTelegramLink(
} }
export async function removeTelegramLink(db: D1Database, telegramUserId: string): Promise<void> { export async function removeTelegramLink(db: D1Database, telegramUserId: string): Promise<void> {
await db.prepare("DELETE FROM telegram_links WHERE telegram_user_id = ?").bind(telegramUserId).run(); await db
.prepare("DELETE FROM telegram_links WHERE telegram_user_id = ?")
.bind(telegramUserId)
.run();
} }