mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
CI: add maintainer ping auto-response
This commit is contained in:
127
.github/workflows/auto-response.yml
vendored
127
.github/workflows/auto-response.yml
vendored
@@ -3,6 +3,8 @@ name: Auto response
|
|||||||
on:
|
on:
|
||||||
issues:
|
issues:
|
||||||
types: [opened, edited, labeled]
|
types: [opened, edited, labeled]
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@ jobs:
|
|||||||
{
|
{
|
||||||
label: "r: testflight",
|
label: "r: testflight",
|
||||||
close: true,
|
close: true,
|
||||||
|
commentTriggers: ["testflight"],
|
||||||
message: "Not available, build from source.",
|
message: "Not available, build from source.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -55,11 +58,76 @@ jobs:
|
|||||||
close: true,
|
close: true,
|
||||||
lock: true,
|
lock: true,
|
||||||
lockReason: "off-topic",
|
lockReason: "off-topic",
|
||||||
|
commentTriggers: ["moltbook"],
|
||||||
message:
|
message:
|
||||||
"OpenClaw is not affiliated with Moltbook, and issues related to Moltbook should not be submitted here.",
|
"OpenClaw is not affiliated with Moltbook, and issues related to Moltbook should not be submitted here.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const maintainerTeam = "maintainer";
|
||||||
|
const pingWarningMessage =
|
||||||
|
"Please don’t spam-ping multiple maintainers at once. Be patient, or join our community Discord for help: https://discord.gg/clawd";
|
||||||
|
const mentionRegex = /@([A-Za-z0-9-]+)/g;
|
||||||
|
const maintainerCache = new Map();
|
||||||
|
const normalizeLogin = (login) => login.toLowerCase();
|
||||||
|
|
||||||
|
const isMaintainer = async (login) => {
|
||||||
|
if (!login) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const normalized = normalizeLogin(login);
|
||||||
|
if (maintainerCache.has(normalized)) {
|
||||||
|
return maintainerCache.get(normalized);
|
||||||
|
}
|
||||||
|
let isMember = false;
|
||||||
|
try {
|
||||||
|
const membership = await github.rest.teams.getMembershipForUserInOrg({
|
||||||
|
org: context.repo.owner,
|
||||||
|
team_slug: maintainerTeam,
|
||||||
|
username: normalized,
|
||||||
|
});
|
||||||
|
isMember = membership?.data?.state === "active";
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.status !== 404) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maintainerCache.set(normalized, isMember);
|
||||||
|
return isMember;
|
||||||
|
};
|
||||||
|
|
||||||
|
const countMaintainerMentions = async (body, authorLogin) => {
|
||||||
|
if (!body) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const normalizedAuthor = authorLogin ? normalizeLogin(authorLogin) : "";
|
||||||
|
if (normalizedAuthor && (await isMaintainer(normalizedAuthor))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const haystack = body.toLowerCase();
|
||||||
|
const teamMention = `@${context.repo.owner.toLowerCase()}/${maintainerTeam}`;
|
||||||
|
if (haystack.includes(teamMention)) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mentions = new Set();
|
||||||
|
for (const match of body.matchAll(mentionRegex)) {
|
||||||
|
mentions.add(normalizeLogin(match[1]));
|
||||||
|
}
|
||||||
|
if (normalizedAuthor) {
|
||||||
|
mentions.delete(normalizedAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
for (const login of mentions) {
|
||||||
|
if (await isMaintainer(login)) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
const triggerLabel = "trigger-response";
|
const triggerLabel = "trigger-response";
|
||||||
const target = context.payload.issue ?? context.payload.pull_request;
|
const target = context.payload.issue ?? context.payload.pull_request;
|
||||||
if (!target) {
|
if (!target) {
|
||||||
@@ -72,6 +140,63 @@ jobs:
|
|||||||
.filter((name) => typeof name === "string"),
|
.filter((name) => typeof name === "string"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const issue = context.payload.issue;
|
||||||
|
const pullRequest = context.payload.pull_request;
|
||||||
|
const comment = context.payload.comment;
|
||||||
|
if (comment) {
|
||||||
|
const authorLogin = comment.user?.login ?? "";
|
||||||
|
if (comment.user?.type === "Bot" || authorLogin.endsWith("[bot]")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commentBody = comment.body ?? "";
|
||||||
|
const responses = [];
|
||||||
|
const mentionCount = await countMaintainerMentions(commentBody, authorLogin);
|
||||||
|
if (mentionCount >= 3) {
|
||||||
|
responses.push(pingWarningMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const commentHaystack = commentBody.toLowerCase();
|
||||||
|
const commentRule = rules.find((item) =>
|
||||||
|
(item.commentTriggers ?? []).some((trigger) =>
|
||||||
|
commentHaystack.includes(trigger),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (commentRule) {
|
||||||
|
responses.push(commentRule.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responses.length > 0) {
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: target.number,
|
||||||
|
body: responses.join("\n\n"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issue) {
|
||||||
|
const action = context.payload.action;
|
||||||
|
if (action === "opened" || action === "edited") {
|
||||||
|
const issueText = `${issue.title ?? ""}\n${issue.body ?? ""}`.trim();
|
||||||
|
const authorLogin = issue.user?.login ?? "";
|
||||||
|
const mentionCount = await countMaintainerMentions(
|
||||||
|
issueText,
|
||||||
|
authorLogin,
|
||||||
|
);
|
||||||
|
if (mentionCount >= 3) {
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
body: pingWarningMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const hasTriggerLabel = labelSet.has(triggerLabel);
|
const hasTriggerLabel = labelSet.has(triggerLabel);
|
||||||
if (hasTriggerLabel) {
|
if (hasTriggerLabel) {
|
||||||
labelSet.delete(triggerLabel);
|
labelSet.delete(triggerLabel);
|
||||||
@@ -94,7 +219,6 @@ jobs:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const issue = context.payload.issue;
|
|
||||||
if (issue) {
|
if (issue) {
|
||||||
const title = issue.title ?? "";
|
const title = issue.title ?? "";
|
||||||
const body = issue.body ?? "";
|
const body = issue.body ?? "";
|
||||||
@@ -136,7 +260,6 @@ jobs:
|
|||||||
const noisyPrMessage =
|
const noisyPrMessage =
|
||||||
"Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.";
|
"Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.";
|
||||||
|
|
||||||
const pullRequest = context.payload.pull_request;
|
|
||||||
if (pullRequest) {
|
if (pullRequest) {
|
||||||
if (labelSet.has(dirtyLabel)) {
|
if (labelSet.has(dirtyLabel)) {
|
||||||
await github.rest.issues.createComment({
|
await github.rest.issues.createComment({
|
||||||
|
|||||||
Reference in New Issue
Block a user