Skip to content

status_page_exposed: allow using Unix socket #17

status_page_exposed: allow using Unix socket

status_page_exposed: allow using Unix socket #17

name: Diplomatize Comments
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
# Manual trigger to process existing comments
workflow_dispatch:
inputs:
issue_number:
description: 'Issue/PR number to scan for hostile comments'
required: true
type: number
dry_run:
description: 'Dry run - analyze but do not hide/rewrite'
required: false
type: boolean
default: true
jobs:
# Job for automatic comment moderation (on new comments)
diplomatize:
if: github.event_name != 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
models: read
steps:
- name: Moderate and diplomatize hostile comments
uses: actions/github-script@v7
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const originalComment = context.payload.comment.body;
const commentAuthor = context.payload.comment.user.login;
const commentNodeId = context.payload.comment.node_id;
// Don't process bot comments
if (context.payload.comment.user.type === 'Bot') {
console.log('Skipping bot comment');
return;
}
// Don't process maintainer comments
const maintainers = ['dvershinin'];
if (maintainers.includes(commentAuthor)) {
console.log(`Skipping maintainer comment from ${commentAuthor}`);
return;
}
console.log(`Checking comment from ${commentAuthor}`);
// Step 0: Check for acronyms/patterns OpenAI doesn't recognize
const knownBadPatterns = [
{ pattern: /\bgfy\b/i, reason: 'profanity acronym' },
{ pattern: /\bstfu\b/i, reason: 'profanity acronym' },
{ pattern: /\bgtfo\b/i, reason: 'profanity acronym' },
{ pattern: /\bai slop\b/i, reason: 'dismissive language' },
];
let patternMatch = null;
for (const { pattern, reason } of knownBadPatterns) {
if (pattern.test(originalComment)) {
patternMatch = reason;
break;
}
}
// Step 1: Use OpenAI's FREE moderation endpoint to check for violations
const moderationResponse = await fetch('https://api.openai.com/v1/moderations', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
input: originalComment
})
});
if (!moderationResponse.ok) {
console.log(`Moderation API error: ${moderationResponse.status}`);
return;
}
const moderationData = await moderationResponse.json();
const result = moderationData.results[0];
// Check if any category is flagged
const flaggedCategories = Object.entries(result.categories)
.filter(([_, flagged]) => flagged)
.map(([category, _]) => category);
// Also check for high scores in harassment even if not flagged
const harassmentScore = result.category_scores.harassment || 0;
const hateScore = result.category_scores.hate || 0;
const isFlagged = result.flagged || harassmentScore > 0.3 || hateScore > 0.3 || patternMatch;
if (!isFlagged) {
console.log('Comment passed moderation - no action needed');
console.log(`Scores: harassment=${harassmentScore.toFixed(3)}, hate=${hateScore.toFixed(3)}`);
return;
}
let reason;
if (patternMatch) {
reason = patternMatch;
} else if (flaggedCategories.length > 0) {
reason = flaggedCategories.join(', ');
} else {
reason = `high toxicity score (harassment: ${harassmentScore.toFixed(2)}, hate: ${hateScore.toFixed(2)})`;
}
console.log(`Comment flagged: ${reason}`);
// Step 2: Use GitHub Models to rewrite the comment professionally
const rewriteResponse = await fetch('https://models.github.ai/inference/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${{ secrets.GITHUB_TOKEN }}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
model: 'openai/gpt-4o-mini',
messages: [
{
role: 'system',
content: `Rewrite this comment to be professional and constructive.
Rules:
- PRESERVE all technical content, bug reports, criticisms, and valid points
- REMOVE profanity, insults, personal attacks, and hostile language
- Keep the same meaning and structure
- Do NOT add emojis or excessive pleasantries
- Output ONLY the rewritten comment, nothing else`
},
{
role: 'user',
content: originalComment
}
],
temperature: 0.2,
max_tokens: 2000
})
});
if (!rewriteResponse.ok) {
const errorText = await rewriteResponse.text();
console.log(`GitHub Models API error: ${rewriteResponse.status} - ${errorText}`);
return;
}
const rewriteData = await rewriteResponse.json();
const rewritten = rewriteData.choices[0].message.content.trim();
// Step 3: Hide the original comment using GraphQL
console.log('Hiding original comment...');
await github.graphql(`
mutation($id: ID!, $classifier: ReportedContentClassifiers!) {
minimizeComment(input: {subjectId: $id, classifier: $classifier}) {
minimizedComment {
isMinimized
}
}
}
`, {
id: commentNodeId,
classifier: 'ABUSE'
});
// Step 4: Post the diplomatic version
const issueNumber = context.payload.issue?.number || context.payload.pull_request?.number;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: `**Comment from @${commentAuthor}** *(reformatted for constructive discussion)*:
---
${rewritten}
---
<sub>🤖 Original hidden due to: ${reason}. Technical content preserved.
[Community guidelines](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/master/CODE_OF_CONDUCT.md)</sub>`
});
console.log('Done - comment hidden and diplomatic version posted');
# Job for manual scanning of existing comments
scan-existing:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
models: read
steps:
- name: Scan and moderate existing comments
uses: actions/github-script@v7
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
DRY_RUN: ${{ inputs.dry_run }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issueNumber = ${{ inputs.issue_number }};
const dryRun = process.env.DRY_RUN === 'true';
console.log(`\n${'='.repeat(60)}`);
console.log(`Scanning issue/PR #${issueNumber} for hostile comments`);
console.log(`Mode: ${dryRun ? '🔍 DRY RUN (analyze only)' : '⚡ LIVE (will hide and rewrite)'}`);
console.log(`${'='.repeat(60)}\n`);
// Maintainers to skip
const maintainers = ['dvershinin'];
// Bad patterns OpenAI misses
const knownBadPatterns = [
{ pattern: /\bgfy\b/i, reason: 'profanity acronym' },
{ pattern: /\bstfu\b/i, reason: 'profanity acronym' },
{ pattern: /\bgtfo\b/i, reason: 'profanity acronym' },
{ pattern: /\bai slop\b/i, reason: 'dismissive language' },
];
// Get all comments on the issue
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100
});
console.log(`Found ${comments.data.length} comments to analyze\n`);
let flaggedCount = 0;
let processedCount = 0;
for (const comment of comments.data) {
const author = comment.user.login;
const nodeId = comment.node_id;
const body = comment.body;
const commentId = comment.id;
// Skip bots and maintainers
if (comment.user.type === 'Bot') continue;
if (maintainers.includes(author)) continue;
processedCount++;
// Check patterns
let patternMatch = null;
for (const { pattern, reason } of knownBadPatterns) {
if (pattern.test(body)) {
patternMatch = reason;
break;
}
}
// Call OpenAI moderation
const moderationResponse = await fetch('https://api.openai.com/v1/moderations', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ input: body })
});
if (!moderationResponse.ok) {
console.log(`[${commentId}] Moderation API error: ${moderationResponse.status}`);
continue;
}
const moderationData = await moderationResponse.json();
const result = moderationData.results[0];
const harassmentScore = result.category_scores.harassment || 0;
const hateScore = result.category_scores.hate || 0;
const isFlagged = result.flagged || harassmentScore > 0.3 || hateScore > 0.3 || patternMatch;
const preview = body.substring(0, 60).replace(/\n/g, ' ') + (body.length > 60 ? '...' : '');
if (!isFlagged) {
console.log(`✅ [${author}] PASS (h=${harassmentScore.toFixed(2)})`);
console.log(` "${preview}"\n`);
continue;
}
flaggedCount++;
let reason;
if (patternMatch) {
reason = patternMatch;
} else if (result.flagged) {
reason = Object.entries(result.categories).filter(([_, v]) => v).map(([k]) => k).join(', ');
} else {
reason = `harassment: ${harassmentScore.toFixed(2)}, hate: ${hateScore.toFixed(2)}`;
}
console.log(`🚨 [${author}] FLAGGED - ${reason}`);
console.log(` "${preview}"`);
console.log(` URL: ${comment.html_url}`);
if (dryRun) {
console.log(` [DRY RUN] Would hide and rewrite this comment\n`);
continue;
}
// LIVE MODE: Rewrite and hide
console.log(` Rewriting...`);
const rewriteResponse = await fetch('https://models.github.ai/inference/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${{ secrets.GITHUB_TOKEN }}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
model: 'openai/gpt-4o-mini',
messages: [
{
role: 'system',
content: `Rewrite this comment to be professional and constructive.
Rules:
- PRESERVE all technical content, bug reports, criticisms, and valid points
- REMOVE profanity, insults, personal attacks, and hostile language
- Keep the same meaning and structure
- Do NOT add emojis or excessive pleasantries
- Output ONLY the rewritten comment, nothing else`
},
{ role: 'user', content: body }
],
temperature: 0.2,
max_tokens: 2000
})
});
if (!rewriteResponse.ok) {
console.log(` GitHub Models error: ${rewriteResponse.status}\n`);
continue;
}
const rewriteData = await rewriteResponse.json();
const rewritten = rewriteData.choices[0].message.content.trim();
// Hide the comment
console.log(` Hiding original...`);
await github.graphql(`
mutation($id: ID!, $classifier: ReportedContentClassifiers!) {
minimizeComment(input: {subjectId: $id, classifier: $classifier}) {
minimizedComment { isMinimized }
}
}
`, { id: nodeId, classifier: 'ABUSE' });
// Post diplomatic version
console.log(` Posting diplomatic version...`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: `**Comment from @${author}** *(reformatted for constructive discussion)*:
---
${rewritten}
---
<sub>🤖 Original hidden due to: ${reason}. Technical content preserved.
[Community guidelines](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/master/CODE_OF_CONDUCT.md)</sub>`
});
console.log(` ✅ Done!\n`);
}
console.log(`\n${'='.repeat(60)}`);
console.log(`SUMMARY`);
console.log(`${'='.repeat(60)}`);
console.log(`Comments analyzed: ${processedCount}`);
console.log(`Comments flagged: ${flaggedCount}`);
if (dryRun && flaggedCount > 0) {
console.log(`\n⚠️ Re-run with dry_run=false to actually hide and rewrite these comments`);
}