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);
}
</script>

View file

@ -22,7 +22,9 @@
<span class="dot" :class="l.ok ? 'ok' : 'bad'"></span>
<span class="log-route">{{ l.routeId }}</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>
</div>
<div class="log-meta">

View file

@ -6,25 +6,25 @@ WebHooker requires several secrets to function. For local development, store the
### Required Secrets
| Variable | Description |
| ----------------------- | ----------------------------------------------- |
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from your GitHub App settings |
| `GITHUB_APP_ID` | Numeric ID of your GitHub App |
| `GITHUB_PRIVATE_KEY` | App private key (PEM format, with `\n` escapes) |
| `GITHUB_CLIENT_ID` | OAuth client ID from App settings |
| `GITHUB_CLIENT_SECRET` | OAuth client secret from App settings |
| `DISCORD_TOKEN` | Discord bot token |
| Variable | Description |
| ----------------------- | ------------------------------------------------------------------ |
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from your GitHub App settings |
| `GITHUB_APP_ID` | Numeric ID of your GitHub App |
| `GITHUB_PRIVATE_KEY` | App private key (PEM format, with `\n` escapes) |
| `GITHUB_CLIENT_ID` | OAuth client ID from App settings |
| `GITHUB_CLIENT_SECRET` | OAuth client secret from App settings |
| `DISCORD_TOKEN` | Discord bot token |
| `TELEGRAM_TOKEN` | Telegram bot token (from BotFather) — required for Telegram routes |
### Optional Secrets
| Variable | Description | Default |
| ------------------------ | ----------------------------------------------------------------------------- | --------------------------------- |
| `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 |
| `TELEGRAM_WEBHOOK_SECRET`| Secret token for `POST /telegram/webhook` verification (X-Telegram-Bot-Api-Secret-Token) | Disabled (no verification) |
| `BASE_URL` | Public URL for OAuth callbacks | `http://localhost:8787` |
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access the Web UI | Disabled |
| Variable | Description | Default |
| ------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------- |
| `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 |
| `TELEGRAM_WEBHOOK_SECRET` | Secret token for `POST /telegram/webhook` verification (X-Telegram-Bot-Api-Secret-Token) | Disabled (no verification) |
| `BASE_URL` | Public URL for OAuth callbacks | `http://localhost:8787` |
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access the Web UI | Disabled |
## Web UI

View file

@ -6,25 +6,25 @@ WebHooker 需要多个密钥才能运行。本地开发时存储在 `.dev.vars`
### 必需密钥
| 变量 | 说明 |
| ----------------------- | ---------------------------------- |
| `GITHUB_WEBHOOK_SECRET` | GitHub App 设置中的 Webhook 密钥 |
| `GITHUB_APP_ID` | GitHub App 的数字 ID |
| `GITHUB_PRIVATE_KEY` | App 私钥PEM 格式,用 `\n` 转义) |
| `GITHUB_CLIENT_ID` | App 设置中的 OAuth 客户端 ID |
| `GITHUB_CLIENT_SECRET` | App 设置中的 OAuth 客户端密钥 |
| `DISCORD_TOKEN` | Discord Bot Token |
| 变量 | 说明 |
| ----------------------- | -------------------------------------------------------- |
| `GITHUB_WEBHOOK_SECRET` | GitHub App 设置中的 Webhook 密钥 |
| `GITHUB_APP_ID` | GitHub App 的数字 ID |
| `GITHUB_PRIVATE_KEY` | App 私钥PEM 格式,用 `\n` 转义) |
| `GITHUB_CLIENT_ID` | App 设置中的 OAuth 客户端 ID |
| `GITHUB_CLIENT_SECRET` | App 设置中的 OAuth 客户端密钥 |
| `DISCORD_TOKEN` | Discord Bot Token |
| `TELEGRAM_TOKEN` | Telegram Bot TokenBotFather 获取)—— Telegram 路由必需 |
### 可选密钥
| 变量 | 说明 | 默认值 |
| ------------------------ | ------------------------------------------------------ | ----------------------- |
| `BASE_URL` | OAuth 回调的公开 URL | `http://localhost:8787` |
| `ADMIN_USER_IDS` | 允许访问 WebUI 的 GitHub 用户 ID或登录名逗号分隔 | 未设置时 WebUI 关闭 |
| `DISCORD_PUBLIC_KEY` | Discord 应用的公钥(开发者门户获取),交互功能必需 | 未设置时交互返回 401 |
| `DISCORD_APPLICATION_ID` | Discord 应用 ID省略时自动获取 | 自动获取 |
| `TELEGRAM_WEBHOOK_SECRET`| `POST /telegram/webhook` 验签密钥X-Telegram-Bot-Api-Secret-Token | 未设置时不校验 |
| 变量 | 说明 | 默认值 |
| ------------------------- | -------------------------------------------------------------------- | ----------------------- |
| `BASE_URL` | OAuth 回调的公开 URL | `http://localhost:8787` |
| `ADMIN_USER_IDS` | 允许访问 WebUI 的 GitHub 用户 ID或登录名逗号分隔 | 未设置时 WebUI 关闭 |
| `DISCORD_PUBLIC_KEY` | Discord 应用的公钥(开发者门户获取),交互功能必需 | 未设置时交互返回 401 |
| `DISCORD_APPLICATION_ID` | Discord 应用 ID省略时自动获取 | 自动获取 |
| `TELEGRAM_WEBHOOK_SECRET` | `POST /telegram/webhook` 验签密钥X-Telegram-Bot-Api-Secret-Token | 未设置时不校验 |
## Web 控制台

View file

@ -38,14 +38,18 @@ function createMockDB(): D1Database {
prepare: (sql: string) => ({
bind: (...args: unknown[]) => ({
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]));
const del = sql.match(/DELETE FROM telegram_links WHERE telegram_user_id = \?/);
if (del) links.delete(String(args[0]));
return { success: true };
},
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) {
const val = links.get(String(args[0]));
return { results: val ? [{ github_user_id: val }] : [] };
@ -146,7 +150,9 @@ describe("telegram-commands /gh comment", () => {
mockFetch((url, init) => {
calls.push({ url, body: String(init!.body) });
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(
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 () => {
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 res = await handleTelegramWebhookRequest(
new Request("https://example.com/telegram/webhook", {

View file

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

View file

@ -72,7 +72,7 @@ export async function dispatchEvent(config: Config, event: WebhookEvent, env: En
target: targetStr,
deliveryId: event.deliveryId,
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();

View file

@ -54,7 +54,12 @@ function extractTarget(msg: TelegramMessage, prOnly = false): Target | 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);
}
@ -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 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, "");
await env.KV.put(
@ -87,7 +93,12 @@ async function cmdLogin(env: Env, msg: TelegramMessage): Promise<void> {
{ expirationTtl: 600 },
);
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> {
@ -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);
}
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) {
await reply(env, chatId, topicId, errText(err));
}

View file

@ -52,7 +52,10 @@ export async function sendMessage(
if (!res.ok) {
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 {
ok: false,
error: lastError,
@ -72,11 +75,23 @@ export async function sendMessage(
lastError = err instanceof Error ? err.message : String(err);
log.error({ err, chatId, attempts: attempt + 1 }, "Failed to send Telegram message");
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)));
}
}
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,
): Promise<void> {
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)
.run();
}
@ -121,5 +123,8 @@ export async function getTelegramLink(
}
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();
}