--- name: Content Moderation 'on': issues: types: [opened, edited] issue_comment: types: [created, edited] pull_request: types: [opened, edited] pull_request_review_comment: types: [created, edited] jobs: moderate-content: runs-on: ubuntu-latest permissions: issues: write pull-requests: write contents: read steps: - name: Check for inappropriate content id: content-check uses: actions/github-script@v7 with: script: | // Define inappropriate content patterns const hateWords = [ // Racial slurs (censored for detection) 'n[i1]gg[e3]r', 'n[i1]gg[a4]', 'ch[i1]nk', 'sp[i1]c', 'k[i1]k[e3]', 'w[e3]tb[a4]ck', 'b[e3][a4]n[e3]r', 'g[o0][o0]k', 'r[a4]gh[e3][a4]d', // Homophobic/transphobic slurs 'f[a4]gg[o0]t', 'tr[a4]nn[y1]', 'd[y1]k[e3]', // Religious/ethnic slurs 'j[e3]w', 'r[a4]gh[e3][a4]d', 'c[a4]m[e3]lj[o0]ck[e3]y', // General offensive terms 'r[e3]t[a4]rd', 'sp[a4]st[i1]c' ]; const profanity = [ 'f[u*]ck[i1]ng?', 'sh[i1]t', 'b[i1]tch', 'c[u*]nt', 'p[i1]ss', 'd[a4]mn', 'h[e3]ll', 'cr[a4]p', '[a4]ss', 'b[a4]st[a4]rd' ]; const threats = [ 'k[i1]ll y[o0]u', 'd[i1][e3]', 'murd[e3]r', 'h[a4]rm y[o0]u', 'hurt y[o0]u', 'destroy y[o0]u', 'end y[o0]u' ]; // Get content based on event type let content = ''; let contentType = ''; let itemNumber = 0; if (context.eventName === 'issues') { content = context.payload.issue.title + ' ' + context.payload.issue.body; contentType = 'issue'; itemNumber = context.payload.issue.number; } else if (context.eventName === 'issue_comment') { content = context.payload.comment.body; contentType = 'issue comment'; itemNumber = context.payload.issue.number; } else if (context.eventName === 'pull_request') { content = context.payload.pull_request.title + ' ' + context.payload.pull_request.body; contentType = 'pull request'; itemNumber = context.payload.pull_request.number; } else if (context.eventName === 'pull_request_review_comment') { content = context.payload.comment.body; contentType = 'PR comment'; itemNumber = context.payload.pull_request.number; } if (!content) return; const normalizedContent = content.toLowerCase() .replace(/[^a-z0-9\s]/g, '') .replace(/\s+/g, ' '); // Check for violations let violation = null; let severity = 'low'; // Check hate speech (highest severity) for (const word of hateWords) { const regex = new RegExp(word, 'i'); if (regex.test(normalizedContent)) { violation = 'hate speech'; severity = 'critical'; break; } } // Check threats (high severity) if (!violation) { for (const threat of threats) { const regex = new RegExp(threat, 'i'); if (regex.test(normalizedContent)) { violation = 'threats'; severity = 'high'; break; } } } // Check excessive profanity (medium severity) if (!violation) { let profanityCount = 0; for (const word of profanity) { const regex = new RegExp(word, 'gi'); const matches = normalizedContent.match(regex); if (matches) profanityCount += matches.length; } if (profanityCount >= 3) { violation = 'excessive profanity'; severity = 'medium'; } } // Set outputs core.setOutput('violation', violation || 'none'); core.setOutput('severity', severity); core.setOutput('content-type', contentType); core.setOutput('item-number', itemNumber); if (violation) { console.log( `āš ļø Detected ${violation} (${severity} severity) ` + `in ${contentType} #${itemNumber}` ); } - name: Handle critical violations if: >- steps.content-check.outputs.violation != 'none' && steps.content-check.outputs.severity == 'critical' uses: actions/github-script@v7 with: script: | const itemNumber = ${{ steps.content-check.outputs.item-number }}; const contentType = '${{ steps.content-check.outputs.content-type }}'; const violation = '${{ steps.content-check.outputs.violation }}'; if (contentType === 'issue') { // Close and lock the issue immediately await github.rest.issues.update({ owner: context.repo.owner, repo: context.repo.repo, issue_number: itemNumber, state: 'closed' }); await github.rest.issues.lock({ owner: context.repo.owner, repo: context.repo.repo, issue_number: itemNumber, lock_reason: 'off-topic' }); // Add moderation comment await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: itemNumber, body: '🚫 **This issue has been automatically closed ' + 'and locked due to a Code of Conduct violation.**' + `\n\n**Violation**: ${violation}\n\n` + '**This action was taken because the content ' + 'violates our community standards. Repeated ' + 'violations may result in being blocked from ' + 'this repository.**\n\nšŸ“– Please review our ' + '[Code of Conduct](.github/CODE_OF_CONDUCT.md) ' + 'and [Contributing Guidelines]' + '(.github/CONTRIBUTING.md).' }); } - name: Handle high severity violations if: >- steps.content-check.outputs.violation != 'none' && steps.content-check.outputs.severity == 'high' uses: actions/github-script@v7 with: script: | const itemNumber = ${{ steps.content-check.outputs.item-number }}; const contentType = '${{ steps.content-check.outputs.content-type }}'; if (contentType === 'issue') { // Add warning label and comment await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: itemNumber, labels: ['moderation-review'] }); await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: itemNumber, body: `āš ļø **Content Warning**: This ${contentType} ` + 'has been flagged for moderation review due to ' + 'potentially inappropriate content.\n\nPlease ' + 'ensure all content follows our ' + '[Code of Conduct](.github/CODE_OF_CONDUCT.md). ' + 'A maintainer will review this shortly.' }); } - name: Notify maintainers if: steps.content-check.outputs.violation != 'none' uses: actions/github-script@v7 with: script: | const violation = '${{ steps.content-check.outputs.violation }}'; const severity = '${{ steps.content-check.outputs.severity }}'; const contentType = '${{ steps.content-check.outputs.content-type }}'; const itemNumber = ${{ steps.content-check.outputs.item-number }}; // Create a moderation alert issue await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: `[MODERATION ALERT] ${violation} detected in ` + `${contentType} #${itemNumber}`, body: '🚨 **Automated Moderation Alert**\n\n' + '**Details:**\n' + `- **Type**: ${violation} (${severity} severity)\n` + `- **Location**: ${contentType} #${itemNumber}\n` + '- **Action taken**: ' + (severity === 'critical' ? 'Automatically closed and locked' : 'Flagged for review') + '\n\n**Next steps:**\n' + '- [ ] Review the flagged content\n' + '- [ ] Take additional action if needed\n' + '- [ ] Consider blocking repeat offenders\n\n' + '**Links:**\n' + `- [View ${contentType} #${itemNumber}]` + `(https://github.com/${context.repo.owner}/` + `${context.repo.repo}/` + (contentType === 'issue' ? 'issues' : 'pull') + `/${itemNumber})\n\n` + 'This alert was generated automatically by the ' + 'content moderation system.', labels: ['moderation', 'automated-alert'] }); console.log( `šŸ“§ Created moderation alert for ${violation} ` + `in ${contentType} #${itemNumber}` );