@qcplay/cli 1.0.18 → 1.0.19
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/bin/qcplay.js +400 -56
- package/lib/platform-publish.js +977 -79
- package/lib/rich-article-upload.js +116 -0
- package/lib/wechat-article.js +104 -23
- package/lib/wechat-data.js +155 -0
- package/package.json +2 -1
- package/templates/skills/qcplay-publish-article/SKILL.md +41 -12
- package/templates/skills/qcplay-wechat-data/SKILL.md +101 -0
package/bin/qcplay.js
CHANGED
|
@@ -10,12 +10,15 @@ import readline from "readline";
|
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
|
+
analyzeArticleContentSafety,
|
|
14
|
+
buildImportedArticleHtml,
|
|
13
15
|
buildOfficialArticleMarkdown,
|
|
14
16
|
importWechatArticle,
|
|
15
17
|
normalizeArticleColor,
|
|
16
18
|
sanitizeArticleRichHtml,
|
|
17
19
|
uploadMediaToBackend
|
|
18
20
|
} from "../lib/wechat-article.js";
|
|
21
|
+
import { buildRichArticleUploadPayload, parseRichArticleUploadFile } from "../lib/rich-article-upload.js";
|
|
19
22
|
import { importLarkArticle, parseLarkDocumentUrl } from "../lib/lark-article.js";
|
|
20
23
|
import {
|
|
21
24
|
CONTENT_RULES_TEMPLATE,
|
|
@@ -24,6 +27,15 @@ import {
|
|
|
24
27
|
loadContentRules,
|
|
25
28
|
matchingContentRules
|
|
26
29
|
} from "../lib/content-rules.js";
|
|
30
|
+
import {
|
|
31
|
+
WECHAT_GAME_CATALOG,
|
|
32
|
+
buildWechatArticleJson,
|
|
33
|
+
normalizeWechatGameId,
|
|
34
|
+
monthDateRange,
|
|
35
|
+
renderWechatArticleMarkdown,
|
|
36
|
+
splitWechatDateRange,
|
|
37
|
+
todayInShanghai
|
|
38
|
+
} from "../lib/wechat-data.js";
|
|
27
39
|
import {
|
|
28
40
|
availableProjects,
|
|
29
41
|
bilibiliPublishingPlan,
|
|
@@ -37,7 +49,8 @@ import {
|
|
|
37
49
|
runPlatformPublishSequence,
|
|
38
50
|
selectRequestedEntries,
|
|
39
51
|
tapTapPublishingPlan,
|
|
40
|
-
weiboPublishingPlan
|
|
52
|
+
weiboPublishingPlan,
|
|
53
|
+
writePlatformPublishUrls
|
|
41
54
|
} from "../lib/platform-publish.js";
|
|
42
55
|
|
|
43
56
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -173,6 +186,7 @@ const ARTICLE_PREVIEW_BASE_URL = "https://snail.qingcigame.com/official/news-det
|
|
|
173
186
|
const SKILLS_DIR = path.join(AGENTS_DIR, "skills");
|
|
174
187
|
const WEST_ARTICLE_LANGUAGES = new Set(["portuguese", "german", "spanish", "italian", "french", "america_en"]);
|
|
175
188
|
const ARTICLE_PREVIEW_URLS = {
|
|
189
|
+
"33": "https://tideng.qingcigame.com/official/news.html",
|
|
176
190
|
"50": "https://mtzg.qingcijoy.com/news-details.html",
|
|
177
191
|
"69": "https://hjwq.qingcijoy.com/news-details.html",
|
|
178
192
|
"74": "https://sakura.qingcijoy.com/news-details.html",
|
|
@@ -226,10 +240,11 @@ Usage:
|
|
|
226
240
|
qcplay-cli article
|
|
227
241
|
qcplay-cli article init [file]
|
|
228
242
|
qcplay-cli article west-init [file]
|
|
229
|
-
qcplay-cli article import <wechat-or-feishu-url> [file] [--rules-file <json|txt|docx>] [--project <项目>] [--platform <平台>] [--backend <url>] [--local]
|
|
243
|
+
qcplay-cli article import <wechat-or-feishu-url> [file] [--format html|markdown] [--rules-file <json|txt|docx>] [--project <项目>] [--platform <平台>] [--backend <url>] [--local]
|
|
230
244
|
qcplay-cli article publish <file> [--title <标题>] [--cover <图片>] [--project <项目>] [--region <区域>] [--rules-file <json>] [--dry-run]
|
|
231
245
|
qcplay-cli article update [id] [--local] [--backend <url>] [--dry-run]
|
|
232
246
|
qcplay-cli article delete [id] [--local] [--backend <url>] [--dry-run]
|
|
247
|
+
qcplay-cli wechat article --game-id <id> [--game-id <id> ...] (--month <YYYY-MM> | --begin-date <YYYY-MM-DD> --end-date <YYYY-MM-DD>) [--output <file>] [--format markdown|json] [--local] [--backend <url>]
|
|
233
248
|
qcplay-cli publish [file] [--title <标题>] [--cover <图片>] [--weibo-column <专栏>] [--project <项目>] [--region <区域>] [--platform <平台>]
|
|
234
249
|
qcplay-cli publish --project <项目> --list-platforms
|
|
235
250
|
qcplay-cli rules [init|validate|list] [--rules-file <json>]
|
|
@@ -264,22 +279,105 @@ function printArticleHelp() {
|
|
|
264
279
|
qcplay-cli article
|
|
265
280
|
qcplay-cli article init [file]
|
|
266
281
|
qcplay-cli article west-init [file]
|
|
267
|
-
qcplay-cli article import <wechat-or-feishu-url> [file] [--rules-file <json>] [--project <项目>] [--backend <url>] [--local]
|
|
282
|
+
qcplay-cli article import <wechat-or-feishu-url> [file] [--format html|markdown] [--rules-file <json>] [--project <项目>] [--backend <url>] [--local]
|
|
268
283
|
qcplay-cli article publish <file> [--title <标题>] [--cover <图片>] [--project <项目>] [--region <区域>] [--rules-file <json>] [--dry-run]
|
|
269
284
|
qcplay-cli article update [id] [--local] [--backend <url>] [--dry-run]
|
|
270
285
|
qcplay-cli article delete [id] [--local] [--backend <url>] [--dry-run]`);
|
|
271
286
|
}
|
|
272
287
|
|
|
288
|
+
function printWechatHelp() {
|
|
289
|
+
console.log(`Usage:
|
|
290
|
+
qcplay-cli wechat article --game-id <id> [--game-id <id> ...] (--month <YYYY-MM> | --begin-date <YYYY-MM-DD> --end-date <YYYY-MM-DD>) [--output <file>] [--format markdown|json] [--local] [--backend <url>]
|
|
291
|
+
|
|
292
|
+
只读取微信公众号接口返回的 day_total,并按游戏和月份生成表格。
|
|
293
|
+
支持的 game_id: ${Object.entries(WECHAT_GAME_CATALOG).map(([id, name]) => `${id}(${name})`).join("、")}
|
|
294
|
+
当前日期作为结束日期时,必须填写到昨天;超过 30 天的范围会按月份和 30 天窗口自动拆分请求。`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function parseWechatArticleOptions(args) {
|
|
298
|
+
const options = {
|
|
299
|
+
gameIds: [],
|
|
300
|
+
beginDate: "",
|
|
301
|
+
endDate: "",
|
|
302
|
+
month: "",
|
|
303
|
+
output: "",
|
|
304
|
+
format: "markdown",
|
|
305
|
+
backend: undefined,
|
|
306
|
+
local: false
|
|
307
|
+
};
|
|
308
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
309
|
+
const current = args[index];
|
|
310
|
+
if (["--game-id", "--game", "--begin-date", "--start", "--end-date", "--end", "--month", "--output", "--format", "--backend"].includes(current)) {
|
|
311
|
+
const next = args[index + 1];
|
|
312
|
+
if (!next || next.startsWith("--")) throw new Error(`${current} 缺少参数值`);
|
|
313
|
+
if (current === "--game-id" || current === "--game") {
|
|
314
|
+
options.gameIds.push(...next.split(",").map(normalizeWechatGameId));
|
|
315
|
+
} else if (current === "--begin-date" || current === "--start") {
|
|
316
|
+
options.beginDate = next;
|
|
317
|
+
} else if (current === "--end-date" || current === "--end") {
|
|
318
|
+
options.endDate = next;
|
|
319
|
+
} else if (current === "--month") {
|
|
320
|
+
options.month = next;
|
|
321
|
+
} else if (current === "--output") {
|
|
322
|
+
options.output = next;
|
|
323
|
+
} else if (current === "--format") {
|
|
324
|
+
options.format = next.toLowerCase();
|
|
325
|
+
} else {
|
|
326
|
+
options.backend = next;
|
|
327
|
+
}
|
|
328
|
+
index += 1;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (current === "--local") {
|
|
332
|
+
options.local = true;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (current === "--all-games") {
|
|
336
|
+
options.gameIds = Object.keys(WECHAT_GAME_CATALOG);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
throw new Error(`未知参数: ${current}`);
|
|
340
|
+
}
|
|
341
|
+
options.gameIds = [...new Set(options.gameIds)];
|
|
342
|
+
if (options.gameIds.length === 0) throw new Error("请至少指定一个 --game-id");
|
|
343
|
+
if (options.month && (options.beginDate || options.endDate)) throw new Error("--month 不能与 --begin-date/--end-date 同时使用");
|
|
344
|
+
if (options.month) {
|
|
345
|
+
const range = monthDateRange(options.month, todayInShanghai());
|
|
346
|
+
options.beginDate = range.begin.toISOString().slice(0, 10);
|
|
347
|
+
options.endDate = range.end.toISOString().slice(0, 10);
|
|
348
|
+
}
|
|
349
|
+
if (!options.beginDate || !options.endDate) throw new Error("请指定 --month,或同时指定 --begin-date 和 --end-date");
|
|
350
|
+
if (!new Set(["markdown", "json"]).has(options.format)) throw new Error("--format 只能是 markdown 或 json");
|
|
351
|
+
return options;
|
|
352
|
+
}
|
|
353
|
+
|
|
273
354
|
function parseArticleImportOptions(args) {
|
|
274
355
|
const positional = [];
|
|
275
|
-
const options = { rulesFile: "", project: "", platform: "", backend: undefined, local: false };
|
|
356
|
+
const options = { rulesFile: "", project: "", platform: "", format: "html", backend: undefined, local: false };
|
|
276
357
|
for (let index = 0; index < args.length; index += 1) {
|
|
277
358
|
const current = args[index];
|
|
278
|
-
if (
|
|
359
|
+
if (
|
|
360
|
+
current === "--rules-file" ||
|
|
361
|
+
current === "--project" ||
|
|
362
|
+
current === "--platform" ||
|
|
363
|
+
current === "--format" ||
|
|
364
|
+
current === "--backend"
|
|
365
|
+
) {
|
|
279
366
|
const next = args[index + 1];
|
|
280
367
|
if (!next || next.startsWith("--")) throw new Error(`${current} 缺少参数值`);
|
|
281
|
-
|
|
282
|
-
|
|
368
|
+
if (current === "--format") {
|
|
369
|
+
options.format = next.toLowerCase();
|
|
370
|
+
} else {
|
|
371
|
+
const key =
|
|
372
|
+
current === "--rules-file"
|
|
373
|
+
? "rulesFile"
|
|
374
|
+
: current === "--project"
|
|
375
|
+
? "project"
|
|
376
|
+
: current === "--platform"
|
|
377
|
+
? "platform"
|
|
378
|
+
: "backend";
|
|
379
|
+
options[key] = next;
|
|
380
|
+
}
|
|
283
381
|
index += 1;
|
|
284
382
|
continue;
|
|
285
383
|
}
|
|
@@ -291,7 +389,14 @@ function parseArticleImportOptions(args) {
|
|
|
291
389
|
positional.push(current);
|
|
292
390
|
}
|
|
293
391
|
if (positional.length > 2) throw new Error(`未知参数: ${positional.slice(2).join(" ")}`);
|
|
294
|
-
|
|
392
|
+
if (!new Set(["markdown", "html"]).has(options.format)) {
|
|
393
|
+
throw new Error("--format 只能是 markdown 或 html");
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
sourceUrl: positional[0],
|
|
397
|
+
file: positional[1] || (options.format === "markdown" ? "article.md" : "article.html"),
|
|
398
|
+
options
|
|
399
|
+
};
|
|
295
400
|
}
|
|
296
401
|
|
|
297
402
|
function printPublishHelp() {
|
|
@@ -311,8 +416,10 @@ Options:
|
|
|
311
416
|
--config-file <json> 使用本地平台配置(主要用于测试)
|
|
312
417
|
--rules-file <json> 使用指定内容规则;默认 ~/.qcplay/platform-rules.json
|
|
313
418
|
--browser-channel <name> Playwright 浏览器通道,Windows 默认 msedge
|
|
314
|
-
--review-draft, --review
|
|
315
|
-
--direct, --no-review
|
|
419
|
+
--review-draft, --review 官网外平台先准备草稿,预览后由当前 AI 对话确认发布
|
|
420
|
+
--direct, --no-review 需配合 AI 对话确认标记后才可跳过发布前预览
|
|
421
|
+
--publish-draft 仅 TapTap:发布标题匹配的已有草稿,不重新写入内容
|
|
422
|
+
--draft-only, --draft 仅保存或停留在官网外平台草稿,不执行正式发布
|
|
316
423
|
--open-page 只打开真实浏览器发布页,供登录和页面流程测试,不读取或提交文章
|
|
317
424
|
--switch-account 先打开发布页切换账号,确认后继续当前发布流程
|
|
318
425
|
--keep-open 浏览器提交后等待用户确认再关闭
|
|
@@ -378,6 +485,56 @@ function parseRulesOptions(args) {
|
|
|
378
485
|
return options;
|
|
379
486
|
}
|
|
380
487
|
|
|
488
|
+
async function importWechatDataCommand(options) {
|
|
489
|
+
const config = await loadConfig();
|
|
490
|
+
const backendUrl = resolveAuthBackendUrl(options, config);
|
|
491
|
+
const reports = [];
|
|
492
|
+
for (const gameId of options.gameIds) {
|
|
493
|
+
const ranges = splitWechatDateRange(options.beginDate, options.endDate, todayInShanghai());
|
|
494
|
+
const dayTotal = new Map();
|
|
495
|
+
for (const range of ranges) {
|
|
496
|
+
const query = new URLSearchParams({
|
|
497
|
+
game_id: gameId,
|
|
498
|
+
begin_date: range.beginDate,
|
|
499
|
+
end_date: range.endDate
|
|
500
|
+
});
|
|
501
|
+
console.log(chalk.gray(`正在拉取${WECHAT_GAME_CATALOG[gameId]} ${range.beginDate} 至 ${range.endDate}`));
|
|
502
|
+
const response = await authenticatedRequest("GET", backendUrl, `/api/wechat/article?${query}`);
|
|
503
|
+
if (Number(response.code) !== 200) {
|
|
504
|
+
throw new Error(`${WECHAT_GAME_CATALOG[gameId]} ${range.beginDate} 至 ${range.endDate}:${response.message || `接口返回 code=${response.code ?? "未知"}`}`);
|
|
505
|
+
}
|
|
506
|
+
const items = Array.isArray(response.data?.day_total) ? response.data.day_total : [];
|
|
507
|
+
for (const item of items) {
|
|
508
|
+
const key = `${item.ref_date || ""}\u0000${item.msgid || ""}`;
|
|
509
|
+
dayTotal.set(key, item);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
reports.push({
|
|
513
|
+
gameId,
|
|
514
|
+
gameName: WECHAT_GAME_CATALOG[gameId],
|
|
515
|
+
beginDate: options.beginDate,
|
|
516
|
+
endDate: options.endDate,
|
|
517
|
+
ranges,
|
|
518
|
+
dayTotal: [...dayTotal.values()].sort((left, right) => {
|
|
519
|
+
const byDate = String(left.ref_date || "").localeCompare(String(right.ref_date || ""));
|
|
520
|
+
return byDate || String(left.msgid || "").localeCompare(String(right.msgid || ""));
|
|
521
|
+
})
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const output = options.format === "json" ? buildWechatArticleJson(reports) : renderWechatArticleMarkdown(reports);
|
|
526
|
+
if (options.output) {
|
|
527
|
+
const target = path.resolve(options.output);
|
|
528
|
+
if (await pathExists(target)) throw new Error(`输出文件已存在: ${target}`);
|
|
529
|
+
await ensureDir(path.dirname(target));
|
|
530
|
+
await fs.promises.writeFile(target, output, { encoding: "utf8", mode: 0o600 });
|
|
531
|
+
console.log(chalk.green(`微信公众号数据已生成: ${target}`));
|
|
532
|
+
console.log(`文章数据: ${reports.reduce((sum, report) => sum + report.dayTotal.length, 0)} 篇`);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
process.stdout.write(output);
|
|
536
|
+
}
|
|
537
|
+
|
|
381
538
|
async function contentRulesCommand(action, args = []) {
|
|
382
539
|
const options = parseRulesOptions(args);
|
|
383
540
|
const file = path.resolve(options.rulesFile || CONTENT_RULES_FILE);
|
|
@@ -430,7 +587,7 @@ function printFeatures() {
|
|
|
430
587
|
console.log(" qcplay-cli auth permissions --key www-article-list.store");
|
|
431
588
|
console.log("");
|
|
432
589
|
console.log(chalk.cyan("3. 发布官网文章"));
|
|
433
|
-
console.log(" qcplay-cli article import <微信文章或飞书文档链接> article.
|
|
590
|
+
console.log(" qcplay-cli article import <微信文章或飞书文档链接> article.html");
|
|
434
591
|
console.log(" qcplay-cli www-article-list.store article.md");
|
|
435
592
|
console.log(" qcplay-cli article update [id]");
|
|
436
593
|
console.log(" qcplay-cli article delete [id]");
|
|
@@ -442,6 +599,9 @@ function printFeatures() {
|
|
|
442
599
|
console.log(chalk.cyan("5. 查看本地目录"));
|
|
443
600
|
console.log(" qcplay-cli where");
|
|
444
601
|
console.log("");
|
|
602
|
+
console.log(chalk.cyan("6. 拉取微信公众号数据"));
|
|
603
|
+
console.log(" qcplay-cli wechat article --game-id 39 --month 2026-08 --output wechat-2026-08.md");
|
|
604
|
+
console.log("");
|
|
445
605
|
}
|
|
446
606
|
|
|
447
607
|
function printWhere() {
|
|
@@ -814,6 +974,9 @@ function parseDistributionOptions(args) {
|
|
|
814
974
|
browserChannel: "",
|
|
815
975
|
reviewDraft: false,
|
|
816
976
|
directPublish: false,
|
|
977
|
+
confirmed: false,
|
|
978
|
+
draftOnly: false,
|
|
979
|
+
publishDraft: false,
|
|
817
980
|
keepOpen: false,
|
|
818
981
|
openPage: false,
|
|
819
982
|
switchAccount: false,
|
|
@@ -857,6 +1020,18 @@ function parseDistributionOptions(args) {
|
|
|
857
1020
|
options.directPublish = true;
|
|
858
1021
|
continue;
|
|
859
1022
|
}
|
|
1023
|
+
if (current === "--confirmed") {
|
|
1024
|
+
options.confirmed = true;
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
if (current === "--draft-only" || current === "--draft") {
|
|
1028
|
+
options.draftOnly = true;
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
if (current === "--publish-draft") {
|
|
1032
|
+
options.publishDraft = true;
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
860
1035
|
|
|
861
1036
|
const valueFlags = new Map([
|
|
862
1037
|
["--backend", "backend"],
|
|
@@ -921,11 +1096,11 @@ function applyPublishOverrides(parsedArticle, options) {
|
|
|
921
1096
|
...parsedArticle.meta,
|
|
922
1097
|
...(cover
|
|
923
1098
|
? {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1099
|
+
weibo_cover: cover,
|
|
1100
|
+
taptap_cover: cover,
|
|
1101
|
+
bilibili_cover: cover,
|
|
1102
|
+
xiaohongshu_images: cover
|
|
1103
|
+
}
|
|
929
1104
|
: {}),
|
|
930
1105
|
...(weiboColumn ? { weibo_column: weiboColumn } : {})
|
|
931
1106
|
};
|
|
@@ -1003,7 +1178,9 @@ function openBrowser(targetUrl) {
|
|
|
1003
1178
|
|
|
1004
1179
|
if (process.platform === "win32") {
|
|
1005
1180
|
command = "cmd";
|
|
1006
|
-
|
|
1181
|
+
// 关键修复:将 URL 中的 & 替换为 ^&
|
|
1182
|
+
const safeUrl = targetUrl.replace(/&/g, '^&');
|
|
1183
|
+
args = ["/c", "start", "", safeUrl];
|
|
1007
1184
|
} else if (process.platform === "darwin") {
|
|
1008
1185
|
command = "open";
|
|
1009
1186
|
args = [targetUrl];
|
|
@@ -1344,7 +1521,7 @@ function backendOriginsMatch(left, right) {
|
|
|
1344
1521
|
}
|
|
1345
1522
|
}
|
|
1346
1523
|
|
|
1347
|
-
async function saveRecentArticleState(articleId, status, backendUrl, previewUrl = "", target = "domestic") {
|
|
1524
|
+
async function saveRecentArticleState(articleId, status, backendUrl, previewUrl = "", target = "domestic", metadata = {}) {
|
|
1348
1525
|
const authState = await loadAuthState();
|
|
1349
1526
|
await writeJson(ARTICLE_STATE_FILE, {
|
|
1350
1527
|
article_id: articleId,
|
|
@@ -1354,13 +1531,15 @@ async function saveRecentArticleState(articleId, status, backendUrl, previewUrl
|
|
|
1354
1531
|
backend_url: backendUrl,
|
|
1355
1532
|
administrator_id: Number(authState.id || 0),
|
|
1356
1533
|
account: normalizeText(authState.account),
|
|
1534
|
+
game_id: normalizeText(metadata.game_id),
|
|
1535
|
+
cate_id: normalizeText(metadata.cate_id),
|
|
1357
1536
|
updated_at: Date.now()
|
|
1358
1537
|
});
|
|
1359
1538
|
}
|
|
1360
1539
|
|
|
1361
|
-
async function trySaveRecentArticleState(articleId, status, backendUrl, previewUrl = "", target = "domestic") {
|
|
1540
|
+
async function trySaveRecentArticleState(articleId, status, backendUrl, previewUrl = "", target = "domestic", metadata = {}) {
|
|
1362
1541
|
try {
|
|
1363
|
-
await saveRecentArticleState(articleId, status, backendUrl, previewUrl, target);
|
|
1542
|
+
await saveRecentArticleState(articleId, status, backendUrl, previewUrl, target, metadata);
|
|
1364
1543
|
return "";
|
|
1365
1544
|
} catch (error) {
|
|
1366
1545
|
return normalizeText(error?.message) || String(error);
|
|
@@ -1701,14 +1880,14 @@ function applyInlineMarkdown(text) {
|
|
|
1701
1880
|
output = output.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, url) => {
|
|
1702
1881
|
return storeToken(
|
|
1703
1882
|
`<img src="${escapeHtml(url.trim())}" alt="${escapeHtml(alt.trim())}" ` +
|
|
1704
|
-
|
|
1883
|
+
'style="display:block;box-sizing:border-box;width:auto;max-width:100%;height:auto;object-fit:contain;margin:24px auto;" />'
|
|
1705
1884
|
);
|
|
1706
1885
|
});
|
|
1707
1886
|
|
|
1708
1887
|
output = output.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, url) => {
|
|
1709
1888
|
return storeToken(
|
|
1710
1889
|
`<a href="${escapeHtml(url.trim())}" target="_blank" rel="noreferrer" ` +
|
|
1711
|
-
|
|
1890
|
+
`style="color:#576b95;text-decoration:none;overflow-wrap:anywhere;word-break:break-all;">${escapeHtml(label.trim())}</a>`
|
|
1712
1891
|
);
|
|
1713
1892
|
});
|
|
1714
1893
|
|
|
@@ -1754,7 +1933,7 @@ function markdownToHtml(markdown) {
|
|
|
1754
1933
|
|
|
1755
1934
|
html.push(
|
|
1756
1935
|
`<p style="margin:0 0 20px;font-size:16px;line-height:1.9;color:#3f3f3f;text-align:justify;overflow-wrap:anywhere;word-break:break-word;">` +
|
|
1757
|
-
|
|
1936
|
+
`${paragraph.map(line => applyInlineMarkdown(line)).join("<br />")}</p>`
|
|
1758
1937
|
);
|
|
1759
1938
|
paragraph = [];
|
|
1760
1939
|
}
|
|
@@ -1766,9 +1945,9 @@ function markdownToHtml(markdown) {
|
|
|
1766
1945
|
|
|
1767
1946
|
html.push(
|
|
1768
1947
|
'<blockquote style="margin:24px 0;padding:14px 18px;border-left:4px solid #17b9c2;background:#f5f7f8;color:#57606a;">' +
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1948
|
+
`<p style="margin:0;font-size:15px;line-height:1.8;">${quote
|
|
1949
|
+
.map(line => applyInlineMarkdown(line))
|
|
1950
|
+
.join("<br />")}</p></blockquote>`
|
|
1772
1951
|
);
|
|
1773
1952
|
quote = [];
|
|
1774
1953
|
}
|
|
@@ -1782,7 +1961,7 @@ function markdownToHtml(markdown) {
|
|
|
1782
1961
|
|
|
1783
1962
|
html.push(
|
|
1784
1963
|
`<${listType} style="margin:0 0 20px;padding-left:1.6em;font-size:16px;line-height:1.9;color:#3f3f3f;">` +
|
|
1785
|
-
|
|
1964
|
+
`${listItems.map(item => `<li style="margin:6px 0;">${applyInlineMarkdown(item)}</li>`).join("")}</${listType}>`
|
|
1786
1965
|
);
|
|
1787
1966
|
listType = "";
|
|
1788
1967
|
listItems = [];
|
|
@@ -1795,7 +1974,7 @@ function markdownToHtml(markdown) {
|
|
|
1795
1974
|
|
|
1796
1975
|
html.push(
|
|
1797
1976
|
'<pre style="margin:24px 0;padding:16px;overflow:auto;border-radius:4px;background:#f5f7f8;">' +
|
|
1798
|
-
|
|
1977
|
+
`<code>${escapeHtml(codeLines.join("\n"))}</code></pre>`
|
|
1799
1978
|
);
|
|
1800
1979
|
inCodeBlock = false;
|
|
1801
1980
|
codeLines = [];
|
|
@@ -1834,18 +2013,18 @@ function markdownToHtml(markdown) {
|
|
|
1834
2013
|
"font-size:15px;line-height:1.7;overflow-wrap:anywhere;word-break:break-word;";
|
|
1835
2014
|
html.push(
|
|
1836
2015
|
'<div style="width:100%;margin:24px 0;overflow-x:auto;-webkit-overflow-scrolling:touch;">' +
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2016
|
+
'<table style="width:100%;min-width:560px;border-collapse:collapse;table-layout:auto;color:#3f3f3f;">' +
|
|
2017
|
+
`<thead><tr>${header
|
|
2018
|
+
.map(cell => `<th style="${cellStyle}background:#f5f7f8;font-weight:600;">${applyInlineMarkdown(cell)}</th>`)
|
|
2019
|
+
.join("")}</tr></thead>` +
|
|
2020
|
+
`<tbody>${body
|
|
2021
|
+
.map(
|
|
2022
|
+
row =>
|
|
2023
|
+
`<tr>${row
|
|
2024
|
+
.map(cell => `<td style="${cellStyle}">${applyInlineMarkdown(cell)}</td>`)
|
|
2025
|
+
.join("")}</tr>`
|
|
2026
|
+
)
|
|
2027
|
+
.join("")}</tbody></table></div>`
|
|
1849
2028
|
);
|
|
1850
2029
|
tableLines = [];
|
|
1851
2030
|
}
|
|
@@ -1908,7 +2087,7 @@ function markdownToHtml(markdown) {
|
|
|
1908
2087
|
const headingSize = level === 1 ? 24 : level === 2 ? 20 : 18;
|
|
1909
2088
|
html.push(
|
|
1910
2089
|
`<h${level} style="margin:32px 0 16px;font-size:${headingSize}px;line-height:1.5;font-weight:700;color:#24292f;overflow-wrap:anywhere;word-break:break-word;">` +
|
|
1911
|
-
|
|
2090
|
+
`${applyInlineMarkdown(headingMatch[2].trim())}</h${level}>`
|
|
1912
2091
|
);
|
|
1913
2092
|
continue;
|
|
1914
2093
|
}
|
|
@@ -2074,6 +2253,23 @@ async function parseArticleFile(file) {
|
|
|
2074
2253
|
}
|
|
2075
2254
|
|
|
2076
2255
|
const raw = await fs.promises.readFile(articleFile, "utf8");
|
|
2256
|
+
if (path.extname(articleFile).toLowerCase() === ".html") {
|
|
2257
|
+
const { meta, richHtml } = parseRichArticleUploadFile(raw);
|
|
2258
|
+
return {
|
|
2259
|
+
articleFile,
|
|
2260
|
+
payload: buildRichArticleUploadPayload(meta, richHtml, {
|
|
2261
|
+
normalizeText,
|
|
2262
|
+
normalizeMappedValue,
|
|
2263
|
+
normalizeGameAndCategory,
|
|
2264
|
+
normalizeBoolLike,
|
|
2265
|
+
statusMap: STATUS_MAP,
|
|
2266
|
+
areaMap: AREA_MAP,
|
|
2267
|
+
westLanguages: WEST_ARTICLE_LANGUAGES
|
|
2268
|
+
}),
|
|
2269
|
+
meta,
|
|
2270
|
+
markdown: ""
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2077
2273
|
const parsed = parseFrontMatter(raw);
|
|
2078
2274
|
return {
|
|
2079
2275
|
articleFile,
|
|
@@ -2106,7 +2302,7 @@ async function initArticleTemplate(file = "article.md", template = "article.md")
|
|
|
2106
2302
|
console.log("");
|
|
2107
2303
|
}
|
|
2108
2304
|
|
|
2109
|
-
async function importArticleCommand(sourceUrl, file = "article.
|
|
2305
|
+
async function importArticleCommand(sourceUrl, file = "article.html", options = {}) {
|
|
2110
2306
|
if (!sourceUrl) {
|
|
2111
2307
|
throw new Error("缺少文章来源链接,请提供微信公众号文章或飞书 Docx/Wiki 文档链接");
|
|
2112
2308
|
}
|
|
@@ -2151,14 +2347,22 @@ async function importArticleCommand(sourceUrl, file = "article.md", options = {}
|
|
|
2151
2347
|
}
|
|
2152
2348
|
});
|
|
2153
2349
|
}
|
|
2154
|
-
const
|
|
2350
|
+
const projectMetadata = importProjectMetadata(options.project);
|
|
2351
|
+
if (projectMetadata) {
|
|
2352
|
+
article = { ...article, ...projectMetadata };
|
|
2353
|
+
}
|
|
2354
|
+
const output = options.format === "html" ? buildImportedArticleHtml(article) : buildOfficialArticleMarkdown(article);
|
|
2155
2355
|
await ensureDir(path.dirname(targetFile));
|
|
2156
2356
|
await fs.promises.writeFile(targetFile, output, { encoding: "utf8", flag: "wx" });
|
|
2157
2357
|
|
|
2158
2358
|
console.log("");
|
|
2159
|
-
console.log(chalk.green(`${sourceLabel}
|
|
2359
|
+
console.log(chalk.green(`${sourceLabel}已转换为${options.format === "html" ? "HTML 富文本文章" : "官网文章草稿"}:`));
|
|
2160
2360
|
console.log(targetFile);
|
|
2161
2361
|
console.log(chalk.gray(`标题: ${article.title}`));
|
|
2362
|
+
const contentSafety = article.contentSafety || analyzeArticleContentSafety(article);
|
|
2363
|
+
for (const warning of contentSafety.warnings) {
|
|
2364
|
+
console.log(chalk.yellow(`内容安全提示: ${warning}`));
|
|
2365
|
+
}
|
|
2162
2366
|
if (sourceLabel === "飞书文档") {
|
|
2163
2367
|
console.log(chalk.gray(`已保留图片: ${article.imageCount} 张,表格: ${article.tableCount} 个`));
|
|
2164
2368
|
console.log(chalk.gray("已保留标题、列表、引用、颜色、高亮、对齐、列宽及合并单元格"));
|
|
@@ -2188,7 +2392,13 @@ async function importArticleCommand(sourceUrl, file = "article.md", options = {}
|
|
|
2188
2392
|
);
|
|
2189
2393
|
}
|
|
2190
2394
|
console.log("");
|
|
2191
|
-
console.log(
|
|
2395
|
+
console.log(
|
|
2396
|
+
options.format === "html"
|
|
2397
|
+
? "可直接用于官网或各分发平台,平台规则会在发布时按原有流程处理:"
|
|
2398
|
+
: sourceLabel === "飞书文档"
|
|
2399
|
+
? "请补充 thumbnail、cate_id 等业务字段,确认内容后执行:"
|
|
2400
|
+
: "请补充 cate_id 等业务字段,确认内容后执行:"
|
|
2401
|
+
);
|
|
2192
2402
|
console.log("");
|
|
2193
2403
|
console.log(` qcplay-cli www-article-list.store ${file}`);
|
|
2194
2404
|
console.log("");
|
|
@@ -2251,9 +2461,9 @@ ${chalk.cyan("发布命令:")}
|
|
|
2251
2461
|
|
|
2252
2462
|
qcplay-cli www-article-list.store article.md
|
|
2253
2463
|
|
|
2254
|
-
${chalk.cyan("
|
|
2464
|
+
${chalk.cyan("从微信推文拉取 HTML 富文本:")}
|
|
2255
2465
|
|
|
2256
|
-
qcplay-cli article import "https://mp.weixin.qq.com/s/..." article.
|
|
2466
|
+
qcplay-cli article import "https://mp.weixin.qq.com/s/..." article.html
|
|
2257
2467
|
|
|
2258
2468
|
${chalk.cyan("项目默认分类:")}
|
|
2259
2469
|
|
|
@@ -2277,6 +2487,21 @@ function inferDistributionProject(payload, explicitProject, meta = {}) {
|
|
|
2277
2487
|
return "";
|
|
2278
2488
|
}
|
|
2279
2489
|
|
|
2490
|
+
function importProjectMetadata(project) {
|
|
2491
|
+
const normalizedProject = normalizeText(project);
|
|
2492
|
+
if (!normalizedProject) return null;
|
|
2493
|
+
const gameId = normalizeMappedValue(normalizedProject, GAME_MAP);
|
|
2494
|
+
const game = gameCatalogEntry(gameId);
|
|
2495
|
+
if (!game) {
|
|
2496
|
+
throw new Error(`未知项目: ${normalizedProject}`);
|
|
2497
|
+
}
|
|
2498
|
+
return {
|
|
2499
|
+
gameId,
|
|
2500
|
+
categoryId: game.defaultCategory,
|
|
2501
|
+
distributionProject: normalizedProject
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2280
2505
|
function printPlatformEntries(entries) {
|
|
2281
2506
|
console.log("");
|
|
2282
2507
|
console.log(chalk.cyan("可用发布平台:"));
|
|
@@ -2324,9 +2549,25 @@ async function publishPlatformsCommand(file, options) {
|
|
|
2324
2549
|
throw new Error("缺少文章文件,例如:qcplay-cli publish article.md --platform TapTap");
|
|
2325
2550
|
}
|
|
2326
2551
|
|
|
2327
|
-
|
|
2552
|
+
let selectedEntries = options.platforms.length
|
|
2328
2553
|
? selectRequestedEntries(candidates, options.platforms)
|
|
2329
2554
|
: await promptForPlatformEntries(candidates);
|
|
2555
|
+
if (options.draftOnly) {
|
|
2556
|
+
selectedEntries = selectedEntries.filter(entry => entry.platformKey !== "website");
|
|
2557
|
+
if (selectedEntries.length === 0) {
|
|
2558
|
+
throw new Error("--draft-only 仅适用于官网之外的游戏平台");
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (options.draftOnly && options.publishDraft) {
|
|
2562
|
+
throw new Error("--draft-only 与 --publish-draft 不能同时使用");
|
|
2563
|
+
}
|
|
2564
|
+
const needsActionSelection =
|
|
2565
|
+
!options.dryRun &&
|
|
2566
|
+
!options.openPage &&
|
|
2567
|
+
!options.switchAccount &&
|
|
2568
|
+
!options.draftOnly &&
|
|
2569
|
+
!(options.publishDraft && options.confirmed) &&
|
|
2570
|
+
!(options.directPublish && options.confirmed);
|
|
2330
2571
|
if (options.openPage) {
|
|
2331
2572
|
if (selectedEntries.some(entry => entry.publisher !== "browser")) {
|
|
2332
2573
|
throw new Error("--open-page 仅支持有真实浏览器发布页的平台,请使用 --platform 指定浏览器平台");
|
|
@@ -2376,8 +2617,11 @@ async function publishPlatformsCommand(file, options) {
|
|
|
2376
2617
|
const matchedRules = matchingContentRules(contentRules, planningArticle, entry).map(rule => rule.name);
|
|
2377
2618
|
return {
|
|
2378
2619
|
...publicEntrySummary(entry),
|
|
2620
|
+
...(options.draftOnly ? { mode: "draft-only" } : {}),
|
|
2379
2621
|
...(matchedRules.length > 0 ? { contentRules: matchedRules } : {}),
|
|
2380
|
-
...(entry.platformKey === "taptap"
|
|
2622
|
+
...(entry.platformKey === "taptap"
|
|
2623
|
+
? { taptap: { ...tapTapPublishingPlan(planningArticle, entry), ...(options.draftOnly ? { draft: true } : {}) } }
|
|
2624
|
+
: {}),
|
|
2381
2625
|
...(entry.platformKey === "bilibili" ? { bilibili: bilibiliPublishingPlan(planningArticle, entry) } : {}),
|
|
2382
2626
|
...(entry.platformKey === "weibo" ? { weibo: weiboPublishingPlan(planningArticle, entry) } : {})
|
|
2383
2627
|
};
|
|
@@ -2396,7 +2640,17 @@ async function publishPlatformsCommand(file, options) {
|
|
|
2396
2640
|
meta: parsedArticle.meta,
|
|
2397
2641
|
platformInputs
|
|
2398
2642
|
};
|
|
2399
|
-
const
|
|
2643
|
+
const contentSafety = analyzeArticleContentSafety(article);
|
|
2644
|
+
for (const warning of contentSafety.warnings) {
|
|
2645
|
+
console.log(chalk.yellow(`内容安全提示: ${warning}`));
|
|
2646
|
+
}
|
|
2647
|
+
const browserEntries = selected.filter(entry => entry.publisher === "browser");
|
|
2648
|
+
// Each browser platform needs its own explicit AI-conversation authorisation.
|
|
2649
|
+
// Preparing every selected editor before asking keeps the user waiting behind
|
|
2650
|
+
// long image uploads on later platforms and leaves no safe action boundary.
|
|
2651
|
+
const executionEntries = needsActionSelection ? browserEntries.slice(0, 1) : selected;
|
|
2652
|
+
const pendingApprovalEntries = needsActionSelection ? browserEntries.slice(1) : [];
|
|
2653
|
+
const { successes, failures, skipped } = await runPlatformPublishSequence(executionEntries, async entry => {
|
|
2400
2654
|
console.log("");
|
|
2401
2655
|
console.log(chalk.cyan(`正在发布到 ${entry.platform}(${entry.region})...`));
|
|
2402
2656
|
let result;
|
|
@@ -2405,13 +2659,34 @@ async function publishPlatformsCommand(file, options) {
|
|
|
2405
2659
|
} else {
|
|
2406
2660
|
result = await publishPlatformEntry(entry, article, {
|
|
2407
2661
|
browserChannel: options.browserChannel,
|
|
2408
|
-
reviewDraft: options.reviewDraft,
|
|
2409
|
-
|
|
2662
|
+
reviewDraft: needsActionSelection || options.reviewDraft,
|
|
2663
|
+
// A review is always approved in the current AI conversation. Never
|
|
2664
|
+
// fall back to the legacy terminal confirmation path.
|
|
2665
|
+
awaitAIAction: needsActionSelection || options.reviewDraft,
|
|
2666
|
+
// The initial AI-authorisation preview prepares the complete native
|
|
2667
|
+
// editor draft, including body media. It still never clicks save or
|
|
2668
|
+
// publish; only the later explicit action may submit it. A platform
|
|
2669
|
+
// must not reach the action selection while any image is still
|
|
2670
|
+
// uploading, including editors that expose their fields after upload.
|
|
2671
|
+
previewTextOnly: false,
|
|
2672
|
+
directPublish: options.directPublish && options.confirmed,
|
|
2673
|
+
draftOnly: options.draftOnly,
|
|
2674
|
+
publishDraft: options.publishDraft,
|
|
2410
2675
|
keepOpen: options.keepOpen,
|
|
2411
2676
|
onProgress: message => console.log(chalk.gray(` ${message}`))
|
|
2412
2677
|
});
|
|
2413
|
-
if (
|
|
2414
|
-
console.log(chalk.green(
|
|
2678
|
+
if (result?.status === "draft") {
|
|
2679
|
+
console.log(chalk.green(`${entry.platform} 草稿已保存,浏览器保持打开`));
|
|
2680
|
+
if (result.approvalRequired) {
|
|
2681
|
+
console.log(
|
|
2682
|
+
JSON.stringify({
|
|
2683
|
+
event: "qcplay.draft-approval-required",
|
|
2684
|
+
platform: entry.platform,
|
|
2685
|
+
title: article.title,
|
|
2686
|
+
actions: result.approvalActions || ["publish", "cancel"]
|
|
2687
|
+
})
|
|
2688
|
+
);
|
|
2689
|
+
}
|
|
2415
2690
|
return result;
|
|
2416
2691
|
}
|
|
2417
2692
|
if (entry.platformKey === "taptap" && result?.status === "scheduled") {
|
|
@@ -2425,6 +2700,42 @@ async function publishPlatformsCommand(file, options) {
|
|
|
2425
2700
|
failures.forEach(({ entry, error }) => {
|
|
2426
2701
|
console.error(chalk.red(`${entry.platform} 发布失败: ${error.message || error}`));
|
|
2427
2702
|
});
|
|
2703
|
+
|
|
2704
|
+
if (needsActionSelection) {
|
|
2705
|
+
if (failures.length > 0) {
|
|
2706
|
+
throw new Error("待授权预览准备失败,请根据上面的错误处理后重试失败平台");
|
|
2707
|
+
}
|
|
2708
|
+
const pages = successes
|
|
2709
|
+
.map(({ entry, result }) => ({ platform: entry.platform, url: result?.url || "" }))
|
|
2710
|
+
.filter(item => item.url);
|
|
2711
|
+
const previewResults = successes.map(({ result }) => result || {});
|
|
2712
|
+
const imagesPending = previewResults.reduce((sum, result) => sum + Number(result.imagesPending || 0), 0);
|
|
2713
|
+
const requiresMediaBeforeText = previewResults.some(result => result.requiresMediaBeforeText);
|
|
2714
|
+
const contentPrepared = previewResults.every(result => result.contentPrepared !== false) && imagesPending === 0;
|
|
2715
|
+
if (!contentPrepared || requiresMediaBeforeText) {
|
|
2716
|
+
throw new Error("编辑器尚未完成正文或图片回读校验,暂不提供发布授权选项");
|
|
2717
|
+
}
|
|
2718
|
+
console.log(
|
|
2719
|
+
JSON.stringify({
|
|
2720
|
+
event: "qcplay.publish-action-required",
|
|
2721
|
+
title: previewResults.find(result => normalizeText(result.title))?.title || article.title,
|
|
2722
|
+
platforms: executionEntries.map(entry => entry.platform),
|
|
2723
|
+
...(pendingApprovalEntries.length > 0
|
|
2724
|
+
? { pendingPlatforms: pendingApprovalEntries.map(entry => entry.platform) }
|
|
2725
|
+
: {}),
|
|
2726
|
+
pages,
|
|
2727
|
+
contentPrepared,
|
|
2728
|
+
imagesPending,
|
|
2729
|
+
actions: ["publish", "save_draft", "cancel"]
|
|
2730
|
+
})
|
|
2731
|
+
);
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
const urlsFile = await writePlatformPublishUrls(file, successes);
|
|
2736
|
+
if (urlsFile) {
|
|
2737
|
+
console.log(chalk.green(`平台地址已写入: ${urlsFile}`));
|
|
2738
|
+
}
|
|
2428
2739
|
if (skipped.length > 0) {
|
|
2429
2740
|
console.error(chalk.yellow("已停止后续平台发布,请先处理当前浏览器平台的登录或页面问题。"));
|
|
2430
2741
|
}
|
|
@@ -2561,11 +2872,20 @@ async function updateArticleStatusCommand(articleIdValue, targetStatus, options,
|
|
|
2561
2872
|
|
|
2562
2873
|
const response = await authenticatedRequest("PATCH", backendUrl, articleApiPath(target, "status"), payload);
|
|
2563
2874
|
const responseStatus = normalizeText(response.data?.status) || targetStatus;
|
|
2875
|
+
const recentState = await readJson(ARTICLE_STATE_FILE);
|
|
2876
|
+
const recentStateMatches =
|
|
2877
|
+
String(recentState.article_id || "") === String(articleId) &&
|
|
2878
|
+
(recentState.target || "domestic") === target;
|
|
2879
|
+
const previewPayload = recentStateMatches
|
|
2880
|
+
? { game_id: recentState.game_id, cate_id: recentState.cate_id }
|
|
2881
|
+
: {};
|
|
2564
2882
|
const previewUrl =
|
|
2565
2883
|
responseStatus === "1"
|
|
2566
|
-
? normalizeText(response.data?.preview_url) ||
|
|
2884
|
+
? normalizeText(response.data?.preview_url) ||
|
|
2885
|
+
(recentStateMatches ? normalizeText(recentState.preview_url) : "") ||
|
|
2886
|
+
(target === "domestic" ? articlePreviewUrl(articleId, previewPayload) : "")
|
|
2567
2887
|
: "";
|
|
2568
|
-
const stateWarning = await trySaveRecentArticleState(articleId, responseStatus, backendUrl, previewUrl, target);
|
|
2888
|
+
const stateWarning = await trySaveRecentArticleState(articleId, responseStatus, backendUrl, previewUrl, target, previewPayload);
|
|
2569
2889
|
|
|
2570
2890
|
console.log(chalk.green(response.message || (responseStatus === "2" ? "文章已删除" : "文章已上线")));
|
|
2571
2891
|
console.log(`文章 ID: ${articleId}`);
|
|
@@ -2654,6 +2974,26 @@ async function main() {
|
|
|
2654
2974
|
return;
|
|
2655
2975
|
}
|
|
2656
2976
|
|
|
2977
|
+
if (command === "wechat") {
|
|
2978
|
+
if (!subcommand || subcommand === "-h" || subcommand === "--help") {
|
|
2979
|
+
printWechatHelp();
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
if (subcommand !== "article") {
|
|
2983
|
+
await runWithErrorBanner("微信公众号数据失败", async () => {
|
|
2984
|
+
throw new Error(`未知 wechat 命令: ${subcommand}`);
|
|
2985
|
+
});
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
if (rest.includes("-h") || rest.includes("--help")) {
|
|
2989
|
+
printWechatHelp();
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
const parsed = parseWechatArticleOptions(rest);
|
|
2993
|
+
await runWithErrorBanner("微信公众号数据失败", () => importWechatDataCommand(parsed));
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2657
2997
|
if (command === "article") {
|
|
2658
2998
|
if (!subcommand || subcommand === "-h" || subcommand === "--help") {
|
|
2659
2999
|
if (!subcommand) {
|
|
@@ -2677,6 +3017,10 @@ async function main() {
|
|
|
2677
3017
|
}
|
|
2678
3018
|
|
|
2679
3019
|
if (subcommand === "import") {
|
|
3020
|
+
if (rest.length === 1 && ["--help", "-h"].includes(rest[0])) {
|
|
3021
|
+
printArticleHelp();
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
2680
3024
|
const parsed = parseArticleImportOptions(rest);
|
|
2681
3025
|
await runWithErrorBanner("转换失败", () =>
|
|
2682
3026
|
importArticleCommand(parsed.sourceUrl, parsed.file, parsed.options)
|