Skip to content

Commit ce7dde6

Browse files
authored
chore: enhance moderator (#670)
Signed-off-by: Huamin Chen <[email protected]>
1 parent bbbe3e6 commit ce7dde6

File tree

2 files changed

+378
-45
lines changed

2 files changed

+378
-45
lines changed

.github/workflows/anti-spam-comment-moderator.yml

Lines changed: 103 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,89 @@
11
name: Anti-Spam Comment Moderator
22

33
on:
4+
issues:
5+
types: [opened, edited]
6+
pull_request:
7+
types: [opened, edited]
48
issue_comment:
59
types: [created, edited]
610
pull_request_review_comment:
711
types: [created, edited]
812

913
permissions:
10-
issues: write # needed to delete issue comments
11-
pull-requests: write # needed to delete PR review comments
14+
issues: write # needed to delete/close issues and comments
15+
pull-requests: write # needed to delete/close PRs and comments
1216
contents: write # needed to delete commit comments
1317
# (discussions not handled here; API differs)
1418

1519
jobs:
1620
moderate:
17-
if: ${{ github.event.action == 'created' || github.event.action == 'edited' }}
21+
if: ${{ github.event.action == 'created' || github.event.action == 'edited' || github.event.action == 'opened' }}
1822
runs-on: ubuntu-latest
1923
steps:
2024
- name: Run spam filter
2125
uses: actions/github-script@v7
2226
with:
2327
script: |
24-
// 1) Collect event/comment info
28+
// 1) Collect event/comment/issue/PR info
2529
const ev = context.eventName;
2630
const comment = context.payload.comment || {};
27-
const body = (comment.body || "").trim();
31+
const issue = context.payload.issue || {};
32+
const pr = context.payload.pull_request || {};
33+
34+
// Determine the content source (comment, issue body, or PR body)
35+
let body, assoc, actor, itemId, itemType;
36+
if (ev === "issue_comment" || ev === "pull_request_review_comment") {
37+
body = (comment.body || "").trim();
38+
assoc = comment.author_association || "NONE";
39+
actor = comment.user?.login || "unknown";
40+
itemId = comment.id;
41+
itemType = "comment";
42+
} else if (ev === "issues") {
43+
body = (issue.body || "").trim();
44+
assoc = issue.author_association || "NONE";
45+
actor = issue.user?.login || "unknown";
46+
itemId = issue.number;
47+
itemType = "issue";
48+
} else if (ev === "pull_request") {
49+
body = (pr.body || "").trim();
50+
assoc = pr.author_association || "NONE";
51+
actor = pr.user?.login || "unknown";
52+
itemId = pr.number;
53+
itemType = "pr";
54+
} else {
55+
core.warning(`Unhandled event: ${ev}`);
56+
return;
57+
}
58+
2859
const bodyLower = body.toLowerCase();
29-
const assoc = comment.author_association || "NONE";
30-
const actor = comment.user?.login || "unknown";
31-
const owner = context.repo.owner;
32-
const repo = context.repo.repo;
60+
const owner = context.repo.owner;
61+
const repo = context.repo.repo;
3362
34-
// Block specific user outright
35-
if ((actor || "").toLowerCase() === "phuole818") {
63+
// Block specific user outright (also block blaji-villeb106)
64+
const blockedUsers = ["phuole818", "blaji-villeb106"];
65+
if (blockedUsers.some(u => (actor || "").toLowerCase() === u.toLowerCase())) {
3666
try {
37-
if (ev === "issue_comment") {
38-
await github.rest.issues.deleteComment({ owner, repo, comment_id: comment.id });
39-
core.notice(`Deleted comment from blocked user @${actor} (issue comment).`);
40-
} else if (ev === "pull_request_review_comment") {
41-
await github.rest.pulls.deleteReviewComment({ owner, repo, comment_id: comment.id });
42-
core.notice(`Deleted comment from blocked user @${actor} (PR review comment).`);
43-
} else if (ev === "commit_comment") {
44-
await github.rest.repos.deleteCommitComment({ owner, repo, comment_id: comment.id });
45-
core.notice(`Deleted comment from blocked user @${actor} (commit comment).`);
46-
} else {
47-
core.warning(`Unhandled event while blocking user: ${ev}`);
67+
if (itemType === "comment") {
68+
if (ev === "issue_comment") {
69+
await github.rest.issues.deleteComment({ owner, repo, comment_id: itemId });
70+
core.notice(`Deleted comment from blocked user @${actor} (issue comment).`);
71+
} else if (ev === "pull_request_review_comment") {
72+
await github.rest.pulls.deleteReviewComment({ owner, repo, comment_id: itemId });
73+
core.notice(`Deleted comment from blocked user @${actor} (PR review comment).`);
74+
} else if (ev === "commit_comment") {
75+
await github.rest.repos.deleteCommitComment({ owner, repo, comment_id: itemId });
76+
core.notice(`Deleted comment from blocked user @${actor} (commit comment).`);
77+
}
78+
} else if (itemType === "issue") {
79+
await github.rest.issues.update({ owner, repo, issue_number: itemId, state: "closed", state_reason: "not_planned" });
80+
core.notice(`Closed issue from blocked user @${actor} (issue #${itemId}).`);
81+
} else if (itemType === "pr") {
82+
await github.rest.pulls.update({ owner, repo, pull_number: itemId, state: "closed" });
83+
core.notice(`Closed PR from blocked user @${actor} (PR #${itemId}).`);
4884
}
4985
} catch (err) {
50-
core.setFailed(`Failed to delete blocked user's comment: ${err?.message || err}`);
86+
core.setFailed(`Failed to handle blocked user's content: ${err?.message || err}`);
5187
}
5288
return;
5389
}
@@ -120,14 +156,16 @@ jobs:
120156
"fake stars","astroturf","bot accounts","paid stars","star farming","star boosting","shill",
121157
"manipulated stars","kpi","kpi boosting","no maintainer","ignore issues","ignore prs",
122158
"close pr","close issue","no response","waste of time","trash project","scam project",
123-
"archive this project","unmaintained","low quality docs","unreadable docs","pitfall","avoid this project"
159+
"archive this project","unmaintained","low quality docs","unreadable docs","pitfall","avoid this project",
160+
"dead project","abandoned project","team lost contact","stay away"
124161
];
125162
const attackTermsCJK = [
126-
"刷星","水军","kpi刷单","假号","买粉","造假","刷榜",
163+
"刷星","水军","kpi刷单","假号","买粉","造假","刷榜","刷人气",
127164
"别踩坑","大坑","浪费时间","赶紧换","不靠谱","建议归档","建议archive",
128-
"没人理你","没人管","装没看见","秒关","石沉大海",
165+
"没人理你","没人管","装没看见","秒关","石沉大海","失联","团队失联","维护团队失联",
129166
"问题一大堆","一塌糊涂","堪忧","离谱","看不懂","入不了门",
130-
"警告","大踩雷","失望透顶","全靠刷星","社区大踩雷"
167+
"警告","大踩雷","失望透顶","全靠刷星","社区大踩雷","死项目","远离","及早远离",
168+
"异常增长","激增","数量异常","star异常","star数异常","内部号召","非自然"
131169
];
132170
const insultTermsAscii = [
133171
"trash","garbage","bullshit","idiot","moron","stupid","dumb","shameful","useless"
@@ -154,7 +192,7 @@ jobs:
154192
const attackHits = countMatchesAscii(attackTermsAscii) + countMatchesCJK(attackTermsCJK);
155193
const insultHit = (countMatchesAscii(insultTermsAscii) + countMatchesCJK(insultTermsCJK)) > 0;
156194
const techCtxHit = (countMatchesAscii(techContextAscii) + countMatchesCJK(techContextCJK)) > 0;
157-
const strongCJK = /(失望透顶|离谱|警告|大踩雷)/.test(body);
195+
const strongCJK = /(失望透顶|离谱|警告|大踩雷|失联|死项目|远离|异常增长|激增|刷星|刷人气)/.test(body);
158196
159197
// Sentiment-lite (AFINN-style mini-lexicon)
160198
const afinn = {
@@ -176,37 +214,57 @@ jobs:
176214
if (techCtxHit) attackContribution = Math.min(1, attackContribution); // cap if technical context detected
177215
points += attackContribution;
178216
179-
core.info(`Spam score for @${actor} = ${points} (attackOnly; links/emails/phones ignored) (links:${linkCount} safe:${safeLinkCount} suspicious:${suspiciousLinkCount}, emails:${emailCount}, phones:${phoneCount}, mentions:${mentions}, sentiment:${sentiment}, attackHits:${attackHits}, insult:${insultHit}, techCtx:${techCtxHit})`);
217+
core.info(`Spam score for @${actor} = ${points} (attackOnly; links/emails/phones ignored) (links:${linkCount} safe:${safeLinkCount} suspicious:${suspiciousLinkCount}, emails:${emailCount}, phones:${phoneCount}, mentions:${mentions}, sentiment:${sentiment}, attackHits:${attackHits}, insult:${insultHit}, techCtx:${techCtxHit}, itemType:${itemType})`);
180218
181219
// Only block when attack/insult crosses threshold
182220
const isSpam = attackContribution >= 2; // adjust threshold if needed
183221
if (!isSpam) {
184-
core.info("Comment not flagged as spam.");
222+
core.info("Content not flagged as spam.");
185223
return;
186224
}
187225
188-
// 4) Delete the comment using the appropriate endpoint
226+
// 4) Delete/close the spam content using the appropriate endpoint
189227
try {
190-
if (ev === "issue_comment") {
191-
await github.rest.issues.deleteComment({
192-
owner, repo, comment_id: comment.id
228+
if (itemType === "comment") {
229+
if (ev === "issue_comment") {
230+
await github.rest.issues.deleteComment({
231+
owner, repo, comment_id: itemId
232+
});
233+
core.notice(`Deleted spam issue comment from @${actor}.`);
234+
} else if (ev === "pull_request_review_comment") {
235+
await github.rest.pulls.deleteReviewComment({
236+
owner, repo, comment_id: itemId
237+
});
238+
core.notice(`Deleted spam PR review comment from @${actor}.`);
239+
} else if (ev === "commit_comment") {
240+
await github.rest.repos.deleteCommitComment({
241+
owner, repo, comment_id: itemId
242+
});
243+
core.notice(`Deleted spam commit comment from @${actor}.`);
244+
}
245+
} else if (itemType === "issue") {
246+
await github.rest.issues.update({
247+
owner, repo, issue_number: itemId, state: "closed", state_reason: "not_planned"
248+
});
249+
await github.rest.issues.createComment({
250+
owner, repo, issue_number: itemId,
251+
body: "This issue has been automatically closed as spam."
193252
});
194-
core.notice(`Deleted spam issue comment from @${actor}.`);
195-
} else if (ev === "pull_request_review_comment") {
196-
await github.rest.pulls.deleteReviewComment({
197-
owner, repo, comment_id: comment.id
253+
core.notice(`Closed spam issue #${itemId} from @${actor}.`);
254+
} else if (itemType === "pr") {
255+
await github.rest.pulls.update({
256+
owner, repo, pull_number: itemId, state: "closed"
198257
});
199-
core.notice(`Deleted spam PR review comment from @${actor}.`);
200-
} else if (ev === "commit_comment") {
201-
await github.rest.repos.deleteCommitComment({
202-
owner, repo, comment_id: comment.id
258+
await github.rest.issues.createComment({
259+
owner, repo, issue_number: itemId,
260+
body: "This pull request has been automatically closed as spam."
203261
});
204-
core.notice(`Deleted spam commit comment from @${actor}.`);
262+
core.notice(`Closed spam PR #${itemId} from @${actor}.`);
205263
} else {
206-
core.warning(`Unhandled event: ${ev}`);
264+
core.warning(`Unhandled item type: ${itemType}`);
207265
}
208266
} catch (err) {
209-
core.setFailed(`Failed to delete comment: ${err?.message || err}`);
267+
core.setFailed(`Failed to handle spam content: ${err?.message || err}`);
210268
}
211269
212270

0 commit comments

Comments
 (0)