diff --git a/AGENTS.md b/AGENTS.md index 9a8a103..0dedb60 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,7 @@ Core pipeline: GitHub Webhook → Worker (verify + filter + format) → Discord - Invites: single-use 7-day links (`invite:{token}`) for joining a group as admin/viewer; `ALLOW_SELF_SIGNUP=1` gives access-less users a personal group on first login (self-service SaaS entry) - Audit log: every admin operation (logins, group/route/member/invite changes) recorded in D1 `audit_logs`; pruned by the scheduled trigger after `AUDIT_RETENTION_DAYS` (default 90) - Group webhook log channel: optional `Group.logTarget` (Discord channel/thread or Telegram chat/topic) receives one summary message per webhook the group's routes dispatched (event, repo, delivery id, per route×target ✅/❌ outcome, green/red color); best-effort, not recorded in `send_logs` -- Per-group forge branding: optional `Group.forgeSources` (a list of `{ host, type: "github" | "gitea", name? }` the group defines itself) labels each message's footer with the first entry whose type matches the event's provider and whose host matches the repository URL's hostname (GitHub matches `github.com`, so two Gitea instances can be `git1.example.com`/`git2.example.com`); the label is the entry's optional `name` (fallback: host); links/favicons are derived from the repo URL; Discord shows the footer icon + name, Telegram a linked name +- Per-group forge branding: optional `Group.forgeSources` (a list of `{ host, type: "github" | "gitea", name? }` the group defines itself) labels each message's footer with the first entry whose type matches the event's provider and whose host matches the repository URL's hostname (GitHub matches `github.com`, so two Gitea instances can be `git1.example.com`/`git2.example.com`); the label is the entry's optional `name` (fallback: host); links are derived from the repo URL and footer icons use raster PNGs Discord renders (GitHub's `fluidicon.png`, Gitea's `/assets/img/favicon.png` — `.ico` favicons are silently ignored); Discord shows the footer icon + name, Telegram a linked name - Local dev: wrangler + Miniflare ## Architecture diff --git a/docs/guide/groups.md b/docs/guide/groups.md index a05db70..d192571 100644 --- a/docs/guide/groups.md +++ b/docs/guide/groups.md @@ -71,7 +71,7 @@ A group defines the forges it receives events from via `forgeSources`, a list of } ``` -- **Discord** — the embed footer shows the label next to the repo (`内网 Gitea · acme/widget`) with the site's favicon as the footer icon (derived from the repository URL). +- **Discord** — the embed footer shows the label next to the repo (`内网 Gitea · acme/widget`) with a site icon as the footer icon: GitHub's `fluidicon.png` or the Gitea instance's `/assets/img/favicon.png` (both raster PNGs — Discord silently ignores `.ico` favicons). - **Telegram** — the footer line starts with the hyperlinked label (`[内网 Gitea](https://git1.example.com)`). - Events whose repository host has no matching entry (e.g. custom webhooks, or a repo hosted elsewhere) get no label. diff --git a/docs/zh/guide/groups.md b/docs/zh/guide/groups.md index 7bf2f71..403f1c4 100644 --- a/docs/zh/guide/groups.md +++ b/docs/zh/guide/groups.md @@ -71,7 +71,7 @@ } ``` -- **Discord** — embed 底部在仓库名旁显示标识(`内网 Gitea · acme/widget`),并以按仓库 URL 推导的站点 favicon 作为底部图标。 +- **Discord** — embed 底部在仓库名旁显示标识(`内网 Gitea · acme/widget`),并以站点图标作为底部图标:GitHub 使用 `fluidicon.png`,Gitea 实例使用 `/assets/img/favicon.png`(均为 Discord 可渲染的栅格 PNG——`.ico` favicon 会被 Discord 静默忽略)。 - **Telegram** — 底部行以带超链接的标识开头(`[内网 Gitea](https://git1.example.com)`)。 - 仓库主机没有匹配条目的事件(如自定义 webhook,或仓库托管在别处)不显示标识。 diff --git a/server/lib/formatters/helpers.ts b/server/lib/formatters/helpers.ts index af6fd61..c0b7d29 100644 --- a/server/lib/formatters/helpers.ts +++ b/server/lib/formatters/helpers.ts @@ -166,8 +166,9 @@ export function senderProfileUrl(repoUrl: string | undefined, login: string): st * The event's repository host (github.com for GitHub, the instance hostname * for Gitea) is matched case-insensitively against the configured hosts; the * first entry whose type and host both match wins. The footer label is the - * entry's display `name` (or its host when no name is set). Link/favicon are - * derived from the repository URL. + * entry's display `name` (or its host when no name is set). Link is derived + * from the repository URL; the footer icon uses a raster PNG Discord can + * render (GitHub's fluidicon, Gitea's /assets/img/favicon.png). */ export function forgeInfo(event: WebhookEvent, sources?: ForgeSource[]): NeutralForge | undefined { const repoUrl = (event.payload.repository as { html_url?: string } | undefined)?.html_url; @@ -187,20 +188,22 @@ export function forgeInfo(event: WebhookEvent, sources?: ForgeSource[]): Neutral if (!source) return undefined; const label = source.name?.trim() || source.host; + const githubIcon = "https://github.com/fluidicon.png"; if (repoUrl) { try { const origin = new URL(repoUrl).origin; - return { name: label, url: origin, iconUrl: `${origin}/favicon.ico` }; + return { + name: label, + url: origin, + iconUrl: + event.provider === "github" ? githubIcon : `${origin}/assets/img/favicon.png`, + }; } catch { // unparseable repo URL — name only } } if (event.provider === "github") { - return { - name: label, - url: "https://github.com", - iconUrl: "https://github.com/favicon.ico", - }; + return { name: label, url: "https://github.com", iconUrl: githubIcon }; } return { name: label }; } diff --git a/tests/discord.test.ts b/tests/discord.test.ts index 1cb6bb1..620ea1b 100644 --- a/tests/discord.test.ts +++ b/tests/discord.test.ts @@ -91,12 +91,12 @@ describe("discord role mentions", () => { forge: { name: "GitHub", url: "https://github.com", - iconUrl: "https://github.com/favicon.ico", + iconUrl: "https://github.com/fluidicon.png", }, }); expect(out.embeds?.[0]?.footer).toEqual({ text: "GitHub · acme/widget", - icon_url: "https://github.com/favicon.ico", + icon_url: "https://github.com/fluidicon.png", }); }); }); @@ -451,7 +451,7 @@ describe("dispatchEvent fallback routing", () => { }); expect(footers).toContainEqual({ text: "内网 Gitea · owner/repo", - icon_url: "https://git.example.com/favicon.ico", + icon_url: "https://git.example.com/assets/img/favicon.png", }); expect(footers).toContainEqual({ text: "owner/repo" }); }); diff --git a/tests/formatter.test.ts b/tests/formatter.test.ts index 9d55778..f65ce7f 100644 --- a/tests/formatter.test.ts +++ b/tests/formatter.test.ts @@ -535,7 +535,7 @@ describe("forge source branding", () => { ).toEqual({ name: "github.com", url: "https://github.com", - iconUrl: "https://github.com/favicon.ico", + iconUrl: "https://github.com/fluidicon.png", }); }); @@ -543,7 +543,7 @@ describe("forge source branding", () => { expect(forgeInfo({ event: "ping", provider: "github", payload: {} }, [ghHost])).toEqual({ name: "github.com", url: "https://github.com", - iconUrl: "https://github.com/favicon.ico", + iconUrl: "https://github.com/fluidicon.png", }); }); @@ -564,7 +564,7 @@ describe("forge source branding", () => { ).toEqual({ name: "git1.example.com", url: "https://git1.example.com", - iconUrl: "https://git1.example.com/favicon.ico", + iconUrl: "https://git1.example.com/assets/img/favicon.png", }); expect( forgeInfo( @@ -578,7 +578,7 @@ describe("forge source branding", () => { ).toEqual({ name: "git2.example.com", url: "https://git2.example.com", - iconUrl: "https://git2.example.com/favicon.ico", + iconUrl: "https://git2.example.com/assets/img/favicon.png", }); }); @@ -599,7 +599,7 @@ describe("forge source branding", () => { ).toEqual({ name: "内网 Gitea", url: "https://git1.example.com", - iconUrl: "https://git1.example.com/favicon.ico", + iconUrl: "https://git1.example.com/assets/img/favicon.png", }); expect( forgeInfo( @@ -613,7 +613,7 @@ describe("forge source branding", () => { ).toEqual({ name: "git2.example.com", url: "https://git2.example.com", - iconUrl: "https://git2.example.com/favicon.ico", + iconUrl: "https://git2.example.com/assets/img/favicon.png", }); expect( forgeInfo( @@ -627,7 +627,7 @@ describe("forge source branding", () => { ).toEqual({ name: "git1.example.com", url: "https://git1.example.com", - iconUrl: "https://git1.example.com/favicon.ico", + iconUrl: "https://git1.example.com/assets/img/favicon.png", }); }); @@ -644,7 +644,7 @@ describe("forge source branding", () => { ).toEqual({ name: "Git1.Example.com", url: "https://git1.example.com", - iconUrl: "https://git1.example.com/favicon.ico", + iconUrl: "https://git1.example.com/assets/img/favicon.png", }); });