qq-bot-roost 0.8.12 → 0.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/gitee.js +23 -3
- package/src/plugin/market.js +107 -4
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="零依赖">
|
|
11
11
|
<img src="https://img.shields.io/badge/tests-600%2B%20passed-brightgreen" alt="测试 600+ 项">
|
|
12
12
|
<img src="https://img.shields.io/badge/plugins-JS%20%7C%20Python%20%7C%20Go%20%7C%20any-blue" alt="多语言插件">
|
|
13
|
-
<img src="https://img.shields.io/badge/version-0.8.
|
|
13
|
+
<img src="https://img.shields.io/badge/version-0.8.13-blueviolet" alt="版本 0.8.13">
|
|
14
14
|
<img src="https://img.shields.io/badge/license-Non--Commercial-red" alt="禁止商用">
|
|
15
15
|
<a href="https://github.com/tdcq9594/qq-bot-roost/actions"><img src="https://img.shields.io/github/actions/workflow/status/tdcq9594/qq-bot-roost/ci.yml?branch=master" alt="CI"></a>
|
|
16
16
|
|
package/package.json
CHANGED
package/src/gitee.js
CHANGED
|
@@ -69,7 +69,14 @@ export function giteeRawUrl(repo, ref, filePath) {
|
|
|
69
69
|
* 如 ['src/', 'package.json'];不传拉全仓
|
|
70
70
|
* @param {number} [o.timeoutMs] 单文件超时
|
|
71
71
|
* @param {(msg:string)=>void} [o.onProgress] 每拉完一个文件调一次
|
|
72
|
-
* @
|
|
72
|
+
* @param {boolean} [o.allowPartial] 有文件拉不下时**不抛错**,把失败清单放进
|
|
73
|
+
* 返回值的 `failed` 里,交给调用方用别的源补拉。默认 false(整体中止)。
|
|
74
|
+
*
|
|
75
|
+
* 为什么要这个开关:Gitee 会对个别文件返回 451(内容审查),而**同一个
|
|
76
|
+
* 文件在 GitHub 上是好的**(实测 douluo 的 content-pack.sql 等 4 个文件)。
|
|
77
|
+
* 以前只要有一个失败就整包中止,等于一个文件被屏蔽 → 整个插件装不上。
|
|
78
|
+
*
|
|
79
|
+
* @returns {Promise<{files:number, skipped:number, failed:Array<{path:string,error:string}>}>}
|
|
73
80
|
*/
|
|
74
81
|
export async function fetchGiteeTree({
|
|
75
82
|
repo,
|
|
@@ -79,6 +86,7 @@ export async function fetchGiteeTree({
|
|
|
79
86
|
only = null,
|
|
80
87
|
timeoutMs = 120000,
|
|
81
88
|
onProgress,
|
|
89
|
+
allowPartial = false,
|
|
82
90
|
}) {
|
|
83
91
|
if (!repo || !/^[\w.-]+\/[\w.-]+$/.test(repo)) throw new Error(`Gitee 仓库名不合法:${repo}`);
|
|
84
92
|
if (!ref) throw new Error('Gitee 下载缺少分支/tag');
|
|
@@ -121,6 +129,7 @@ export async function fetchGiteeTree({
|
|
|
121
129
|
await fs.mkdir(destDir, { recursive: true });
|
|
122
130
|
let done = 0;
|
|
123
131
|
let failed = 0;
|
|
132
|
+
const failedList = [];
|
|
124
133
|
let cursor = 0;
|
|
125
134
|
|
|
126
135
|
async function worker() {
|
|
@@ -145,7 +154,15 @@ export async function fetchGiteeTree({
|
|
|
145
154
|
signal: AbortSignal.timeout(timeoutMs),
|
|
146
155
|
});
|
|
147
156
|
if (res.ok) break;
|
|
148
|
-
|
|
157
|
+
/**
|
|
158
|
+
* 451 = Unavailable For Legal Reasons。Gitee 会对个别文件返回它
|
|
159
|
+
* (内容审查),而同一个文件在 GitHub 上是 200。光写「HTTP 451」
|
|
160
|
+
* 没人看得懂该去换下载源,所以翻成人话。
|
|
161
|
+
*/
|
|
162
|
+
lastErr =
|
|
163
|
+
res.status === 451
|
|
164
|
+
? new Error('HTTP 451(Gitee 上这个文件不可用,多半被内容审查挡了)')
|
|
165
|
+
: new Error(`HTTP ${res.status}`);
|
|
149
166
|
if (!isRetryable(res, null)) {
|
|
150
167
|
res = null;
|
|
151
168
|
break;
|
|
@@ -169,6 +186,7 @@ export async function fetchGiteeTree({
|
|
|
169
186
|
onProgress?.(`${done}/${files.length} ${f.path}`);
|
|
170
187
|
} catch (e) {
|
|
171
188
|
failed += 1;
|
|
189
|
+
failedList.push({ path: f.path, error: e.message });
|
|
172
190
|
log.warn(`拉取失败 ${f.path}: ${e.message}`);
|
|
173
191
|
}
|
|
174
192
|
}
|
|
@@ -177,9 +195,11 @@ export async function fetchGiteeTree({
|
|
|
177
195
|
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, files.length) }, worker));
|
|
178
196
|
|
|
179
197
|
if (failed) {
|
|
198
|
+
// 调用方要自己补拉就不抛 —— 它补完还失败会自己报错,那时信息更全
|
|
199
|
+
if (allowPartial) return { files: done, skipped: files.length - done, failed: failedList };
|
|
180
200
|
throw new Error(`Gitee 下载 ${repo}@${ref}:${files.length} 个文件里 ${failed} 个失败,已中止`);
|
|
181
201
|
}
|
|
182
|
-
return { files: done, skipped: files.length - done };
|
|
202
|
+
return { files: done, skipped: files.length - done, failed: [] };
|
|
183
203
|
}
|
|
184
204
|
|
|
185
205
|
/**
|
package/src/plugin/market.js
CHANGED
|
@@ -251,6 +251,69 @@ export async function verifyPluginDir(entry, dir) {
|
|
|
251
251
|
* @param {number} [o.timeoutMs]
|
|
252
252
|
* @param {(msg:string)=>void} [o.onRetry]
|
|
253
253
|
*/
|
|
254
|
+
/**
|
|
255
|
+
* 单个文件的 GitHub raw 地址。
|
|
256
|
+
* 走 `fetchViaMirror`,所以自定义镜像 / 代理一样生效。
|
|
257
|
+
*/
|
|
258
|
+
function githubRawUrl(repo, ref, filePath) {
|
|
259
|
+
const p = String(filePath ?? '')
|
|
260
|
+
.split('/')
|
|
261
|
+
.map((s) => encodeURIComponent(s))
|
|
262
|
+
.join('/');
|
|
263
|
+
return `https://raw.githubusercontent.com/${repo}/${encodeURIComponent(ref)}/${p}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 把 Gitee 拉不下来的那几个文件,**逐个**从 GitHub 补回来。
|
|
268
|
+
*
|
|
269
|
+
* 为什么只补这几个而不是整包重拉:Gitee 的 451 是**按文件**来的 —— douluo
|
|
270
|
+
* 那么大一个插件只有 4 个文件被挡,其余都拉成功了。为 4 个文件重拉整包
|
|
271
|
+
* (几十上百个文件)纯属浪费,国内网络下很容易再断一次。
|
|
272
|
+
*
|
|
273
|
+
* @param {Array<{path:string,error:string}>} failed fetchGiteeTree 返回的失败清单
|
|
274
|
+
* @param {(url:string, init?:object)=>Promise<Response>} [fetcher] 取数函数,默认
|
|
275
|
+
* 走镜像。**给测试注入假网络用**,正常调用不需要传。
|
|
276
|
+
* @returns {Promise<Array<{path:string,error:string}>>} 补拉后仍然失败的(空数组 = 全补齐了)
|
|
277
|
+
*
|
|
278
|
+
* @internal 导出只为可测(selftest 里用它跑假网络),不是公共 API。
|
|
279
|
+
*/
|
|
280
|
+
export async function backfillFromGitHub({
|
|
281
|
+
repo,
|
|
282
|
+
branch,
|
|
283
|
+
destDir,
|
|
284
|
+
failed,
|
|
285
|
+
timeoutMs = 120000,
|
|
286
|
+
onProgress,
|
|
287
|
+
fetcher = fetchViaMirror,
|
|
288
|
+
}) {
|
|
289
|
+
const still = [];
|
|
290
|
+
for (const item of failed) {
|
|
291
|
+
const dest = path.resolve(path.join(destDir, item.path));
|
|
292
|
+
// 清单来自服务端,仍按不可信处理,防路径穿越
|
|
293
|
+
const base = path.resolve(destDir);
|
|
294
|
+
if (dest !== base && !dest.startsWith(base + path.sep)) {
|
|
295
|
+
still.push({ path: item.path, error: '越界路径' });
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const res = await fetcher(githubRawUrl(repo, branch, item.path), {
|
|
300
|
+
headers: { 'User-Agent': 'qq-bot-roost' },
|
|
301
|
+
signal: AbortSignal.timeout(Math.min(timeoutMs, 30000)),
|
|
302
|
+
});
|
|
303
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
304
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
305
|
+
if (!buf.length) throw new Error('内容是空的');
|
|
306
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
307
|
+
await fs.writeFile(dest, buf);
|
|
308
|
+
log.info(`已从 GitHub 补齐 ${item.path}(Gitee 上拉不到)`);
|
|
309
|
+
onProgress?.(`补齐 ${item.path}`);
|
|
310
|
+
} catch (e) {
|
|
311
|
+
still.push({ path: item.path, error: `Gitee 失败后 GitHub 也失败:${e.message}` });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return still;
|
|
315
|
+
}
|
|
316
|
+
|
|
254
317
|
async function downloadRepoContents({
|
|
255
318
|
repo,
|
|
256
319
|
branch,
|
|
@@ -259,13 +322,53 @@ async function downloadRepoContents({
|
|
|
259
322
|
expectSha256 = null,
|
|
260
323
|
timeoutMs = 120000,
|
|
261
324
|
onRetry,
|
|
325
|
+
onProgress,
|
|
262
326
|
}) {
|
|
263
327
|
const id = currentMirror().id;
|
|
264
328
|
const token = currentMirror().giteeToken;
|
|
265
329
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
330
|
+
/**
|
|
331
|
+
* Gitee 路径统一走这里:允许部分失败,再用 GitHub 把缺口补上。
|
|
332
|
+
* `allowPartial` 打开后 fetchGiteeTree 不再抛错,失败清单交给下面处理。
|
|
333
|
+
*/
|
|
334
|
+
const viaGitee = async () => {
|
|
335
|
+
const r = await fetchGiteeTree({
|
|
336
|
+
repo,
|
|
337
|
+
ref: branch,
|
|
338
|
+
destDir,
|
|
339
|
+
only,
|
|
340
|
+
timeoutMs,
|
|
341
|
+
token,
|
|
342
|
+
allowPartial: true,
|
|
343
|
+
});
|
|
344
|
+
if (!r.failed?.length) return r;
|
|
345
|
+
log.warn(
|
|
346
|
+
`Gitee 上有 ${r.failed.length} 个文件拉不到(常见原因是 451 内容审查),` +
|
|
347
|
+
`改用 GitHub 逐个补齐:${r.failed.slice(0, 3).map((f) => f.path).join('、')}` +
|
|
348
|
+
(r.failed.length > 3 ? ' 等' : ''),
|
|
349
|
+
);
|
|
350
|
+
const still = await backfillFromGitHub({
|
|
351
|
+
repo,
|
|
352
|
+
branch,
|
|
353
|
+
destDir,
|
|
354
|
+
failed: r.failed,
|
|
355
|
+
timeoutMs,
|
|
356
|
+
onProgress,
|
|
357
|
+
});
|
|
358
|
+
if (still.length) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
`${still.length} 个文件在 Gitee 和 GitHub 上都拉不到:\n ` +
|
|
361
|
+
still
|
|
362
|
+
.slice(0, 5)
|
|
363
|
+
.map((f) => `${f.path}(${f.error})`)
|
|
364
|
+
.join('\n ') +
|
|
365
|
+
'\n可以在面板「下载源」里换一个镜像再试。',
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
return { ...r, failed: [], backfilled: r.failed.length };
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
if (id === 'gitee') return viaGitee();
|
|
269
372
|
|
|
270
373
|
try {
|
|
271
374
|
return await fetchAndExtract(branchTarballUrl(repo, branch), destDir, {
|
|
@@ -280,7 +383,7 @@ async function downloadRepoContents({
|
|
|
280
383
|
log.warn(`GitHub 下载失败(${e.message}),改用 Gitee 镜像`);
|
|
281
384
|
// 清掉可能写了一半的残留,再整树重拉
|
|
282
385
|
await fs.rm(destDir, { recursive: true, force: true }).catch(() => {});
|
|
283
|
-
return
|
|
386
|
+
return viaGitee();
|
|
284
387
|
}
|
|
285
388
|
}
|
|
286
389
|
|