WebHooker/docs/guide/groups.md
RhenCloud 3dce114cec
feat(groups): optional forge source label in message footers
- Group.forgeLabel (default off, editable per group in the console) shows the
  source forge on every message the group's routes dispatch
- Discord embed footer: forge name next to the repo + the site's favicon as
  icon_url (Gitea instance's own favicon from its origin)
- Telegram footer: hyperlinked site name (GitHub or Gitea hostname); custom
  webhooks render a plain 'Custom' label
- forgeInfo() derives branding from event.provider + repository.html_url;
  dispatch attaches it to the NeutralMessage like mentionRoleIds
- validateGroups accepts forgeLabel booleans; GroupEditor gains the toggle
- tests: forgeInfo unit, discord/telegram footer render, dispatch on/off
- docs: groups.md + message-format.md (en/zh), config.example.yaml, AGENTS.md
- fix: wrangler.jsonc compatibility_date was an incomplete '2026-'
2026-08-14 08:10:04 +08:00

7.1 KiB
Raw Blame History

Groups & Access Control

Routes belong to groups. Groups scope admin access and can restrict which events flow into them. They are stored in Cloudflare KV under the key config:groups as a JSON array, managed via the Web UI or the Admin API. At most 100 groups can be saved per instance.

Group Schema

{
  "id": "backend-team",
  "name": "Backend Team",
  "members": [
    { "login": "rhencloud", "role": "owner" },
    { "login": "octobot", "role": "admin" },
    { "login": "reader", "role": "viewer" }
  ],
  "owners": ["myorg"],
  "providers": ["github", "gitea"],
  "installationId": 12345678,
  "logTarget": { "platform": "discord", "channelId": "123456789", "threadId": "987654321" }
}
Field Type Required Description
id string Yes Lowercase id (a-z0-9, -); referenced by each route's groupId. Editable: renaming a group re-points its routes, per-group webhook secret and pending invites
name string Yes Human-readable group name
members object[] No { login, role } entries; role is owner, admin, or viewer
adminIds string[] No Deprecated legacy field; treated as members with role owner when present
owners string[] No Org/user logins whose events are accepted into this group; empty = all
providers string[] No Source platforms allowed into this group (github, gitea); empty = all
installationId number No GitHub App installation id bound to this group; only that installation's events are accepted (empty = all)
emoji boolean No Whether to include emoji in this group's messages (default true)
forgeLabel boolean No Whether to show the forge source (GitHub / Gitea instance / custom) in the footer of this group's messages (default false)
lang string No Message language for every route in this group (e.g. en, zh; custom via KV i18n:<lang>) — see Message Language — defaults to en
logTarget object No Webhook log channel: a Discord { platform, channelId, threadId? } or Telegram { platform, chatId, topicId? } target that receives a summary of every webhook the group's routes dispatch

Roles

Every group member has one of three roles. Super admins (ADMIN_USER_IDS) always bypass them.

Role View routes/logs Edit routes Manage members & invites Edit group settings
owner ✓ (except owners)
admin
viewer ✓ (read-only)

Access Model

  • Super admins (ADMIN_USER_IDS) see and edit every group and all routes; only they can edit a group's owners list.
  • Owners manage their group's routes, members, invites, name, id, emoji, and providers. They cannot remove the last owner or demote themselves when no other owner remains.
  • Admins edit routes inside their groups and view logs; viewers get a read-only console.
  • Group admin endpoints operate on a single group at a time via /admin/api/groups/:id/routes; groupId is forced from the path parameter.
  • The owners list restricts which event actors (sender logins) the group's routes will dispatch at all.
  • The providers list restricts which forge's events (github, gitea) the group's routes will dispatch. This lets you keep GitHub and Gitea groups separate even when org/user names collide.

Webhook Log Channel

A group may set logTarget to a Discord channel/thread or Telegram chat/topic. Whenever the group's routes dispatch a webhook, a single summary message is sent there: the event type/action, the repo, the delivery id, and one line per route×target with an / outcome (including the error for failed sends; at most the first 10 lines are listed, the rest is summarized as +N). The message is green when every dispatch succeeded and red when any failed. The summary uses the group's message language. Log messages are sent best-effort and are not themselves recorded in the D1 send log.

Forge Source Label

With forgeLabel: true, every message this group's routes send carries the source forge in its footer, so events from GitHub, a self-hosted Gitea instance and custom webhooks are easy to tell apart when they share a channel:

  • Discord — the embed footer shows the forge name next to the repo (GitHub · acme/widget) with the site's favicon as the footer icon (for Gitea the instance's own favicon is fetched from its origin).
  • Telegram — the footer line starts with the hyperlinked site name ([GitHub](https://github.com) or the Gitea instance hostname).
  • Custom webhooks are labeled Custom without a link.

The label is independent of Group.emoji and follows every message that group dispatches (including in-place edits of workflow/check messages).

Invites

Owners (and super admins) can create single-use invite links valid for 7 days from the group's Members panel. Accepting an invite adds the user with the invited role (admin or viewer — never owner); an existing viewer is upgraded to admin. Invites are stored in KV as invite:{token}.

Self Sign-up

With ALLOW_SELF_SIGNUP=1, a GitHub user who has no group access gets a personal group (u-{userId}, owned by them) on first login instead of a 403. This is the entry point for a fully self-service SaaS install; disable it to keep the console invite-only.