|
| 1 | +name: Auto-merge approved submission |
| 2 | + |
| 3 | +on: |
| 4 | + issues: |
| 5 | + types: [labeled] |
| 6 | + |
| 7 | +concurrency: |
| 8 | + group: approve-submission |
| 9 | + cancel-in-progress: false # 不取消排队中的,按顺序跑 |
| 10 | + |
| 11 | +jobs: |
| 12 | + merge: |
| 13 | + if: github.event.label.name == 'approved' |
| 14 | + runs-on: ubuntu-latest |
| 15 | + permissions: |
| 16 | + contents: write |
| 17 | + issues: write |
| 18 | + |
| 19 | + steps: |
| 20 | + - uses: actions/checkout@v4 |
| 21 | + with: |
| 22 | + ref: main |
| 23 | + fetch-depth: 1 |
| 24 | + |
| 25 | + - name: Parse issue and merge script |
| 26 | + uses: actions/github-script@v7 |
| 27 | + with: |
| 28 | + script: | |
| 29 | + const issue = context.payload.issue; |
| 30 | + const body = issue.body || ''; |
| 31 | +
|
| 32 | + // ── 1. 解析 Issue body ── |
| 33 | + const field = (key) => { |
| 34 | + const re = new RegExp(`${key}:\\s*(.+)`, 'i'); |
| 35 | + const m = body.match(re); |
| 36 | + return m ? m[1].trim() : ''; |
| 37 | + }; |
| 38 | +
|
| 39 | + const author = field('作者') || field('Author'); |
| 40 | + const category = field('分类') || field('Category'); |
| 41 | + const name = field('描述') || issue.title.replace(/^\[投稿\]\s*/, ''); |
| 42 | + const desc = field('描述') || ''; |
| 43 | + const scriptName = issue.title.replace(/^\[投稿\]\s*/, '').trim(); |
| 44 | +
|
| 45 | + // 提取代码块 |
| 46 | + const codeMatch = body.match(/---\s*代码\s*---\s*\n([\s\S]+)$/i) |
| 47 | + || body.match(/```(?:python)?\n([\s\S]+?)```/); |
| 48 | + if (!codeMatch) { |
| 49 | + await github.rest.issues.createComment({ |
| 50 | + owner: context.repo.owner, |
| 51 | + repo: context.repo.repo, |
| 52 | + issue_number: issue.number, |
| 53 | + body: '❌ 无法从 Issue 中提取代码,请检查格式。' |
| 54 | + }); |
| 55 | + return; |
| 56 | + } |
| 57 | + const code = codeMatch[1].trimEnd(); |
| 58 | +
|
| 59 | + // 映射分类 |
| 60 | + const catMap = { |
| 61 | + '基础脚本': 'basic', 'basic': 'basic', 'Basics': 'basic', |
| 62 | + 'UI 界面': 'ui', 'UI': 'ui', 'ui': 'ui', |
| 63 | + '游戏': 'games', 'Games': 'games', 'games': 'games', |
| 64 | + '小组件': 'widgets', 'Widgets': 'widgets', 'widgets': 'widgets', |
| 65 | + '其他': 'other', 'Other': 'other', 'other': 'other' |
| 66 | + }; |
| 67 | + const catId = catMap[category] || 'basic'; |
| 68 | +
|
| 69 | + // 生成安全的文件 ID |
| 70 | + const safeId = scriptName |
| 71 | + .toLowerCase() |
| 72 | + .replace(/[\s]+/g, '_') |
| 73 | + .replace(/[^a-z0-9_\u4e00-\u9fff]/g, '') |
| 74 | + .substring(0, 40) || `submission_${issue.number}`; |
| 75 | + const scriptId = `community_${safeId}`; |
| 76 | +
|
| 77 | + const catDir = { basic: 'basic', ui: 'ui', games: 'games', widgets: 'widgets', other: 'other' }; |
| 78 | + const dir = catDir[catId] || 'basic'; |
| 79 | +
|
| 80 | + // ── 2. 读取现有 index.json ── |
| 81 | + const fs = require('fs'); |
| 82 | + const indexPath = 'script_library/index.json'; |
| 83 | + const indexRaw = fs.readFileSync(indexPath, 'utf8'); |
| 84 | + const index = JSON.parse(indexRaw); |
| 85 | +
|
| 86 | + // 检查重复 |
| 87 | + if (index.scripts.some(s => s.id === scriptId)) { |
| 88 | + await github.rest.issues.createComment({ |
| 89 | + owner: context.repo.owner, |
| 90 | + repo: context.repo.repo, |
| 91 | + issue_number: issue.number, |
| 92 | + body: `⚠️ 脚本 ID \`${scriptId}\` 已存在,跳过。` |
| 93 | + }); |
| 94 | + return; |
| 95 | + } |
| 96 | +
|
| 97 | + // ── 3. 写入脚本文件 ── |
| 98 | + const scriptRelPath = `scripts/${dir}/${scriptId}.py`; |
| 99 | + const scriptFullPath = `script_library/${scriptRelPath}`; |
| 100 | + const scriptDir = require('path').dirname(scriptFullPath); |
| 101 | + fs.mkdirSync(scriptDir, { recursive: true }); |
| 102 | + fs.writeFileSync(scriptFullPath, code + '\n', 'utf8'); |
| 103 | +
|
| 104 | + const lineCount = code.split('\n').length; |
| 105 | +
|
| 106 | + // ── 4. 更新 index.json ── |
| 107 | + index.data_version += 1; |
| 108 | + index.updated = new Date().toISOString().replace(/\.\d+Z$/, 'Z'); |
| 109 | +
|
| 110 | + index.scripts.push({ |
| 111 | + id: scriptId, |
| 112 | + name: scriptName, |
| 113 | + name_en: scriptName, |
| 114 | + desc: desc || scriptName, |
| 115 | + desc_en: desc || scriptName, |
| 116 | + category: catId, |
| 117 | + file: scriptRelPath, |
| 118 | + thumbnail: null, |
| 119 | + version: 1, |
| 120 | + file_type: 'py', |
| 121 | + author: author || '社区投稿', |
| 122 | + author_en: author || 'Community', |
| 123 | + tags: ['community', catId], |
| 124 | + requires: [], |
| 125 | + min_app_version: '1.5.0', |
| 126 | + added: new Date().toISOString().split('T')[0], |
| 127 | + updated: null, |
| 128 | + status: 'active', |
| 129 | + lines: lineCount |
| 130 | + }); |
| 131 | +
|
| 132 | + fs.writeFileSync(indexPath, JSON.stringify(index, null, 2) + '\n', 'utf8'); |
| 133 | +
|
| 134 | + // ── 5. Git commit & push ── |
| 135 | + const { execSync } = require('child_process'); |
| 136 | + execSync('git config user.name "github-actions[bot]"'); |
| 137 | + execSync('git config user.email "github-actions[bot]@users.noreply.github.com"'); |
| 138 | + execSync(`git add "${scriptFullPath}" "${indexPath}"`); |
| 139 | + execSync(`git commit -m "✅ 收录社区投稿: ${scriptName} (#${issue.number})"`); |
| 140 | +
|
| 141 | + // 推送前拉取最新(防止与其他 workflow 微小时差冲突) |
| 142 | + for (let attempt = 0; attempt < 3; attempt++) { |
| 143 | + try { |
| 144 | + execSync('git pull --rebase origin main'); |
| 145 | + execSync('git push origin main'); |
| 146 | + break; |
| 147 | + } catch (e) { |
| 148 | + if (attempt === 2) throw e; |
| 149 | + execSync('sleep 3'); |
| 150 | + } |
| 151 | + } |
| 152 | +
|
| 153 | + // ── 6. 关闭 Issue 并评论 ── |
| 154 | + await github.rest.issues.createComment({ |
| 155 | + owner: context.repo.owner, |
| 156 | + repo: context.repo.repo, |
| 157 | + issue_number: issue.number, |
| 158 | + body: [ |
| 159 | + `✅ **已收录!** 感谢 @${issue.user?.login || author} 的投稿。`, |
| 160 | + '', |
| 161 | + `- 📄 脚本: \`${scriptId}.py\``, |
| 162 | + `- 📁 分类: ${category}`, |
| 163 | + `- 📊 ${lineCount} 行代码`, |
| 164 | + '', |
| 165 | + '脚本将在用户下次刷新脚本库时可见。' |
| 166 | + ].join('\n') |
| 167 | + }); |
| 168 | +
|
| 169 | + await github.rest.issues.update({ |
| 170 | + owner: context.repo.owner, |
| 171 | + repo: context.repo.repo, |
| 172 | + issue_number: issue.number, |
| 173 | + state: 'closed' |
| 174 | + }); |
| 175 | +
|
| 176 | + core.info(`Successfully merged: ${scriptId} (${lineCount} lines)`); |
0 commit comments