mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-23 00:21:28 +00:00
feat: add fallback routes and repository event enrichment
This commit is contained in:
parent
44b8fead78
commit
d419b9c940
11 changed files with 104 additions and 8 deletions
|
|
@ -83,6 +83,9 @@ function validateRoutes(
|
|||
if (r.lang !== undefined && typeof r.lang !== "string") {
|
||||
return { ok: false, error: `route "${r.id}".lang must be a string` };
|
||||
}
|
||||
if (r.fallback !== undefined && typeof r.fallback !== "boolean") {
|
||||
return { ok: false, error: `route "${r.id}".fallback must be a boolean` };
|
||||
}
|
||||
if (!Array.isArray(r.filters) || r.filters.length === 0) {
|
||||
return { ok: false, error: `route "${r.id}" needs at least one filter` };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Config, FormattedMessage, WebhookEvent, Env } from "./types";
|
||||
import type { Config, FormattedMessage, WebhookEvent, Env, Route } from "./types";
|
||||
import { formatEvent } from "./formatter";
|
||||
import { matchRoute, eventOwners } from "./webhook";
|
||||
import { log } from "./log";
|
||||
|
|
@ -42,13 +42,20 @@ export async function dispatchEvent(config: Config, event: WebhookEvent, env: En
|
|||
const groupById = new Map(groups.map((g) => [g.id, g]));
|
||||
const owners = eventOwners(event);
|
||||
|
||||
for (const route of config.routes) {
|
||||
if (!matchRoute(route, event)) continue;
|
||||
// Only routes that pass both their filters and their group's owner
|
||||
// restriction count as "matched", so a fallback route still fires when a
|
||||
// regular route was suppressed by its group.
|
||||
const accepted = (route: Route): boolean => {
|
||||
if (!route.groupId) return true;
|
||||
const group = groupById.get(route.groupId);
|
||||
return !group || groupAcceptsOwners(group, owners);
|
||||
};
|
||||
const matched = config.routes.filter((route) => matchRoute(route, event) && accepted(route));
|
||||
const anyRegularMatched = matched.some((route) => !route.fallback);
|
||||
|
||||
if (route.groupId) {
|
||||
const group = groupById.get(route.groupId);
|
||||
if (group && !groupAcceptsOwners(group, owners)) continue;
|
||||
}
|
||||
for (const route of matched) {
|
||||
// A fallback route only fires when no regular route matched the event.
|
||||
if (route.fallback && anyRegularMatched) continue;
|
||||
|
||||
const target = route.target.threadId
|
||||
? `${route.target.channelId}/${route.target.threadId}`
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ export function formatEvent(route: Route, event: WebhookEvent, tr?: Translations
|
|||
case "discussion_comment":
|
||||
return formatDiscussionComment(payload, repo, author, t);
|
||||
case "repository":
|
||||
return formatRepository(payload, repo, author, t);
|
||||
return formatRepository(payload, repo, repoUrl, author, t);
|
||||
case "code_scanning_alert":
|
||||
return formatCodeScanningAlert(payload, repo, author, t);
|
||||
case "dependabot_alert":
|
||||
|
|
@ -1231,6 +1231,7 @@ function formatDiscussionComment(
|
|||
function formatRepository(
|
||||
payload: Record<string, unknown>,
|
||||
repo: string | undefined,
|
||||
repoUrl: string | undefined,
|
||||
author: { name: string; icon_url?: string; url?: string },
|
||||
t: T,
|
||||
): FormattedMessage {
|
||||
|
|
@ -1261,12 +1262,50 @@ function formatRepository(
|
|||
});
|
||||
}
|
||||
|
||||
// Enrich create / visibility-change notifications with a clickable link and
|
||||
// basic metadata. repoUrl also makes the embed title a hyperlink for all actions.
|
||||
const repoData = payload.repository as {
|
||||
visibility?: string;
|
||||
fork?: boolean;
|
||||
description?: string | null;
|
||||
};
|
||||
|
||||
const isCreateOrVisibility =
|
||||
action === "created" || action === "publicized" || action === "privatized";
|
||||
|
||||
if (isCreateOrVisibility) {
|
||||
if (repoData.visibility) {
|
||||
fields.push({
|
||||
name: t("events.repository.visibility"),
|
||||
value: t("events.repository." + repoData.visibility) ?? repoData.visibility,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
if (repoData.fork) {
|
||||
fields.push({
|
||||
name: t("common.repository"),
|
||||
value: t("events.repository.is_fork"),
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const descriptionParts: string[] = [];
|
||||
if (isCreateOrVisibility && repoUrl) {
|
||||
descriptionParts.push(`[${t("events.repository.open")}](${repoUrl})`);
|
||||
}
|
||||
if (isCreateOrVisibility && repoData.description) {
|
||||
descriptionParts.push(`> ${repoData.description}`);
|
||||
}
|
||||
|
||||
return {
|
||||
embeds: [
|
||||
{
|
||||
author,
|
||||
title: t("events.repository.title", { action: al, repo: repo ?? t("common.repository") }),
|
||||
url: repoUrl,
|
||||
color: GITHUB_COLORS.repository,
|
||||
description: descriptionParts.length > 0 ? descriptionParts.join("\n") : undefined,
|
||||
fields: fields.length > 0 ? fields : undefined,
|
||||
footer: { text: t("common.footer", { repo: repo ?? t("common.github") }) },
|
||||
timestamp: new Date().toISOString(),
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ export const en = {
|
|||
pinned: "Pinned",
|
||||
unpinned: "Unpinned",
|
||||
transferred: "Transferred",
|
||||
publicized: "made public",
|
||||
privatized: "made private",
|
||||
locked: "Locked",
|
||||
unlocked: "Unlocked",
|
||||
renamed: "Renamed",
|
||||
|
|
@ -159,6 +161,12 @@ export const en = {
|
|||
},
|
||||
repository: {
|
||||
title: "📦 Repository {action}: {repo}",
|
||||
open: "🔗 Open repository",
|
||||
public: "public",
|
||||
private: "private",
|
||||
internal: "internal",
|
||||
is_fork: "This is a fork",
|
||||
visibility: "Visibility",
|
||||
},
|
||||
code_scanning: {
|
||||
title: "🔍 Code Scanning: {action}",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ export const zh = {
|
|||
pinned: "已置顶",
|
||||
unpinned: "已取消置顶",
|
||||
transferred: "已转移",
|
||||
publicized: "已公开",
|
||||
privatized: "已设为私有",
|
||||
locked: "已锁定",
|
||||
unlocked: "已解锁",
|
||||
renamed: "已重命名",
|
||||
|
|
@ -159,6 +161,12 @@ export const zh = {
|
|||
},
|
||||
repository: {
|
||||
title: "📦 仓库 {action}: {repo}",
|
||||
open: "🔗 打开仓库",
|
||||
public: "公开",
|
||||
private: "私有",
|
||||
internal: "内部",
|
||||
is_fork: "这是 Fork 仓库",
|
||||
visibility: "可见性",
|
||||
},
|
||||
code_scanning: {
|
||||
title: "🔍 代码扫描: {action}",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ export interface Route {
|
|||
};
|
||||
lang?: string;
|
||||
groupId?: string;
|
||||
/**
|
||||
* Fallback route: only fires when no other (non-fallback) route matched the
|
||||
* event. Multiple fallback routes may exist; they are all skipped whenever at
|
||||
* least one regular route matches.
|
||||
*/
|
||||
fallback?: boolean;
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue