Delete Spam Issues #47
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Delete Spam Issues | |
on: | |
# Run on a schedule to batch process spam issues | |
schedule: | |
- cron: '*/5 * * * *' # Run every 5 minutes | |
# Also run manually from the Actions tab | |
workflow_dispatch: | |
jobs: | |
delete-spam: | |
runs-on: ubuntu-latest | |
permissions: | |
issues: write | |
contents: read | |
steps: | |
- name: Delete spam issues with GraphQL | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ secrets.ISSUE_SPAM_PROTECTION_TOKEN }} | |
script: | | |
// Helper function to add delay between operations | |
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); | |
// Get issue ID | |
async function getIssueId(issueNumber) { | |
const query = ` | |
query GetIssueId($owner: String!, $repo: String!, $number: Int!) { | |
repository(owner: $owner, name: $repo) { | |
issue(number: $number) { | |
id | |
title | |
url | |
state | |
} | |
} | |
} | |
`; | |
try { | |
const result = await github.graphql(query, { | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
number: issueNumber | |
}); | |
if (!result.repository?.issue?.id) { | |
console.error(`Could not find GraphQL ID for issue #${issueNumber}. Full response:`, JSON.stringify(result, null, 2)); | |
return null; | |
} | |
console.log(`Found issue #${issueNumber}: "${result.repository.issue.title}" (${result.repository.issue.state})`); | |
return result.repository.issue.id; | |
} catch (error) { | |
console.error(`Failed to get issue ID for #${issueNumber}:`, error.message); | |
console.error(JSON.stringify(error, null, 2)); | |
return null; | |
} | |
} | |
// Delete issue using GraphQL | |
async function deleteIssue(issueId, issueNumber) { | |
if (!issueId) { | |
console.error(`Cannot delete issue #${issueNumber}: No valid ID provided`); | |
return false; | |
} | |
const mutation = ` | |
mutation DeleteIssue($issueId: ID!) { | |
deleteIssue(input: {issueId: $issueId}) { | |
clientMutationId | |
} | |
} | |
`; | |
try { | |
await github.graphql(mutation, { | |
issueId: issueId | |
}); | |
return true; | |
} catch (error) { | |
console.error(`Failed to delete issue #${issueNumber}:`, error.message); | |
console.error(JSON.stringify(error, null, 2)); | |
return false; | |
} | |
} | |
// Process all spam-labeled issues | |
console.log("Finding all issues labeled as spam..."); | |
// Rate limiting parameters | |
const RATE_LIMIT_DELAY = 1000; // 1 second delay between operations | |
const ITEMS_PER_PAGE = 30; // Number of items per page | |
const MAX_PAGES = 3; // Maximum number of pages to process in one run | |
let page = 1; | |
let processedCount = 0; | |
let successCount = 0; | |
let hasMorePages = true; | |
// Process issues page by page with resilience | |
while (hasMorePages && page <= MAX_PAGES) { | |
try { | |
console.log(`Fetching page ${page} of spam issues...`); | |
const issues = await github.rest.issues.listForRepo({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
state: 'all', // Get both open and closed issues | |
labels: 'spam', | |
per_page: ITEMS_PER_PAGE, | |
page: page | |
}); | |
if (issues.data.length === 0) { | |
console.log("No more issues found."); | |
hasMorePages = false; | |
break; | |
} | |
console.log(`Found ${issues.data.length} spam issues on page ${page}`); | |
for (const issue of issues.data) { | |
const issueNumber = issue.number; | |
processedCount++; | |
try { | |
// Skip if this is a pull request | |
if (issue.pull_request) { | |
console.log(`Skipping #${issueNumber} because it's a pull request, not an issue`); | |
continue; | |
} | |
console.log(`Processing spam issue #${issueNumber}: "${issue.title}"`); | |
const issueId = await getIssueId(issueNumber); | |
if (issueId) { | |
// Add delay before deletion | |
await sleep(RATE_LIMIT_DELAY); | |
const success = await deleteIssue(issueId, issueNumber); | |
if (success) { | |
console.log(`Successfully deleted issue #${issueNumber}`); | |
successCount++; | |
} | |
} | |
} catch (error) { | |
console.error(`Error processing issue #${issueNumber}:`, error.message); | |
// Continue with next issue even if this one fails | |
} | |
// Add delay after each issue processing | |
await sleep(RATE_LIMIT_DELAY); | |
} | |
page++; | |
} catch (error) { | |
console.error(`Error processing page ${page}:`, error.message); | |
break; // Break the loop if we can't process a page | |
} | |
} | |
if (hasMorePages && page > MAX_PAGES) { | |
console.log(`Pagination limit reached (${MAX_PAGES} pages). Remaining issues will be processed in future runs.`); | |
} | |
console.log(`Summary: Successfully deleted ${successCount} out of ${processedCount} processed spam issues`); |