Skip to content

Commit 803cfbd

Browse files
authored
Merge branch 'main' into fix_monitoring
2 parents 405b8cf + 5f5a079 commit 803cfbd

File tree

90 files changed

+13052
-1755
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+13052
-1755
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Content Moderation
2+
3+
on:
4+
issues:
5+
types: [opened, edited]
6+
pull_request:
7+
types: [opened, edited]
8+
issue_comment:
9+
types: [created, edited]
10+
pull_request_review_comment:
11+
types: [created, edited]
12+
13+
permissions:
14+
issues: write
15+
pull-requests: write
16+
contents: write
17+
18+
jobs:
19+
moderate:
20+
if: ${{ github.event.action == 'created' || github.event.action == 'edited' || github.event.action == 'opened' }}
21+
runs-on: ubuntu-latest
22+
steps:
23+
- name: Run spam filter
24+
uses: actions/github-script@v7
25+
env:
26+
# The entire spam detection logic is stored here
27+
SPAM_DETECTION_SCRIPT: ${{ secrets.SPAM_DETECTION_SCRIPT }}
28+
with:
29+
script: |
30+
// Load and execute the spam detection script from secret
31+
const detectionScript = process.env.SPAM_DETECTION_SCRIPT;
32+
33+
if (!detectionScript) {
34+
core.warning("SPAM_DETECTION_SCRIPT secret not configured - skipping spam detection");
35+
core.info("To enable spam filtering, set up the SPAM_DETECTION_SCRIPT secret.");
36+
core.info("See documentation for setup instructions.");
37+
return;
38+
}
39+
40+
try {
41+
// Execute the hidden script
42+
// The script has access to: github, context, core
43+
const detectSpam = eval(detectionScript);
44+
45+
// Run the detection
46+
await detectSpam(github, context, core);
47+
48+
} catch (err) {
49+
core.error(`Spam filter error: ${err.message}`);
50+
core.warning(`Filter execution failed - continuing without spam check`);
51+
// Don't fail the workflow, just log the error
52+
}
53+
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
name: Cleanup Existing Spam
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
dry_run:
7+
description: 'Dry run (only report, do not delete)'
8+
required: false
9+
default: 'true'
10+
type: choice
11+
options:
12+
- 'true'
13+
- 'false'
14+
scan_issues:
15+
description: 'Scan open issues'
16+
required: false
17+
default: 'true'
18+
type: boolean
19+
scan_closed_issues:
20+
description: 'Scan closed issues (may find previously closed spam)'
21+
required: false
22+
default: 'false'
23+
type: boolean
24+
scan_comments:
25+
description: 'Scan issue comments'
26+
required: false
27+
default: 'true'
28+
type: boolean
29+
max_issues:
30+
description: 'Max number of issues to scan (0 = all)'
31+
required: false
32+
default: '100'
33+
type: string
34+
35+
permissions:
36+
issues: write
37+
pull-requests: write
38+
contents: read
39+
40+
jobs:
41+
cleanup:
42+
runs-on: ubuntu-latest
43+
steps:
44+
- name: Scan and cleanup spam
45+
uses: actions/github-script@v7
46+
env:
47+
# Uses the same hidden detection logic as the main filter
48+
SPAM_DETECTION_SCRIPT: ${{ secrets.SPAM_DETECTION_SCRIPT }}
49+
with:
50+
script: |
51+
const dryRun = '${{ inputs.dry_run }}' === 'true';
52+
const scanIssues = '${{ inputs.scan_issues }}' === 'true';
53+
const scanClosedIssues = '${{ inputs.scan_closed_issues }}' === 'true';
54+
const scanComments = '${{ inputs.scan_comments }}' === 'true';
55+
const maxIssues = parseInt('${{ inputs.max_issues }}') || 0;
56+
const owner = context.repo.owner;
57+
const repo = context.repo.repo;
58+
59+
core.info(`Starting cleanup with hidden detection logic`);
60+
core.info(` Dry run: ${dryRun}`);
61+
core.info(` Scan open issues: ${scanIssues}`);
62+
core.info(` Scan closed issues: ${scanClosedIssues}`);
63+
core.info(` Scan comments: ${scanComments}`);
64+
core.info(``);
65+
66+
// Load detection script from secret
67+
const detectionScript = process.env.SPAM_DETECTION_SCRIPT;
68+
69+
if (!detectionScript) {
70+
core.error("SPAM_DETECTION_SCRIPT secret not found!");
71+
core.error("The cleanup tool requires the SPAM_DETECTION_SCRIPT secret to be configured.");
72+
core.error("Please set up the secret before running cleanup.");
73+
core.error("See documentation for setup instructions.");
74+
core.setFailed("Spam detection not configured");
75+
return;
76+
}
77+
78+
// Create analyzer function (all logic hidden in secret)
79+
let analyzeContent;
80+
81+
try {
82+
// Wrap the detection script to create a reusable analyzer
83+
const analyzerWrapper = `
84+
(async function(github, context, core) {
85+
${detectionScript}
86+
})
87+
`;
88+
89+
// Execute to get the detection capabilities
90+
const detectionModule = eval(analyzerWrapper);
91+
await detectionModule(github, context, core);
92+
93+
// Create a mock context for analysis
94+
analyzeContent = async function(body, actor, assoc) {
95+
// Create mock event for analysis
96+
const mockContext = {
97+
...context,
98+
eventName: 'issues',
99+
payload: {
100+
issue: {
101+
body: body,
102+
user: { login: actor },
103+
author_association: assoc,
104+
number: 0
105+
},
106+
action: 'opened'
107+
}
108+
};
109+
110+
// Run detection on mock event
111+
let isSpam = false;
112+
let reason = "Clean";
113+
let score = 0;
114+
115+
// Capture the detection result by checking if action would be taken
116+
const originalUpdate = github.rest.issues.update;
117+
const originalDelete = github.rest.issues.deleteComment;
118+
119+
let detectionResult = { isSpam: false };
120+
121+
github.rest.issues.update = async (params) => {
122+
detectionResult = { isSpam: true, reason: "Would close", score: 2 };
123+
return { data: {} };
124+
};
125+
126+
github.rest.issues.deleteComment = async (params) => {
127+
detectionResult = { isSpam: true, reason: "Would delete", score: 2 };
128+
return { data: {} };
129+
};
130+
131+
github.rest.issues.createComment = async () => ({ data: {} });
132+
github.rest.pulls.update = async () => ({ data: {} });
133+
github.rest.pulls.deleteReviewComment = async () => ({ data: {} });
134+
135+
try {
136+
await detectionModule(github, mockContext, core);
137+
} catch (e) {
138+
// Ignore errors from mock execution
139+
}
140+
141+
// Restore original functions
142+
github.rest.issues.update = originalUpdate;
143+
github.rest.issues.deleteComment = originalDelete;
144+
145+
return detectionResult;
146+
};
147+
148+
core.info("✅ Loaded spam detection from secret");
149+
core.info("");
150+
151+
} catch (err) {
152+
core.error(`Failed to load detection: ${err.message}`);
153+
core.setFailed(`Detection script error`);
154+
return;
155+
}
156+
157+
let totalScanned = 0;
158+
let totalSpam = 0;
159+
let totalClosed = 0;
160+
let totalDeleted = 0;
161+
162+
// Scan issues
163+
if (scanIssues) {
164+
core.info("Scanning open issues...");
165+
let page = 1;
166+
167+
while (maxIssues === 0 || totalScanned < maxIssues) {
168+
const issues = await github.rest.issues.listForRepo({
169+
owner, repo, state: 'open', per_page: 100, page
170+
});
171+
172+
if (issues.data.length === 0) break;
173+
174+
for (const issue of issues.data) {
175+
if (issue.pull_request) continue;
176+
if (maxIssues > 0 && totalScanned >= maxIssues) break;
177+
178+
totalScanned++;
179+
const analysis = await analyzeContent(
180+
issue.body || "",
181+
issue.user?.login || "unknown",
182+
issue.author_association || "NONE"
183+
);
184+
185+
if (analysis.isSpam) {
186+
totalSpam++;
187+
core.warning(`[SPAM] Issue #${issue.number} by @${issue.user?.login}`);
188+
core.warning(` Preview: ${(issue.body || "").substring(0, 150)}...`);
189+
190+
if (!dryRun) {
191+
try {
192+
await github.rest.issues.update({
193+
owner, repo, issue_number: issue.number,
194+
state: "closed", state_reason: "not_planned"
195+
});
196+
await github.rest.issues.createComment({
197+
owner, repo, issue_number: issue.number,
198+
body: "This issue has been automatically closed as spam during cleanup."
199+
});
200+
totalClosed++;
201+
core.notice(`✓ Closed spam issue #${issue.number}`);
202+
} catch (err) {
203+
core.error(`✗ Failed to close issue #${issue.number}: ${err.message}`);
204+
}
205+
}
206+
}
207+
}
208+
page++;
209+
}
210+
}
211+
212+
// Scan closed issues
213+
if (scanClosedIssues) {
214+
core.info("Scanning closed issues...");
215+
let page = 1;
216+
let closedScanned = 0;
217+
218+
while (maxIssues === 0 || closedScanned < maxIssues) {
219+
const issues = await github.rest.issues.listForRepo({
220+
owner, repo, state: 'closed', per_page: 100, page
221+
});
222+
223+
if (issues.data.length === 0) break;
224+
225+
for (const issue of issues.data) {
226+
if (issue.pull_request) continue;
227+
if (maxIssues > 0 && closedScanned >= maxIssues) break;
228+
229+
closedScanned++;
230+
const analysis = await analyzeContent(
231+
issue.body || "",
232+
issue.user?.login || "unknown",
233+
issue.author_association || "NONE"
234+
);
235+
236+
if (analysis.isSpam) {
237+
totalSpam++;
238+
core.warning(`[SPAM] Closed Issue #${issue.number} by @${issue.user?.login}`);
239+
core.warning(` Preview: ${(issue.body || "").substring(0, 150)}...`);
240+
core.warning(` Already closed, but content still visible`);
241+
242+
if (!dryRun) {
243+
try {
244+
// Rewrite the closed spam issue to remove content
245+
await github.rest.issues.update({
246+
owner, repo, issue_number: issue.number,
247+
title: "[MODERATED] Content Removed",
248+
body: "**This content has been automatically moderated and removed.**\n\n" +
249+
"The original content violated our spam policy and has been hidden.\n\n" +
250+
"_This was detected during a cleanup scan of existing content._"
251+
});
252+
totalClosed++;
253+
core.notice(`✓ Moderated closed spam issue #${issue.number}`);
254+
} catch (err) {
255+
core.error(`✗ Failed to moderate issue #${issue.number}: ${err.message}`);
256+
}
257+
}
258+
}
259+
}
260+
page++;
261+
}
262+
core.info(`Scanned ${closedScanned} closed issues`);
263+
}
264+
265+
// Scan comments
266+
if (scanComments) {
267+
core.info("Scanning issue comments...");
268+
let page = 1;
269+
let commentCount = 0;
270+
271+
while (page <= 10) {
272+
const comments = await github.rest.issues.listCommentsForRepo({
273+
owner, repo, per_page: 100, page, sort: 'created', direction: 'desc'
274+
});
275+
276+
if (comments.data.length === 0) break;
277+
278+
for (const comment of comments.data) {
279+
commentCount++;
280+
const analysis = await analyzeContent(
281+
comment.body || "",
282+
comment.user?.login || "unknown",
283+
comment.author_association || "NONE"
284+
);
285+
286+
if (analysis.isSpam) {
287+
totalSpam++;
288+
core.warning(`[SPAM] Comment #${comment.id} by @${comment.user?.login}`);
289+
core.warning(` Preview: ${(comment.body || "").substring(0, 150)}...`);
290+
291+
if (!dryRun) {
292+
try {
293+
await github.rest.issues.deleteComment({
294+
owner, repo, comment_id: comment.id
295+
});
296+
totalDeleted++;
297+
core.notice(`✓ Deleted spam comment #${comment.id}`);
298+
} catch (err) {
299+
core.error(`✗ Failed to delete comment #${comment.id}: ${err.message}`);
300+
}
301+
}
302+
}
303+
}
304+
page++;
305+
}
306+
307+
core.info(`Scanned ${commentCount} comments`);
308+
}
309+
310+
// Summary
311+
core.notice("=".repeat(60));
312+
core.notice(`Cleanup Summary ${dryRun ? '(DRY RUN)' : '(EXECUTED)'}`);
313+
core.notice("=".repeat(60));
314+
if (scanIssues) core.notice(`✓ Scanned open issues: ${totalScanned}`);
315+
if (scanClosedIssues) core.notice(`✓ Scanned closed issues for content removal`);
316+
if (scanComments) core.notice(`✓ Scanned comments`);
317+
core.notice("");
318+
core.notice(`Total spam found: ${totalSpam}`);
319+
if (!dryRun) {
320+
core.notice(`Actions taken:`);
321+
core.notice(` - Issues closed/moderated: ${totalClosed}`);
322+
core.notice(` - Comments deleted: ${totalDeleted}`);
323+
} else {
324+
core.notice("⚠️ DRY RUN - No actions taken");
325+
core.notice(" Set dry_run to 'false' to execute cleanup");
326+
}
327+
core.notice("=".repeat(60));

0 commit comments

Comments
 (0)