1616 required : false
1717 default : ' true'
1818 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
1924 scan_comments :
2025 description : ' Scan issue comments'
2126 required : false
@@ -45,41 +50,43 @@ jobs:
4550 script : |
4651 const dryRun = '${{ inputs.dry_run }}' === 'true';
4752 const scanIssues = '${{ inputs.scan_issues }}' === 'true';
53+ const scanClosedIssues = '${{ inputs.scan_closed_issues }}' === 'true';
4854 const scanComments = '${{ inputs.scan_comments }}' === 'true';
4955 const maxIssues = parseInt('${{ inputs.max_issues }}') || 0;
5056 const owner = context.repo.owner;
5157 const repo = context.repo.repo;
52-
58+
5359 core.info(`Starting cleanup with hidden detection logic`);
5460 core.info(` Dry run: ${dryRun}`);
55- core.info(` Scan issues: ${scanIssues}`);
61+ core.info(` Scan open issues: ${scanIssues}`);
62+ core.info(` Scan closed issues: ${scanClosedIssues}`);
5663 core.info(` Scan comments: ${scanComments}`);
5764 core.info(``);
58-
65+
5966 // Load detection script from secret
6067 const detectionScript = process.env.SPAM_DETECTION_SCRIPT;
61-
68+
6269 if (!detectionScript) {
6370 core.error("SPAM_DETECTION_SCRIPT secret not found!");
6471 core.setFailed("Spam detection not configured");
6572 return;
6673 }
67-
74+
6875 // Create analyzer function (all logic hidden in secret)
6976 let analyzeContent;
70-
77+
7178 try {
7279 // Wrap the detection script to create a reusable analyzer
7380 const analyzerWrapper = `
7481 (async function(github, context, core) {
7582 ${detectionScript}
7683 })
7784 `;
78-
85+
7986 // Execute to get the detection capabilities
8087 const detectionModule = eval(analyzerWrapper);
8188 await detectionModule(github, context, core);
82-
89+
8390 // Create a mock context for analysis
8491 analyzeContent = async function(body, actor, assoc) {
8592 // Create mock event for analysis
@@ -96,87 +103,87 @@ jobs:
96103 action: 'opened'
97104 }
98105 };
99-
106+
100107 // Run detection on mock event
101108 let isSpam = false;
102109 let reason = "Clean";
103110 let score = 0;
104-
111+
105112 // Capture the detection result by checking if action would be taken
106113 const originalUpdate = github.rest.issues.update;
107114 const originalDelete = github.rest.issues.deleteComment;
108-
115+
109116 let detectionResult = { isSpam: false };
110-
117+
111118 github.rest.issues.update = async (params) => {
112119 detectionResult = { isSpam: true, reason: "Would close", score: 2 };
113120 return { data: {} };
114121 };
115-
122+
116123 github.rest.issues.deleteComment = async (params) => {
117124 detectionResult = { isSpam: true, reason: "Would delete", score: 2 };
118125 return { data: {} };
119126 };
120-
127+
121128 github.rest.issues.createComment = async () => ({ data: {} });
122129 github.rest.pulls.update = async () => ({ data: {} });
123130 github.rest.pulls.deleteReviewComment = async () => ({ data: {} });
124-
131+
125132 try {
126133 await detectionModule(github, mockContext, core);
127134 } catch (e) {
128135 // Ignore errors from mock execution
129136 }
130-
137+
131138 // Restore original functions
132139 github.rest.issues.update = originalUpdate;
133140 github.rest.issues.deleteComment = originalDelete;
134-
141+
135142 return detectionResult;
136143 };
137-
144+
138145 core.info("✅ Loaded spam detection from secret");
139146 core.info("");
140-
147+
141148 } catch (err) {
142149 core.error(`Failed to load detection: ${err.message}`);
143150 core.setFailed(`Detection script error`);
144151 return;
145152 }
146-
153+
147154 let totalScanned = 0;
148155 let totalSpam = 0;
149156 let totalClosed = 0;
150157 let totalDeleted = 0;
151-
158+
152159 // Scan issues
153160 if (scanIssues) {
154161 core.info("Scanning open issues...");
155162 let page = 1;
156-
163+
157164 while (maxIssues === 0 || totalScanned < maxIssues) {
158165 const issues = await github.rest.issues.listForRepo({
159166 owner, repo, state: 'open', per_page: 100, page
160167 });
161-
168+
162169 if (issues.data.length === 0) break;
163-
170+
164171 for (const issue of issues.data) {
165172 if (issue.pull_request) continue;
166173 if (maxIssues > 0 && totalScanned >= maxIssues) break;
167-
174+
168175 totalScanned++;
169176 const analysis = await analyzeContent(
170177 issue.body || "",
171178 issue.user?.login || "unknown",
172179 issue.author_association || "NONE"
173180 );
174-
181+
175182 if (analysis.isSpam) {
176183 totalSpam++;
177184 core.warning(`[SPAM] Issue #${issue.number} by @${issue.user?.login}`);
178185 core.warning(` Preview: ${(issue.body || "").substring(0, 150)}...`);
179-
186+
180187 if (!dryRun) {
181188 try {
182189 await github.rest.issues.update({
@@ -198,33 +205,86 @@ jobs:
198205 page++;
199206 }
200207 }
201-
208+
209+ // Scan closed issues
210+ if (scanClosedIssues) {
211+ core.info("Scanning closed issues...");
212+ let page = 1;
213+ let closedScanned = 0;
214+
215+ while (maxIssues === 0 || closedScanned < maxIssues) {
216+ const issues = await github.rest.issues.listForRepo({
217+ owner, repo, state: 'closed', per_page: 100, page
218+ });
219+
220+ if (issues.data.length === 0) break;
221+
222+ for (const issue of issues.data) {
223+ if (issue.pull_request) continue;
224+ if (maxIssues > 0 && closedScanned >= maxIssues) break;
225+
226+ closedScanned++;
227+ const analysis = await analyzeContent(
228+ issue.body || "",
229+ issue.user?.login || "unknown",
230+ issue.author_association || "NONE"
231+ );
232+
233+ if (analysis.isSpam) {
234+ totalSpam++;
235+ core.warning(`[SPAM] Closed Issue #${issue.number} by @${issue.user?.login}`);
236+ core.warning(` Preview: ${(issue.body || "").substring(0, 150)}...`);
237+ core.warning(` Already closed, but content still visible`);
238+
239+ if (!dryRun) {
240+ try {
241+ // Rewrite the closed spam issue to remove content
242+ await github.rest.issues.update({
243+ owner, repo, issue_number: issue.number,
244+ title: "[MODERATED] Content Removed",
245+ body: "**This content has been automatically moderated and removed.**\n\n" +
246+ "The original content violated our spam policy and has been hidden.\n\n" +
247+ "_This was detected during a cleanup scan of existing content._"
248+ });
249+ totalClosed++;
250+ core.notice(`✓ Moderated closed spam issue #${issue.number}`);
251+ } catch (err) {
252+ core.error(`✗ Failed to moderate issue #${issue.number}: ${err.message}`);
253+ }
254+ }
255+ }
256+ }
257+ page++;
258+ }
259+ core.info(`Scanned ${closedScanned} closed issues`);
260+ }
261+
202262 // Scan comments
203263 if (scanComments) {
204264 core.info("Scanning issue comments...");
205265 let page = 1;
206266 let commentCount = 0;
207-
267+
208268 while (page <= 10) {
209269 const comments = await github.rest.issues.listCommentsForRepo({
210270 owner, repo, per_page: 100, page, sort: 'created', direction: 'desc'
211271 });
212-
272+
213273 if (comments.data.length === 0) break;
214-
274+
215275 for (const comment of comments.data) {
216276 commentCount++;
217277 const analysis = await analyzeContent(
218278 comment.body || "",
219279 comment.user?.login || "unknown",
220280 comment.author_association || "NONE"
221281 );
222-
282+
223283 if (analysis.isSpam) {
224284 totalSpam++;
225285 core.warning(`[SPAM] Comment #${comment.id} by @${comment.user?.login}`);
226286 core.warning(` Preview: ${(comment.body || "").substring(0, 150)}...`);
227-
287+
228288 if (!dryRun) {
229289 try {
230290 await github.rest.issues.deleteComment({
@@ -240,19 +300,25 @@ jobs:
240300 }
241301 page++;
242302 }
243-
303+
244304 core.info(`Scanned ${commentCount} comments`);
245305 }
246-
306+
247307 // Summary
248308 core.notice("=".repeat(60));
249309 core.notice(`Cleanup Summary ${dryRun ? '(DRY RUN)' : '(EXECUTED)'}`);
250- core.notice(`Total scanned: ${totalScanned} issues`);
310+ core.notice("=".repeat(60));
311+ if (scanIssues) core.notice(`✓ Scanned open issues: ${totalScanned}`);
312+ if (scanClosedIssues) core.notice(`✓ Scanned closed issues for content removal`);
313+ if (scanComments) core.notice(`✓ Scanned comments`);
314+ core.notice("");
251315 core.notice(`Total spam found: ${totalSpam}`);
252316 if (!dryRun) {
253- core.notice(`Issues closed: ${totalClosed}`);
254- core.notice(`Comments deleted: ${totalDeleted}`);
317+ core.notice(`Actions taken:`);
318+ core.notice(` - Issues closed/moderated: ${totalClosed}`);
319+ core.notice(` - Comments deleted: ${totalDeleted}`);
255320 } else {
256- core.notice("DRY RUN - No actions taken. Set dry_run to 'false' to execute.");
321+ core.notice("⚠️ DRY RUN - No actions taken");
322+ core.notice(" Set dry_run to 'false' to execute cleanup");
257323 }
258324 core.notice("=".repeat(60));
0 commit comments