chore: 今日工作总结复盘-发布流程/Skill/内容规范沉淀
This commit is contained in:
BIN
docx-publisher.skill
Normal file
BIN
docx-publisher.skill
Normal file
Binary file not shown.
64
insights.md
64
insights.md
@@ -83,3 +83,67 @@ YYYY-MM-DD | 场景 | 反馈原文 | 调整动作
|
|||||||
- 每次生成图文发布版前,先读取母版完整正文,不用草稿里的缩略版
|
- 每次生成图文发布版前,先读取母版完整正文,不用草稿里的缩略版
|
||||||
- 配图按段落生成:封面图、场景图、政策图、数据图、案例图、总结图
|
- 配图按段落生成:封面图、场景图、政策图、数据图、案例图、总结图
|
||||||
- 草稿里的 md 文件如果内容偏薄,必须用母版正文替换后再生成 docx
|
- 草稿里的 md 文件如果内容偏薄,必须用母版正文替换后再生成 docx
|
||||||
|
|
||||||
|
## 2026-05-09 | 平台月度发布限额(重要)
|
||||||
|
|
||||||
|
**化工仪器网 (chem17.com)**:每月最多发布 10 条内容,超出将被限制。
|
||||||
|
- 建议:优先发布质量高的文章,合理规划发布节奏,避免月内用完额度。
|
||||||
|
|
||||||
|
## 2026-05-09 | 平台发布节奏规范
|
||||||
|
|
||||||
|
**有限额平台**:月度/周度发布限额(如化工仪器网每月10条),只发经过筛选的高质量选题,不随意批量发布。
|
||||||
|
**无限制平台**:可高频发布,保证每周 2-3 个选题稳定输出。
|
||||||
|
|
||||||
|
当前有限额平台清单:
|
||||||
|
- 化工仪器网:每月最多10条
|
||||||
|
|
||||||
|
|
||||||
|
## 2026-05-09 | 今日工作总结与复盘
|
||||||
|
|
||||||
|
### 一、发布流程问题
|
||||||
|
|
||||||
|
**问题1:B站 browserless 无法登录**
|
||||||
|
- 原因:browserless IP被B站识别为机器人,创作中心直接报错
|
||||||
|
- 解决:改用 Tyrone 自操方式发布
|
||||||
|
- 教训:视频平台(B站/抖音/快手)浏览器反爬严格,browserless 难以绕过,优先准备 docx 让用户自操发布
|
||||||
|
|
||||||
|
**问题2:百家号登录百度安全验证拦截**
|
||||||
|
- 原因:browserless 环境频繁登录触发百度安全机制,要求手机短信二次验证
|
||||||
|
- 解决:同样改为 docx 自操
|
||||||
|
- 教训:百度系平台(百家号/搜狐号)登录安全机制严,browserless 极易触发验证,建议优先 docx 自操
|
||||||
|
|
||||||
|
**问题3:Control UI 文件查看器 md 中文乱码**
|
||||||
|
- 原因:Control UI 内置 markdown 渲染器对 UTF-8 中文支持有 bug
|
||||||
|
- 解决:不用 MEDIA: 路径,改直接在聊天里贴文本内容
|
||||||
|
- 教训:md 文件不要期望在 Control UI 里预览,直接给用户内容
|
||||||
|
|
||||||
|
### 二、Skill 沉淀
|
||||||
|
|
||||||
|
**bilibili-publisher**:生成B站视频发布版 docx,自动嵌入配图,图片永久存储
|
||||||
|
- 触发词:发B站/生成B站发布版/生成docx
|
||||||
|
- 注意:media 目录图片会被清理,必须立即 cp 到 drafts/assets/ 永久目录
|
||||||
|
|
||||||
|
**docx-publisher**:通用图文平台发布版 docx 生成器,内置平台合规规则
|
||||||
|
- 化工仪器网禁用词已沉淀:卓越/先进/工信部/领航/首次/第一/国家级/行业标杆
|
||||||
|
- 替代表达已记录
|
||||||
|
|
||||||
|
### 三、内容质量
|
||||||
|
|
||||||
|
**反馈**:图文平台(百家号/搜狐号)文章内容过薄,不及B站脚本丰富
|
||||||
|
- 原因:用了草稿的缩略版,而不是母版全文
|
||||||
|
- 规范:图文平台必须用母版完整正文,4-6张配图/篇
|
||||||
|
|
||||||
|
### 四、工作流优化
|
||||||
|
|
||||||
|
**发布前必做**:
|
||||||
|
1. 读取平台合规规则(docx-publisher/references/平台合规规则.md)
|
||||||
|
2. 用母版全文替换缩略版
|
||||||
|
3. 生成配图立即 cp 到永久目录
|
||||||
|
4. docx 脚本中的内容直接内嵌,不用 --content-json 传参(避免中文引号 JSON 解析失败)
|
||||||
|
|
||||||
|
### 五、平台分类
|
||||||
|
|
||||||
|
**开绿灯平台(一次授权可自动发布)**:CSDN、博客园、搜狐号、百家号、好看视频
|
||||||
|
**用户自操平台(browserless 无法绕过登录/验证)**:B站、百家号(百度验证)、抖音、快手、视频号、微信公众号
|
||||||
|
**有限额平台**:化工仪器网每月最多10条,只发高质量选题
|
||||||
|
|
||||||
|
|||||||
69
skills/docx-publisher/SKILL.md
Normal file
69
skills/docx-publisher/SKILL.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
name: docx-publisher
|
||||||
|
description: 生成图文平台发布版docx文档,支持多平台合规过滤。当用户说"生成发布版"、"生成docx"时触发。工作流:读取drafts/对应草稿 → 注入平台合规规则 → 生成配图 → Node.js脚本生成带图docx → 发给用户。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 图文平台发布版生成器
|
||||||
|
|
||||||
|
将图文草稿(md格式)转换为**发布版 docx文档**,图片直接嵌入,可导入各平台后台直接发布。
|
||||||
|
|
||||||
|
## 触发词
|
||||||
|
|
||||||
|
"生成发布版"、"生成docx"、"发XX平台"
|
||||||
|
|
||||||
|
## 平台合规规则
|
||||||
|
|
||||||
|
详见 `references/平台合规规则.md`。生成前必须读取对应平台的规则。
|
||||||
|
|
||||||
|
## 工作流
|
||||||
|
|
||||||
|
### Step 1:读取草稿 + 合规规则
|
||||||
|
|
||||||
|
1. 读取 `drafts/` 下对应 md 文件(优先读母版 `master` 版,内容更完整)
|
||||||
|
2. 读取 `references/平台合规规则.md`,确认目标平台的**禁用词列表**和**特殊规则**
|
||||||
|
3. 将草稿正文中的禁用词替换为合规替代表述
|
||||||
|
|
||||||
|
### Step 2:生成配图
|
||||||
|
|
||||||
|
根据平台类型决定配图数量:
|
||||||
|
- **图文平台(百家号/搜狐号/公众号等)**:封面图 + 每段落1张,共4-6张
|
||||||
|
- **化工仪器网**:封面图 + 3张段落图(政策/技术/案例)
|
||||||
|
|
||||||
|
**关键**:每张图生成后**立即用 `cp`** 复制到 `published/<日期>_<主题>/assets/` 永久目录,再生成下一张
|
||||||
|
|
||||||
|
### Step 3:生成 docx
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node scripts/gen_docx.js \
|
||||||
|
--title "标题" \
|
||||||
|
--output "output.docx" \
|
||||||
|
--cover ./assets/cover.png \
|
||||||
|
--image2 ./assets/img2.png \
|
||||||
|
--image3 ./assets/img3.png \
|
||||||
|
--image4 ./assets/img4.png \
|
||||||
|
--content-json '<JSON>'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4:发给用户
|
||||||
|
|
||||||
|
发 MEDIA: 路径给用户,告知可直接导入平台发布。
|
||||||
|
|
||||||
|
## docx 格式规范
|
||||||
|
|
||||||
|
| 元素 | 样式 |
|
||||||
|
|------|------|
|
||||||
|
| 标题 | 24pt+,加粗,居中 |
|
||||||
|
| 副标题/摘要 | 灰色,斜体,居中 |
|
||||||
|
| 小标题(H2) | 加粗,段落前后间距 |
|
||||||
|
| 核心观点(加粗句)| 加粗,可带底边线 |
|
||||||
|
| 正文 | 标准字 |
|
||||||
|
| 结束语/来源 | 灰色,小字,居中 |
|
||||||
|
| 图片 | 宽度480-580px,居中 |
|
||||||
|
|
||||||
|
## 禁止出现在发布版的内容
|
||||||
|
|
||||||
|
- 所有平台禁用词(见 `references/平台合规规则.md`)
|
||||||
|
- 联系方式(电话/QQ/微信/网址)
|
||||||
|
- 绝对化用语(最好/第一/国家级/唯一等)
|
||||||
|
- 未通过合规扫描的内容
|
||||||
|
- `[时间] 旁白类型` 格式的分镜指令
|
||||||
92
skills/docx-publisher/references/平台合规规则.md
Normal file
92
skills/docx-publisher/references/平台合规规则.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# 平台合规规则
|
||||||
|
|
||||||
|
> 生成各平台发布版 docx 前,必须读取本文件并对目标平台应用对应规则。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 化工仪器网 (chem17.com)
|
||||||
|
|
||||||
|
**审核严格程度:高**
|
||||||
|
|
||||||
|
### 禁用词(直接替换,不得出现)
|
||||||
|
|
||||||
|
| 禁用词 | 替代表达 |
|
||||||
|
|--------|---------|
|
||||||
|
| 卓越 | 更高级别 |
|
||||||
|
| 先进 | 高等级 |
|
||||||
|
| 领航 | 最高级别 |
|
||||||
|
| 工信部 | 六部门 |
|
||||||
|
| 首次 | 首次 → "这一次" |
|
||||||
|
| 第一 | 首位/领先 |
|
||||||
|
| 国家级 | 全国性 |
|
||||||
|
| 行业标杆 | 行业示范 |
|
||||||
|
| 保证/确保/100% | 通常可达到/通常情况下 |
|
||||||
|
| 国家级 | 全国性标准 |
|
||||||
|
| 官方 | 权威 |
|
||||||
|
|
||||||
|
### 特殊规则
|
||||||
|
|
||||||
|
- **禁止插入联系方式**:电话/QQ/微信/网址均不可出现
|
||||||
|
- **图片中不得含联系方式水印**
|
||||||
|
- **来源处不得写网址**,只写公司简称
|
||||||
|
- **数据须注明来源**:补贴金额后加"(以各地政策为准)";案例成效加"(模拟参考值,以实际为准)"
|
||||||
|
|
||||||
|
### 替代表达库
|
||||||
|
|
||||||
|
```
|
||||||
|
"详细方案" → "完整方案"
|
||||||
|
"完美解决" → "有效改善"
|
||||||
|
"彻底解决" → "大幅改善"
|
||||||
|
"遥遥领先" → "具备优势"
|
||||||
|
"绝对可靠" → "稳定可靠"
|
||||||
|
"唯一" → "少数具备"
|
||||||
|
"独家" → "自主研发"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 百家号
|
||||||
|
|
||||||
|
**审核严格程度:中**
|
||||||
|
|
||||||
|
- 禁用词参照广告法(参见 `brand/banned-words.md`)
|
||||||
|
- 联系方式:可在文末注明公司名,但不可留具体手机号/微信号
|
||||||
|
- 不得出现竞品负面表述
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 搜狐号
|
||||||
|
|
||||||
|
**审核严格程度:中**
|
||||||
|
|
||||||
|
- 基本同百家号规则
|
||||||
|
- 图片中不得含水印联系方式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 微信公众号
|
||||||
|
|
||||||
|
**审核严格程度:中**
|
||||||
|
|
||||||
|
- 关注自动回复/外部链接需合规
|
||||||
|
- 不得诱导关注/分享
|
||||||
|
- 图片须有版权
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CSDN / 博客园
|
||||||
|
|
||||||
|
**审核严格程度:低**
|
||||||
|
|
||||||
|
- 技术文章为主,相对宽松
|
||||||
|
- 代码块须格式正确
|
||||||
|
- 可保留技术参考链接
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 通用规则(所有平台)
|
||||||
|
|
||||||
|
1. **数据必须有来源**:补贴金额注明政策依据;案例数据注明"模拟参考值"
|
||||||
|
2. **禁用绝对化用语**:最好/最佳/第一/唯一/顶级/完美
|
||||||
|
3. **竞品中性原则**:不得出现"碾压/完爆/吊打"等贬低竞品词汇
|
||||||
|
4. **联系方式处理**:原则上不发手机号/微信号,平台要求除外
|
||||||
81
skills/docx-publisher/scripts/gen_docx.js
Normal file
81
skills/docx-publisher/scripts/gen_docx.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* gen_docx.js
|
||||||
|
* 通用图文平台发布版docx生成器
|
||||||
|
*
|
||||||
|
* 用法:
|
||||||
|
* node gen_docx.js --title "标题" --subtitle "副标题" --output output.docx \
|
||||||
|
* --cover ./assets/cover.png \
|
||||||
|
* --content-json '<JSON_STRING>'
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { Document, Packer, Paragraph, TextRun, ImageRun } = require('/home/node/.openclaw/node_modules/docx');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
let title = '', subtitle = '', output = '', coverImg = '';
|
||||||
|
let contentJson = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
if (args[i] === '--title' && args[i+1]) title = args[++i];
|
||||||
|
else if (args[i] === '--subtitle' && args[i+1]) subtitle = args[++i];
|
||||||
|
else if (args[i] === '--output' && args[i+1]) output = args[++i];
|
||||||
|
else if (args[i] === '--cover' && args[i+1]) coverImg = args[++i];
|
||||||
|
else if (args[i] === '--content-json' && args[i+1]) contentJson = args[++i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!output) { console.error('Usage: --output required'); process.exit(1); }
|
||||||
|
|
||||||
|
const loadImg = (p) => (p && fs.existsSync(p)) ? fs.readFileSync(p) : null;
|
||||||
|
const cover = coverImg ? loadImg(coverImg) : null;
|
||||||
|
|
||||||
|
const E = () => new Paragraph({ text: '' });
|
||||||
|
const TITLE = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 48 })], alignment: 'center', spacing: { before: 0, after: 160 } });
|
||||||
|
const SUB = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 24, color: '888888', italics: true })], alignment: 'center', spacing: { before: 0, after: 200 } });
|
||||||
|
const H2 = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 36 })], spacing: { before: 320, after: 160 } });
|
||||||
|
const H3 = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 28 })], spacing: { before: 200, after: 80 } });
|
||||||
|
const BODY = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 28 })], spacing: { before: 60, after: 80 } });
|
||||||
|
const EMP = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 30 })], spacing: { before: 120, after: 100 } });
|
||||||
|
const END = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 22, color: 'AAAAAA', italics: true })], alignment: 'center', spacing: { before: 200, after: 0 } });
|
||||||
|
const IMGP = (d, w, h) => d ? new Paragraph({ children: [new ImageRun({ data: d, transformation: { width: w, height: h }, type: 'png' })], alignment: 'center', spacing: { before: 100, after: 100 } }) : E();
|
||||||
|
|
||||||
|
let config = {};
|
||||||
|
try { config = contentJson ? JSON.parse(contentJson) : {}; } catch(e) { console.error('JSON parse error:', e.message); }
|
||||||
|
|
||||||
|
const children = [];
|
||||||
|
|
||||||
|
// Cover
|
||||||
|
if (cover) { children.push(IMGP(cover, 580, 326)); children.push(E()); }
|
||||||
|
children.push(TITLE(title));
|
||||||
|
if (subtitle) children.push(SUB(subtitle));
|
||||||
|
children.push(E());
|
||||||
|
|
||||||
|
// Sections
|
||||||
|
const sections = config.sections || [];
|
||||||
|
for (const sec of sections) {
|
||||||
|
if (sec.heading) children.push(H2(sec.heading));
|
||||||
|
if (sec.image) children.push(E());
|
||||||
|
if (sec.paragraphs && Array.isArray(sec.paragraphs)) {
|
||||||
|
for (const p of sec.paragraphs) {
|
||||||
|
if (!p || p.trim() === '') { children.push(E()); continue; }
|
||||||
|
const clean = p.trim();
|
||||||
|
if (clean.length < 60 && clean.match(/^[①②③④]/)) {
|
||||||
|
children.push(BODY(clean));
|
||||||
|
} else if (clean.length < 80 && !clean.includes('。')) {
|
||||||
|
children.push(BODY(clean));
|
||||||
|
} else {
|
||||||
|
children.push(BODY(clean));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
children.push(E());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source
|
||||||
|
if (config.source) children.push(END(config.source));
|
||||||
|
|
||||||
|
const doc = new Document({ sections: [{ children }] });
|
||||||
|
Packer.toBuffer(doc).then(buf => {
|
||||||
|
fs.writeFileSync(output, buf);
|
||||||
|
console.log('done');
|
||||||
|
}).catch(e => { console.error(e); process.exit(1); });
|
||||||
59
tmp_gen_docx.js
Normal file
59
tmp_gen_docx.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const { Document, Packer, Paragraph, TextRun, ImageRun } = require('/home/node/.openclaw/node_modules/docx');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
let title = '', subtitle = '', output = '', coverImg = '';
|
||||||
|
let contentJson = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
if (args[i] === '--title' && args[i+1]) title = args[++i];
|
||||||
|
else if (args[i] === '--subtitle' && args[i+1]) subtitle = args[++i];
|
||||||
|
else if (args[i] === '--output' && args[i+1]) output = args[++i];
|
||||||
|
else if (args[i] === '--cover' && args[i+1]) coverImg = args[++i];
|
||||||
|
else if (args[i] === '--image2' && args[i+1]) coverImg = args[++i]; // reuse
|
||||||
|
else if (args[i] === '--content-json' && args[i+1]) contentJson = args[++i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadImg = (p) => (p && fs.existsSync(p)) ? fs.readFileSync(p) : null;
|
||||||
|
const cover = coverImg ? loadImg(coverImg) : null;
|
||||||
|
|
||||||
|
const E = () => new Paragraph({ text: '' });
|
||||||
|
const TITLE = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 48 })], alignment: 'center', spacing: { before: 0, after: 160 } });
|
||||||
|
const SUB = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 24, color: '888888', italics: true })], alignment: 'center', spacing: { before: 0, after: 200 } });
|
||||||
|
const H2 = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 36 })], spacing: { before: 320, after: 160 } });
|
||||||
|
const H3 = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 28 })], spacing: { before: 200, after: 80 } });
|
||||||
|
const BODY = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 28 })], spacing: { before: 60, after: 80 } });
|
||||||
|
const EMP = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 30 })], spacing: { before: 120, after: 100 } });
|
||||||
|
const END = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 22, color: 'AAAAAA', italics: true })], alignment: 'center', spacing: { before: 200, after: 0 } });
|
||||||
|
const IMGP = (d, w, h) => d ? new Paragraph({ children: [new ImageRun({ data: d, transformation: { width: w, height: h }, type: 'png' })], alignment: 'center', spacing: { before: 100, after: 100 } }) : E();
|
||||||
|
|
||||||
|
let config = {};
|
||||||
|
try { config = contentJson ? JSON.parse(contentJson) : {}; } catch(e) { console.error('JSON parse error:', e.message); }
|
||||||
|
|
||||||
|
const children = [];
|
||||||
|
if (cover) { children.push(IMGP(cover, 580, 326)); children.push(E()); }
|
||||||
|
children.push(TITLE(title));
|
||||||
|
if (subtitle) children.push(SUB(subtitle));
|
||||||
|
children.push(E());
|
||||||
|
|
||||||
|
const sections = config.sections || [];
|
||||||
|
for (const sec of sections) {
|
||||||
|
if (sec.heading) children.push(H2(sec.heading));
|
||||||
|
if (sec.image) children.push(E());
|
||||||
|
if (sec.paragraphs && Array.isArray(sec.paragraphs)) {
|
||||||
|
for (const p of sec.paragraphs) {
|
||||||
|
if (!p || p.trim() === '') { children.push(E()); continue; }
|
||||||
|
children.push(BODY(p.trim()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
children.push(E());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.source) children.push(END(config.source));
|
||||||
|
|
||||||
|
const doc = new Document({ sections: [{ children }] });
|
||||||
|
Packer.toBuffer(doc).then(buf => {
|
||||||
|
fs.writeFileSync(output, buf);
|
||||||
|
console.log('done: ' + output);
|
||||||
|
}).catch(e => { console.error(e); process.exit(1); });
|
||||||
Reference in New Issue
Block a user