@qcplay/cli 1.0.13 → 1.0.14
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 +116 -1
- package/bin/qcplay.js +326 -23
- package/lib/lark-article.js +294 -0
- package/lib/platform-publish.js +4473 -0
- package/lib/wechat-article.js +15 -4
- package/package.json +5 -2
- package/templates/skills/qcplay-publish-article/SKILL.md +110 -2
- package/templates/skills/qcplay-publish-article/article/article.md +15 -0
package/lib/wechat-article.js
CHANGED
|
@@ -7,7 +7,7 @@ import zlib from "zlib";
|
|
|
7
7
|
import * as cheerio from "cheerio";
|
|
8
8
|
|
|
9
9
|
const WECHAT_HOST = "mp.weixin.qq.com";
|
|
10
|
-
const IMAGE_UPLOAD_URL = "
|
|
10
|
+
const IMAGE_UPLOAD_URL = "https://api.qingcigame.com/novel/time/literature/avatar";
|
|
11
11
|
const MAX_REDIRECTS = 5;
|
|
12
12
|
const MAX_HTML_BYTES = 8 * 1024 * 1024;
|
|
13
13
|
const MAX_IMAGE_BYTES = 25 * 1024 * 1024;
|
|
@@ -34,6 +34,8 @@ const SAFE_RICH_TAGS = new Set([
|
|
|
34
34
|
"b",
|
|
35
35
|
"blockquote",
|
|
36
36
|
"br",
|
|
37
|
+
"col",
|
|
38
|
+
"colgroup",
|
|
37
39
|
"del",
|
|
38
40
|
"div",
|
|
39
41
|
"em",
|
|
@@ -303,7 +305,10 @@ export function parseWechatUrl(value) {
|
|
|
303
305
|
function assertWechatImageUrl(value, pageUrl) {
|
|
304
306
|
const url = new URL(value, pageUrl);
|
|
305
307
|
const hostname = url.hostname.toLowerCase();
|
|
306
|
-
const allowedHost =
|
|
308
|
+
const allowedHost =
|
|
309
|
+
hostname === "mmbiz.qpic.cn" ||
|
|
310
|
+
hostname.endsWith(".mmbiz.qpic.cn") ||
|
|
311
|
+
(hostname === "res.wx.qq.com" && url.pathname.startsWith("/t/wx_fed/we-emoji/res/assets/"));
|
|
307
312
|
if (url.protocol !== "https:" || !allowedHost) {
|
|
308
313
|
throw new Error(`文章包含不受支持的图片地址: ${url.origin}`);
|
|
309
314
|
}
|
|
@@ -597,9 +602,14 @@ function isStandaloneImage(element) {
|
|
|
597
602
|
});
|
|
598
603
|
}
|
|
599
604
|
|
|
600
|
-
export function sanitizeArticleRichHtml(value) {
|
|
605
|
+
export function sanitizeArticleRichHtml(value, options = {}) {
|
|
601
606
|
const initial = cheerio.load(String(value || ""), null, false);
|
|
602
607
|
const existingRoot = initial('[data-qcplay-rich-root="1"]').first();
|
|
608
|
+
const requestedMaxWidth = Number.parseInt(
|
|
609
|
+
existingRoot.attr("data-qcplay-rich-max-width") || options.maxWidth || "677",
|
|
610
|
+
10
|
|
611
|
+
);
|
|
612
|
+
const maxWidth = Math.max(320, Math.min(Number.isFinite(requestedMaxWidth) ? requestedMaxWidth : 677, 1200));
|
|
603
613
|
const source = existingRoot.length ? existingRoot.html() || "" : initial.root().html() || "";
|
|
604
614
|
const $ = cheerio.load(source, null, false);
|
|
605
615
|
|
|
@@ -731,7 +741,8 @@ export function sanitizeArticleRichHtml(value) {
|
|
|
731
741
|
return "";
|
|
732
742
|
}
|
|
733
743
|
return (
|
|
734
|
-
|
|
744
|
+
`<section data-qcplay-rich-root="1" data-qcplay-rich-max-width="${maxWidth}" ` +
|
|
745
|
+
`style="box-sizing:border-box;width:100%;max-width:${maxWidth}px;min-width:0;` +
|
|
735
746
|
'margin:0 auto;overflow:hidden;isolation:isolate;overflow-wrap:anywhere;word-break:break-word;">' +
|
|
736
747
|
`${innerHtml}</section>`
|
|
737
748
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qcplay/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "QCPlay CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,11 +27,14 @@
|
|
|
27
27
|
"test:auth": "node bin/qcplay.js auth",
|
|
28
28
|
"test:publish": "node bin/qcplay.js www-article-list.store ../article.md",
|
|
29
29
|
"test:wechat": "node test/wechat-article.test.js",
|
|
30
|
+
"test:lark": "node test/lark-article.test.js",
|
|
31
|
+
"test:platforms": "node test/platform-publish.test.js",
|
|
30
32
|
"build:cli": "node --check bin/qcplay.js",
|
|
31
33
|
"pack:check": "npm pack --dry-run"
|
|
32
34
|
},
|
|
33
35
|
"license": "MIT",
|
|
34
36
|
"dependencies": {
|
|
35
|
-
"cheerio": "^1.0.0-rc.12"
|
|
37
|
+
"cheerio": "^1.0.0-rc.12",
|
|
38
|
+
"playwright-core": "1.40.1"
|
|
36
39
|
}
|
|
37
40
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qcplay-article
|
|
3
3
|
version: 1.0.0
|
|
4
|
-
description: "QCPlay
|
|
4
|
+
description: "QCPlay 官网文章上传与多平台分发:Use when user mentions 发布文章、上传文章、官网文章、多平台发布、内容分发、指定平台、TapTap、B站、微博、Discord、文章模板、微信推文转官网文章、微信公众号文章导入、飞书文档转官网文章、文章草稿、攻略文章、活动文章、视频攻略、萌新入门、资料站文章、文章 Front Matter、article.md 等;use for article publishing intent only. Do not use for mail/docs/calendar/auth setup/IM chat tasks."
|
|
5
5
|
metadata:
|
|
6
6
|
requires:
|
|
7
7
|
bins: ["qcplay", "qcplay-cli"]
|
|
@@ -9,14 +9,114 @@ metadata:
|
|
|
9
9
|
authFile: "~/.qcplay/auth.json"
|
|
10
10
|
templateCommand: "qcplay article"
|
|
11
11
|
initCommand: "qcplay article init"
|
|
12
|
-
importCommand: "qcplay article import <wechat-url> [file]"
|
|
12
|
+
importCommand: "qcplay article import <wechat-or-feishu-url> [file]"
|
|
13
13
|
publishCommand: "qcplay www-article-list.store article.md"
|
|
14
|
+
multiPlatformPublishCommand: "qcplay publish article.md --project 最强蜗牛"
|
|
14
15
|
updateCommand: "qcplay article update [id]"
|
|
15
16
|
deleteCommand: "qcplay article delete [id]"
|
|
16
17
|
---
|
|
17
18
|
|
|
18
19
|
# qcplay www-article-list.store
|
|
19
20
|
|
|
21
|
+
## 多平台发布入口
|
|
22
|
+
|
|
23
|
+
当用户要求把同一篇文章分发到官网之外的平台时,使用:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
qcplay publish article.md --project 最强蜗牛
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
用户明确指定平台时传入一个或多个 `--platform`:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
qcplay publish article.md --project 最强蜗牛 --region 国内 --platform TapTap --platform B站
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
用户没有指定平台时,不要替用户猜测;让 `publish` 命令在交互终端列出当前项目可用平台并等待选择。只查看清单使用:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
qcplay publish --project 最强蜗牛 --list-platforms
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
平台配置由 CLI 从飞书只读获取,不得把账号、密码或 webhook 写进文章、项目文件、命令参数或回复。TapTap、好游快爆、B站和微博的账号密码配置必须在加载后丢弃;未登录时必须由用户在浏览器中自行填写登录信息或扫码,CLI 不得向登录页写入账号密码或点击登录提交。浏览器出现扫码、验证码、二次验证或反爬校验时必须暂停并请用户完成,不得绕过。官网单独发布仍使用下文的 `www-article-list.store` 流程,原有草稿预览和确认上线规则不变。
|
|
42
|
+
|
|
43
|
+
默认多平台命令保持直接发布。用户明确要求先查看官网外平台的待发布稿时,增加 `--review-draft`(或 `--review`):CLI 填完标题、富文本、图片、封面和发布设置后,在最终提交前保持浏览器打开;用户在 CMD 输入“可以发布”“发布”或直接按 Enter 后才可继续,输入“取消”时不得点击发布。该参数不改变官网流程。好游快爆没有原生草稿箱,只能保持已填好的编辑页供用户预览。
|
|
44
|
+
|
|
45
|
+
多个浏览器平台必须逐个处理:当前平台登录或验证未完成时不得进入下一个平台;当前浏览器平台失败时,后续平台保持未执行。已有有效持久登录态时必须等待编辑器加载并直接填充内容,不得重复提示登录。好游快爆只允许使用飞书配置中当前游戏对应的 `bbs.3839.com` 发布链接,必须保留有效项目 `fid`(最强蜗牛为 `12201`),缺失或为 `0` 时停止;未登录时先打开官网首页头像登录入口,完成官方 iframe 登录后最多重试 3 次返回原游戏发布页。任何图片或视频都必须确认上传或处理完成后再提交;失败或超时必须停止当前平台。
|
|
46
|
+
|
|
47
|
+
好游快爆新版发帖页的标题是 `#threadTitle[contenteditable="true"]`,正文是 `.ql-editor[contenteditable="true"]`,发布控件是 `.editSubmit.active`。必须在填充后连续回读标题和正文,并忽略 Quill 对空白和换行的重排,同时确认 Quill 已移除空状态;标题最多 48 字。点击发布后必须等待 `setThread` 返回 `code: 100`、成功提示或论坛/帖子页跳转,未取得成功回执不得判定发布成功,并应保留浏览器窗口供用户检查。不得因旧版 `input`/`textarea` 选择器缺失而把已登录编辑页误判成登录页。
|
|
48
|
+
|
|
49
|
+
### TapTap 发布意图映射
|
|
50
|
+
|
|
51
|
+
用户提到 TapTap 的发布形态或设置时,将意图写入文章 Front Matter,命令仍只传文章文件:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
taptap_type: long-post # 长帖;默认值
|
|
55
|
+
taptap_forum: "" # 默认使用当前项目同名论坛;填 0 不添加论坛
|
|
56
|
+
taptap_scheduled: "1" # “定时发布”
|
|
57
|
+
taptap_draft: "0" # “保存草稿”时改为 1
|
|
58
|
+
taptap_images: image-1.png,image-2.png
|
|
59
|
+
taptap_video: ./video.mp4
|
|
60
|
+
taptap_cover: ./cover.png
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- 用户说“发长帖/长贴”时使用 `taptap_type: long-post`。
|
|
64
|
+
- 用户说“发图文”时使用 `taptap_type: image-text`,并确认至少有 1 张、最多 18 张图片。
|
|
65
|
+
- 用户说“发视频”时使用 `taptap_type: video`,并确认存在可读的本地视频文件;远程 URL 不能直接用于文件上传。
|
|
66
|
+
- 用户说“添加游戏论坛”但未另给名称时,使用当前项目名;用户给出论坛名时按原名填写。
|
|
67
|
+
- TapTap 论坛搜索包含两级选择:先点击搜索建议,再点击精确同名论坛结果行,然后执行“返回 → 完成”。必须识别 `tap-button--disabled` CSS 禁用态;只有“完成”可用、弹窗关闭且主页面回读到已选论坛后才能继续发布。主页面已存在同名论坛时不得重复点击反选。
|
|
68
|
+
- 用户说“定时发布”时设置 `taptap_scheduled: "1"`,让 CLI 勾选定时发布;页面打开后由用户确认具体日期与时间。
|
|
69
|
+
- 用户说“存为草稿/保存草稿”时设置 `taptap_draft: "1"`,不得点击正式发布。
|
|
70
|
+
- `taptap_scheduled` 与 `taptap_draft` 不得同时设为 `"1"`;意图冲突时先让用户选择定时发布或保存草稿。
|
|
71
|
+
- 未指定形态时,文章默认使用长帖,避免误入必须上传图片的图文模式。
|
|
72
|
+
- 长帖正文中的图片必须按原文顺序通过 TapTap 官方正文图片控件上传,不得替换成图片 URL 文本;普通 `http/https` 超链接必须保留为 TapTap 可点击链接。
|
|
73
|
+
- Markdown 正文必须先转成富文本;在 TapTap 支持范围内保留标题强调、段落间距、有序/无序列表、引用、粗体、斜体、下划线和删除线。不得声称 TapTap 不支持的任意 CSS 已被完整还原。
|
|
74
|
+
- 图文模式的图片进入 TapTap 顶部图片区,正文超链接仍需保留为可点击链接;视频模式正文也保留超链接。
|
|
75
|
+
- 自动化测试不得连接真实账号上传测试图片;真实编辑器结构探测必须断网进行,不得保存或发布测试草稿。
|
|
76
|
+
- 只有 TapTap 返回成功提示、离开编辑页或发布接口返回成功时,CLI 才能报告成功;仅点击按钮不算成功。
|
|
77
|
+
- TapTap 自动草稿创建/更新不是正式发布成功。正式发布必须等待 `moment/topic` 的直接创建接口或 `moment/topic-draft` 的正式发布接口成功后才能结束;显式保存草稿时才接受草稿创建/更新回执。
|
|
78
|
+
|
|
79
|
+
### 微博发布意图映射
|
|
80
|
+
|
|
81
|
+
微博文章使用头条文章编辑器,不使用首页普通微博发送框。标题、导语和正文必须分别写入对应控件:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
weibo_intro: "" # 可选;留空使用 article_excerpt,最多 44 字
|
|
85
|
+
weibo_cover: "" # 可选;必须引用正文图片,留空默认使用正文第 1 张图
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- 标题写入“请输入标题”文本域,最多 32 个字符;导语写入“导语(选填)”,最多 44 个字符。
|
|
89
|
+
- 正文必须写入 Tiptap/ProseMirror 富文本编辑器,保留平台支持的段落、标题、列表、引用和行内格式。
|
|
90
|
+
- 正文图片必须按原文位置和顺序通过“图片库”上传。首次打开图片库时,必须逐个点击已有 `.image-list .image-item` 内的 `.ico_delpic`,每次确认旧缩略图数量减少后再继续,清空后才上传第一张本次文章图片;本次后续图片不得再次清理。相同规范化图片地址只保留首次出现的一张;等待加载状态结束后点击新 `.image-list .image-item`,确认出现 `is-selected` 且“插入”按钮移除 `n-button--disabled` 后再点击。出现裁剪对话框时使用默认“插入/确定/完成”,正文图片写入后才处理下一张。不得降级为图片 URL 或普通微博九宫格附件。
|
|
91
|
+
- 正文含图片时必须设置文章封面:打开 `.cover-empty` 或现有 `.cover-preview`,在“正文图片”页签选择 `weibo_cover` 指定图片;未指定时选择第 1 个 `.image-list .image-item`。确认 `is-selected` 后依次点击“下一步 → 图片裁剪”,等待 `cropper-image` 和 `cropper-selection` 连续稳定后再点“确定”。若提示“图片处理失败”,等待提示消失后最多自动重试一次;封面未回显时不得继续发布。
|
|
92
|
+
- 普通 `http/https` 超链接必须保留为可点击链接,并在提交前回读检查。
|
|
93
|
+
- 封面确认且回显后必须立即按“下一步 → 发布”完成文章投稿。只有发布接口、成功提示或发布历史页面跳转给出成功回执时,CLI 才能报告成功。
|
|
94
|
+
- 自动化测试不得连接真实微博账号上传或发布测试内容。
|
|
95
|
+
|
|
96
|
+
### B站投稿意图映射
|
|
97
|
+
|
|
98
|
+
用户要求发布 B站专栏或视频时,将投稿形态和平台字段写入文章 Front Matter:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
bilibili_type: article # 专栏图文;视频使用 video
|
|
102
|
+
bilibili_video: ./video.mp4 # 视频投稿使用的本地文件
|
|
103
|
+
bilibili_cover: ./cover.png
|
|
104
|
+
bilibili_category: 游戏/手机游戏
|
|
105
|
+
bilibili_tags: 最强蜗牛,攻略
|
|
106
|
+
bilibili_topic: 最强蜗牛攻略
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- 未指定形态时使用 `bilibili_type: article`,进入官方专栏编辑地址。
|
|
110
|
+
- 用户说“B站视频投稿”时使用 `bilibili_type: video`,并确认 `bilibili_video` 是可读的本地文件;不得把远程 URL 直接交给视频文件控件。
|
|
111
|
+
- 视频文件必须小于 16GB,推荐使用 MP4、MOV 或 MKV;视频上传完成并出现稿件表单后才能填写标题、简介和投稿设置。
|
|
112
|
+
- 专栏正文必须写成富文本;正文图片按原文位置通过 B站官方图片控件上传,`http/https` 超链接必须保留为可点击链接。
|
|
113
|
+
- 在 B站支持范围内保留标题、段落、列表、引用、粗体、斜体、下划线、删除线、颜色和对齐;不得声称任意 CSS 都能完整还原。
|
|
114
|
+
- 视频简介不等同于专栏富文本。带标签的链接应同时写入可见 URL,避免只剩链接文字。
|
|
115
|
+
- `bilibili_category` 使用 `/` 分隔多级分区;`bilibili_tags` 最多 10 个。
|
|
116
|
+
- 用户指定 B站话题时写入 `bilibili_topic`。CLI 必须搜索并选择精确同名话题,找不到时停止投稿,不得改选相似话题;未指定时不得猜测话题。
|
|
117
|
+
- 只有 B站返回成功提示、离开投稿页或投稿接口返回成功时,CLI 才能报告成功;仅点击按钮不算成功。
|
|
118
|
+
- 自动化测试不得连接真实 B站账号上传图片或视频,也不得保存或发布测试稿件。
|
|
119
|
+
|
|
20
120
|
## CRITICAL — 文章上传工作流,必须遵循
|
|
21
121
|
|
|
22
122
|
本 Skill 用于通过 QCPlay CLI 保存、预览、上线和删除官网文章。
|
|
@@ -43,6 +143,14 @@ qcplay-cli www-article-list.store article.md
|
|
|
43
143
|
qcplay article import "https://mp.weixin.qq.com/s/..." article.md
|
|
44
144
|
```
|
|
45
145
|
|
|
146
|
+
当内容来源是飞书 Docx/Wiki 文档时,也使用 `article import`。CLI 必须读取 `docs +fetch --detail full` 的 XML 样式结构,不得用会丢失颜色、高亮、对齐和列宽的 Markdown 导出替代:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
qcplay article import "https://tenant.feishu.cn/wiki/..." article.md
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
飞书转换需保留标题层级、粗体/斜体、文字颜色、背景高亮、对齐、引用、编号列表、链接、表格列宽和合并单元格;宽表格在窄屏横向滚动。无法取得完整 XML 或图片缺少可访问地址时必须停止,不生成不完整草稿。
|
|
153
|
+
|
|
46
154
|
该命令只生成本地官网文章草稿,不会提交到官网。确认草稿正文和 Front Matter 后,再执行保存命令。
|
|
47
155
|
|
|
48
156
|
保存命令始终以 `status: "0"` 创建未上线文章,并输出文章 ID 和可点击预览地址。保存成功后必须打开该预览网页,只让用户本人检查文章内容和排版。AI 不得自动访问、抓取、截图、分析、比较或判断预览内容。只有用户明确确认上线后,才能执行:
|
|
@@ -16,6 +16,21 @@ sort: 100
|
|
|
16
16
|
game_id: 最强蜗牛
|
|
17
17
|
index_pc_img: ""
|
|
18
18
|
index_move_img: ""
|
|
19
|
+
taptap_type: long-post
|
|
20
|
+
taptap_forum: "" # 留空默认使用当前项目同名论坛;填 0 不添加论坛
|
|
21
|
+
taptap_scheduled: "0"
|
|
22
|
+
taptap_draft: "0"
|
|
23
|
+
taptap_images: ""
|
|
24
|
+
taptap_video: ""
|
|
25
|
+
taptap_cover: ""
|
|
26
|
+
bilibili_type: article
|
|
27
|
+
bilibili_video: ""
|
|
28
|
+
bilibili_cover: ""
|
|
29
|
+
bilibili_category: ""
|
|
30
|
+
bilibili_tags: ""
|
|
31
|
+
bilibili_topic: ""
|
|
32
|
+
weibo_intro: ""
|
|
33
|
+
weibo_cover: ""
|
|
19
34
|
---
|
|
20
35
|
|
|
21
36
|
# 最强蜗牛萌新入门指南
|