11name : Anti-Spam Comment Moderator
22
33on :
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
913permissions :
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
1519jobs :
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