name: Issue Guard on: issues: types: [opened] issue_comment: types: [created] pull_request: types: [opened] pull_request_review_comment: types: [created] jobs: moderate: runs-on: ubuntu-latest permissions: issues: write pull-requests: write models: read contents: read steps: - uses: actions/checkout@v4 # AI 内容审核(垃圾检测 + 链接检测 + AI 生成检测) - uses: github/ai-moderator@v1 with: token: ${{ secrets.GITHUB_TOKEN }} spam-label: 'spam' ai-label: 'ai-generated' minimize-detected-comments: true enable-spam-detection: true enable-link-spam-detection: true enable-ai-detection: true # 两层防护: # 1) 账号 < 7 天 → 直接删除/关闭 # 2) 账号 >= 7 天 + AI 判定为垃圾 → 也删除/关闭 - name: Spam defense uses: actions/github-script@v7 with: script: | const MIN_ACCOUNT_AGE_DAYS = 7; const event = context.eventName; // 解析事件上下文 let username, authorAssociation, number, nodeId, commentId; let isIssue = false, isPR = false, isComment = false; if (event === 'issues') { const issue = context.payload.issue; username = issue.user.login; authorAssociation = issue.author_association; number = issue.number; isIssue = true; } else if (event === 'issue_comment') { const comment = context.payload.comment; username = comment.user.login; authorAssociation = comment.author_association; number = context.payload.issue.number; nodeId = comment.node_id; commentId = comment.id; isComment = true; isIssue = true; } else if (event === 'pull_request') { const pr = context.payload.pull_request; username = pr.user.login; authorAssociation = pr.author_association; number = pr.number; isPR = true; } else if (event === 'pull_request_review_comment') { const comment = context.payload.comment; username = comment.user.login; authorAssociation = comment.author_association; number = context.payload.pull_request.number; nodeId = comment.node_id; commentId = comment.id; isComment = true; isPR = true; } // 跳过项目成员 if (['OWNER', 'MEMBER', 'COLLABORATOR'].includes(authorAssociation)) { core.info(`Skip: ${username} (${authorAssociation})`); return; } // 获取账号年龄 const { data: user } = await github.rest.users.getByUsername({ username }); const accountAgeDays = Math.floor((Date.now() - new Date(user.created_at)) / 86400000); const isNewAccount = accountAgeDays < MIN_ACCOUNT_AGE_DAYS; // 判断是否需要拦截 let shouldBlock = false; let reason = ''; if (isNewAccount) { // 第一层:新账号直接拦截 shouldBlock = true; reason = `new account (${accountAgeDays}d < ${MIN_ACCOUNT_AGE_DAYS}d)`; } else { // 第二层:老账号检查 AI 垃圾判定 let isSpam = false; if (isComment) { const { node } = await github.graphql(` query($id: ID!) { node(id: $id) { ... on IssueComment { isMinimized } ... on PullRequestReviewComment { isMinimized } } } `, { id: nodeId }); isSpam = node?.isMinimized === true; } else { const { data: labels } = await github.rest.issues.listLabelsOnIssue({ ...context.repo, issue_number: number }); isSpam = labels.some(l => l.name === 'spam'); } if (isSpam) { shouldBlock = true; reason = `spam detected (account age: ${accountAgeDays}d)`; } } if (!shouldBlock) { core.info(`${username} (${accountAgeDays}d): OK`); return; } core.info(`Blocking ${event} #${number} from ${username}: ${reason}`); // 根据拦截原因生成不同提示 const notice = isNewAccount ? [ `@${username} 你好,感谢你对本项目的关注!`, '', '为维护社区环境、防止垃圾信息和自动化 bot 的干扰,', `本项目要求 GitHub 账号注册满 **${MIN_ACCOUNT_AGE_DAYS} 天**后才能参与互动。`, '', `你的账号注册时间为 ${accountAgeDays} 天,暂时无法参与。`, `请在账号满 ${MIN_ACCOUNT_AGE_DAYS} 天后重新提交,或通过项目 README 中的联系方式反馈。`, '', '感谢理解与支持 🙏', ].join('\n') : [ `@${username} 你好,`, '', '你的内容已被自动审核系统标记为垃圾信息并移除。', '', '**如有误判,还请见谅!** 请通过项目 README 中的联系方式反馈。', '', '感谢理解 🙏', ].join('\n'); // 执行拦截操作 if (isComment) { // 评论:直接删除 if (isIssue) { await github.rest.issues.deleteComment({ ...context.repo, comment_id: commentId }); } else { await github.rest.pulls.deleteReviewComment({ ...context.repo, comment_id: commentId }); } } else if (isIssue) { // 新建 Issue:评论提示 → 关闭 → 锁定 await github.rest.issues.createComment({ ...context.repo, issue_number: number, body: notice }); await github.rest.issues.update({ ...context.repo, issue_number: number, state: 'closed', state_reason: 'not_planned' }); await github.rest.issues.lock({ ...context.repo, issue_number: number, lock_reason: 'spam' }); } else if (isPR) { // 新建 PR:评论提示 → 关闭 await github.rest.issues.createComment({ ...context.repo, issue_number: number, body: notice }); await github.rest.pulls.update({ ...context.repo, pull_number: number, state: 'closed' }); }