diff --git a/.github/GITHUB-BOT-SETUP.md b/.github/GITHUB-BOT-SETUP.md new file mode 100644 index 0000000..6c01f81 --- /dev/null +++ b/.github/GITHUB-BOT-SETUP.md @@ -0,0 +1,159 @@ +# GitHub Bot 自动回复设置指南 + +本项目已集成GitHub Actions驱动的自动回复机器人,可以自动处理Issues和Pull Requests。 + +## 🚀 功能特性 + +### ✅ 已实现功能 +- **Issue自动回复**: 根据标题关键词智能回复 +- **PR自动回复**: 欢迎新贡献者并提供审核清单 +- **智能标签**: 根据内容自动添加相关标签 +- **定期状态更新**: 每周检查并更新长期无响应的issue +- **生成周报**: 统计项目活动数据 + +### 🤖 自动回复类型 + +#### Issue回复模板 +- 🐛 **Bug报告**: 引导用户提供复现步骤和环境信息 +- 🚀 **功能建议**: 说明评估流程和时间安排 +- 📖 **文档问题**: 承诺改进文档质量 +- 👋 **通用回复**: 感谢反馈并说明处理流程 + +#### PR回复模板 +- 🎉 **欢迎贡献者**: 感谢贡献并说明审核流程 +- 📋 **审核清单**: 提醒代码规范、测试、文档等要求 +- 📊 **PR统计**: 显示修改文件数和PR类型 +- 🏷️ **自动标签**: 根据内容和规模自动分类 + +## 📋 设置步骤 + +### 1. 创建所需标签 + +在仓库的 **Issues** → **Labels** 页面创建以下标签: + +#### 基础标签 +``` +bug - 🐛 - #d73a49 - Bug报告 +enhancement - ✨ - #a2eeef - 功能增强 +documentation - 📖 - #0075ca - 文档相关 +question - ❓ - #d876e3 - 问题咨询 +performance - ⚡ - #fbca04 - 性能优化 +security - 🔒 - #b60205 - 安全相关 +``` + +#### 优先级标签 +``` +priority/high - 🔴 - #b60205 - 高优先级 +priority/medium - 🟡 - #fbca04 - 中优先级 +priority/low - 🟢 - #0e8a16 - 低优先级 +``` + +#### PR大小标签 +``` +size/small - S - #28a745 - 小型PR (≤3文件) +size/medium - M - #ffc107 - 中型PR (4-10文件) +size/large - L - #dc3545 - 大型PR (>10文件) +``` + +#### 平台标签 +``` +platform/windows - 🪟 - #0078d4 - Windows平台 +platform/linux - 🐧 - #f89820 - Linux平台 +platform/mac - 🍎 - #999999 - macOS平台 +``` + +#### 组件标签 +``` +component/bilibili - 📺 - #00a1d6 - B站相关 +component/tiktok - 🎵 - #ff0050 - 抖音相关 +component/youtube - ▶️ - #ff0000 - YouTube相关 +component/music - 🎵 - #1db954 - 音乐功能 +``` + +#### 状态标签 +``` +stale - ⏰ - #795548 - 长期无响应 +needs-response - 💬 - #8e44ad - 需要回复 +refactor - ♻️ - #00d4aa - 重构相关 +test - 🧪 - #17becf - 测试相关 +``` + +### 2. 启用GitHub Actions + +1. 进入仓库 **Settings** → **Actions** → **General** +2. 选择 **Allow all actions and reusable workflows** +3. 在 **Workflow permissions** 部分选择 **Read and write permissions** +4. 勾选 **Allow GitHub Actions to create and approve pull requests** + +### 3. 创建里程碑(可选) + +为bug自动分配功能创建里程碑: +1. 进入 **Issues** → **Milestones** +2. 创建如 `v1.1.0`、`v1.2.0` 等版本里程碑 +3. bot会自动将bug分配到最近的开放里程碑 + +### 4. 测试设置 + +创建一个测试issue验证: +1. 标题包含 "bug" 或 "功能" 关键词 +2. 查看是否收到自动回复 +3. 检查是否自动添加了相关标签 + +## 🔧 自定义配置 + +### 修改回复模板 + +编辑 `.github/workflows/` 目录下的workflow文件: +- `auto-reply-issues.yml` - Issue回复模板 +- `auto-reply-prs.yml` - PR回复模板 +- `auto-label.yml` - 标签规则 +- `status-update.yml` - 定期更新规则 + +### 调整触发条件 + +修改workflow文件中的 `on` 部分: +```yaml +on: + issues: + types: [opened, labeled] # 添加labeled触发 + pull_request: + types: [opened, edited] # 添加edited触发 +``` + +### 修改定时任务 + +在 `status-update.yml` 中调整cron表达式: +```yaml +on: + schedule: + - cron: '0 2 * * *' # 每天凌晨2点运行 +``` + +## 📈 监控和维护 + +### 查看运行日志 +1. 进入 **Actions** 页面 +2. 点击具体的workflow运行记录 +3. 查看详细日志和错误信息 + +### 常见问题排查 +- **权限错误**: 检查Actions权限设置 +- **标签不存在**: 确保创建了所有必需的标签 +- **API限制**: GitHub API有频率限制,大量操作时可能触发 + +### 性能优化建议 +- 限制单次处理的issue数量(当前设置为50) +- 合理设置触发条件避免重复运行 +- 定期清理过时的workflow运行记录 + +## 🎯 最佳实践 + +1. **渐进式部署**: 先在测试仓库验证功能 +2. **定期审查**: 每月检查自动回复效果和用户反馈 +3. **模板优化**: 根据项目特点调整回复内容 +4. **标签管理**: 保持标签体系简洁清晰 +5. **用户反馈**: 收集用户对自动回复的意见 + +--- + +📝 **注意**: 此bot完全基于GitHub Actions,无需外部服务器,维护成本极低。所有配置都通过workflow文件管理,便于版本控制和团队协作。 \ No newline at end of file diff --git a/.github/workflows/auto-label.yml b/.github/workflows/auto-label.yml new file mode 100644 index 0000000..1530113 --- /dev/null +++ b/.github/workflows/auto-label.yml @@ -0,0 +1,132 @@ +name: Auto Label Issues + +on: + issues: + types: [opened] + +jobs: + auto-label: + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + steps: + - name: Add Labels Based on Content + uses: actions/github-script@v7 + with: + script: | + const title = context.payload.issue.title.toLowerCase(); + const body = context.payload.issue.body?.toLowerCase() || ''; + const labels = []; + + // 根据关键词自动添加标签 + if (title.includes('bug') || title.includes('错误') || title.includes('问题') || + body.includes('bug') || body.includes('错误') || body.includes('异常')) { + labels.push('bug'); + } + + if (title.includes('feature') || title.includes('功能') || title.includes('建议') || + title.includes('enhancement') || body.includes('功能') || body.includes('建议')) { + labels.push('enhancement'); + } + + if (title.includes('doc') || title.includes('文档') || title.includes('说明') || + body.includes('文档') || body.includes('说明') || body.includes('document')) { + labels.push('documentation'); + } + + if (title.includes('help') || title.includes('求助') || title.includes('question') || + title.includes('如何') || title.includes('怎么') || body.includes('求助')) { + labels.push('question'); + } + + if (title.includes('性能') || title.includes('performance') || title.includes('优化') || + body.includes('性能') || body.includes('慢') || body.includes('优化')) { + labels.push('performance'); + } + + if (title.includes('安全') || title.includes('security') || + body.includes('安全') || body.includes('漏洞')) { + labels.push('security'); + } + + // 根据关键词添加优先级标签 + if (title.includes('紧急') || title.includes('urgent') || title.includes('critical') || + body.includes('紧急') || body.includes('严重')) { + labels.push('priority/high'); + } else if (title.includes('minor') || title.includes('小') || body.includes('小问题')) { + labels.push('priority/low'); + } else { + labels.push('priority/medium'); + } + + // 根据平台关键词添加标签 + if (title.includes('windows') || body.includes('windows')) { + labels.push('platform/windows'); + } + if (title.includes('linux') || body.includes('linux')) { + labels.push('platform/linux'); + } + if (title.includes('mac') || title.includes('darwin') || body.includes('mac')) { + labels.push('platform/mac'); + } + + // 根据组件关键词添加标签 + if (title.includes('bilibili') || title.includes('bili') || body.includes('bilibili')) { + labels.push('component/bilibili'); + } + if (title.includes('tiktok') || title.includes('抖音') || body.includes('tiktok')) { + labels.push('component/tiktok'); + } + if (title.includes('youtube') || body.includes('youtube')) { + labels.push('component/youtube'); + } + if (title.includes('music') || title.includes('音乐') || body.includes('音乐')) { + labels.push('component/music'); + } + + if (labels.length > 0) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: labels + }); + } + + console.log(`Added labels: ${labels.join(', ')}`); + + - name: Add Milestone for Bugs + uses: actions/github-script@v7 + with: + script: | + const labels = context.payload.issue.labels.map(label => label.name); + const title = context.payload.issue.title.toLowerCase(); + + // 为bug类issue自动分配到下个版本里程碑 + if (labels.includes('bug') || title.includes('bug') || title.includes('错误')) { + try { + // 获取所有开放的里程碑 + const { data: milestones } = await github.rest.issues.listMilestones({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + sort: 'due_on', + direction: 'asc' + }); + + if (milestones.length > 0) { + // 分配到最近的里程碑 + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + milestone: milestones[0].number + }); + + console.log(`Assigned to milestone: ${milestones[0].title}`); + } + } catch (error) { + console.log('No milestones found or error assigning milestone'); + } + } \ No newline at end of file diff --git a/.github/workflows/auto-reply-issues.yml b/.github/workflows/auto-reply-issues.yml new file mode 100644 index 0000000..389197b --- /dev/null +++ b/.github/workflows/auto-reply-issues.yml @@ -0,0 +1,81 @@ +name: Auto Reply to Issues + +on: + issues: + types: [opened] + +jobs: + auto-reply: + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + steps: + - name: Reply to Issue + uses: actions/github-script@v7 + with: + script: | + const issueNumber = context.issue.number; + const issueTitle = context.payload.issue.title; + const issueAuthor = context.payload.issue.user.login; + + // 根据标题关键词判断类型 + let replyMessage = ''; + let emoji = '👋'; + + if (issueTitle.toLowerCase().includes('bug') || issueTitle.toLowerCase().includes('错误') || issueTitle.toLowerCase().includes('问题')) { + emoji = '🐛'; + replyMessage = `${emoji} Hi @${issueAuthor}! + +感谢您报告这个bug!我们已经收到您的反馈,会尽快调查并修复。 + +为了更好地帮助您解决问题,请确保提供: +- [ ] 详细的问题描述 +- [ ] 复现步骤 +- [ ] 运行环境信息 +- [ ] 相关的错误日志 + +我们会尽快回复您!`; + } else if (issueTitle.toLowerCase().includes('feature') || issueTitle.toLowerCase().includes('功能') || issueTitle.toLowerCase().includes('建议')) { + emoji = '🚀'; + replyMessage = `${emoji} Hi @${issueAuthor}! + +感谢您的功能建议!我们很高兴收到新的想法和建议。 + +我们会仔细评估您的建议: +- [ ] 评估技术可行性 +- [ ] 分析对现有功能的影响 +- [ ] 确定开发优先级 + +如果您有更多细节或使用场景,欢迎补充!`; + } else if (issueTitle.toLowerCase().includes('doc') || issueTitle.toLowerCase().includes('文档') || issueTitle.toLowerCase().includes('说明')) { + emoji = '📖'; + replyMessage = `${emoji} Hi @${issueAuthor}! + +感谢您关注文档改进!清晰的文档对项目非常重要。 + +我们会: +- [ ] 审查当前文档内容 +- [ ] 补充缺失的说明 +- [ ] 优化文档结构 + +您的反馈很有价值!`; + } else { + replyMessage = `${emoji} Hi @${issueAuthor}! + +感谢您提交issue!我们已经收到您的反馈。 + +我们会尽快处理您的请求: +- [ ] 分析问题内容 +- [ ] 确定处理方案 +- [ ] 及时反馈进展 + +如有任何疑问,随时与我们联系!`; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: replyMessage + }); \ No newline at end of file diff --git a/.github/workflows/auto-reply-prs.yml b/.github/workflows/auto-reply-prs.yml new file mode 100644 index 0000000..77db957 --- /dev/null +++ b/.github/workflows/auto-reply-prs.yml @@ -0,0 +1,94 @@ +name: Auto Reply to Pull Requests + +on: + pull_request: + types: [opened] + +jobs: + auto-reply: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - name: Reply to PR + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.issue.number; + const prTitle = context.payload.pull_request.title; + const prAuthor = context.payload.pull_request.user.login; + const prFiles = context.payload.pull_request.changed_files; + + let replyMessage = `🎉 Hi @${prAuthor}! + +感谢您的贡献!您的Pull Request已经提交,我们会尽快进行代码审核。 + +## 📋 审核清单 +期间请确保以下项目已完成: +- [ ] 代码符合项目规范和风格 +- [ ] 已测试新功能或修复 +- [ ] 更新了相关文档(如有需要) +- [ ] 提交信息清晰明确 + +## 📊 PR 统计 +- 📁 修改文件数:${prFiles} +- 🏷️ PR类型:${prTitle.toLowerCase().includes('fix') ? '🐛 Bug修复' : + prTitle.toLowerCase().includes('feat') ? '✨ 新功能' : + prTitle.toLowerCase().includes('doc') ? '📖 文档更新' : + prTitle.toLowerCase().includes('refactor') ? '♻️ 重构' : '🔧 其他改进'} + +## 🔄 下一步 +我们会在1-3个工作日内完成初步审核。如有问题会及时与您沟通。 + +再次感谢您对项目的贡献! 🙏`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: replyMessage + }); + + - name: Add PR Labels + uses: actions/github-script@v7 + with: + script: | + const prTitle = context.payload.pull_request.title.toLowerCase(); + const labels = []; + + // 根据PR标题自动添加标签 + if (prTitle.includes('fix') || prTitle.includes('修复')) { + labels.push('bug'); + } + if (prTitle.includes('feat') || prTitle.includes('功能') || prTitle.includes('add')) { + labels.push('enhancement'); + } + if (prTitle.includes('doc') || prTitle.includes('文档')) { + labels.push('documentation'); + } + if (prTitle.includes('refactor') || prTitle.includes('重构')) { + labels.push('refactor'); + } + if (prTitle.includes('test') || prTitle.includes('测试')) { + labels.push('test'); + } + + // 根据文件数量添加大小标签 + const fileCount = context.payload.pull_request.changed_files; + if (fileCount <= 3) { + labels.push('size/small'); + } else if (fileCount <= 10) { + labels.push('size/medium'); + } else { + labels.push('size/large'); + } + + if (labels.length > 0) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: labels + }); + } \ No newline at end of file diff --git a/.github/workflows/status-update.yml b/.github/workflows/status-update.yml new file mode 100644 index 0000000..f3a6967 --- /dev/null +++ b/.github/workflows/status-update.yml @@ -0,0 +1,150 @@ +name: Weekly Status Update + +on: + schedule: + - cron: '0 10 * * 1' # 每周一上午10点(UTC时间) + workflow_dispatch: # 允许手动触发 + +jobs: + status-update: + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + steps: + - name: Update Stale Issues + uses: actions/github-script@v7 + with: + script: | + const { data: issues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + sort: 'updated', + direction: 'asc', + per_page: 50 + }); + + const now = new Date(); + let updatedCount = 0; + + for (const issue of issues) { + // 跳过PR + if (issue.pull_request) continue; + + const lastUpdated = new Date(issue.updated_at); + const daysSinceUpdate = Math.floor((now - lastUpdated) / (1000 * 60 * 60 * 24)); + const daysSinceCreated = Math.floor((now - new Date(issue.created_at)) / (1000 * 60 * 60 * 24)); + + const hasStaleLabel = issue.labels.some(label => label.name === 'stale'); + const hasNeedsResponseLabel = issue.labels.some(label => label.name === 'needs-response'); + const isPriorityHigh = issue.labels.some(label => label.name === 'priority/high'); + + // 7天无更新且不是高优先级的issue + if (daysSinceUpdate >= 7 && !hasStaleLabel && !isPriorityHigh) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `👋 这个issue已经一周没有更新了。 + +如果问题仍然存在,请提供更多信息: +- 是否还能复现? +- 有没有新的错误信息? +- 是否尝试了其他解决方案? + +如果问题已解决,欢迎关闭此issue。如果30天内没有响应,此issue将被自动关闭。` + }); + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['stale'] + }); + + updatedCount++; + } + + // 30天无更新的stale issue自动关闭 + else if (daysSinceUpdate >= 30 && hasStaleLabel) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `🤖 此issue因长期无响应被自动关闭。 + +如果问题仍然存在,请创建新的issue并提供详细信息。感谢您的理解!` + }); + + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed', + state_reason: 'not_planned' + }); + + updatedCount++; + } + + // 需要回复的issue提醒 + else if (daysSinceUpdate >= 3 && hasNeedsResponseLabel) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `⏰ 提醒:此issue等待回复已超过3天。 + +我们会尽快处理,感谢您的耐心等待!` + }); + + updatedCount++; + } + } + + console.log(`Updated ${updatedCount} issues`); + + - name: Generate Weekly Report + uses: actions/github-script@v7 + with: + script: | + const oneWeekAgo = new Date(); + oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); + + // 获取本周的issues + const { data: newIssues } = await github.rest.search.issuesAndPullRequests({ + q: `repo:${context.repo.owner}/${context.repo.repo} is:issue created:>=${oneWeekAgo.toISOString().split('T')[0]}`, + sort: 'created', + order: 'desc' + }); + + // 获取本周的PRs + const { data: newPRs } = await github.rest.search.issuesAndPullRequests({ + q: `repo:${context.repo.owner}/${context.repo.repo} is:pr created:>=${oneWeekAgo.toISOString().split('T')[0]}`, + sort: 'created', + order: 'desc' + }); + + // 获取本周关闭的issues + const { data: closedIssues } = await github.rest.search.issuesAndPullRequests({ + q: `repo:${context.repo.owner}/${context.repo.repo} is:issue closed:>=${oneWeekAgo.toISOString().split('T')[0]}`, + sort: 'updated', + order: 'desc' + }); + + const report = `📊 **本周活动报告** (${oneWeekAgo.toISOString().split('T')[0]} ~ ${new Date().toISOString().split('T')[0]}) + +## 📈 统计数据 +- 🎯 新增 Issues: ${newIssues.total_count} +- 🔀 新增 PRs: ${newPRs.total_count} +- ✅ 关闭 Issues: ${closedIssues.total_count} + +## 🏷️ Issue 分类 +${newIssues.items.length > 0 ? newIssues.items.slice(0, 5).map(issue => + `- [${issue.title}](${issue.html_url}) - ${issue.labels.map(l => l.name).join(', ') || '未分类'}` +).join('\n') : '本周暂无新issue'} + +感谢所有贡献者的参与! 🙏`; + + console.log(report); \ No newline at end of file