Skip to content

Commit 7f9b0a0

Browse files
添加自动合并投稿 workflow
1 parent dadc1f3 commit 7f9b0a0

1 file changed

Lines changed: 176 additions & 0 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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

Comments
 (0)