From d2ff5db8f2f1ad011e2bb3d6b8ba7ccc3adadfba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=99?= Date: Sat, 9 May 2026 15:33:28 +0000 Subject: [PATCH] =?UTF-8?q?chore:=20=E4=BB=8A=E6=97=A5=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=80=BB=E7=BB=93=E5=A4=8D=E7=9B=98-=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E6=B5=81=E7=A8=8B/Skill/=E5=86=85=E5=AE=B9=E8=A7=84=E8=8C=83?= =?UTF-8?q?=E6=B2=89=E6=B7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docx-publisher.skill | Bin 0 -> 4360 bytes insights.md | 64 ++++++++++++ skills/docx-publisher/SKILL.md | 69 +++++++++++++ .../docx-publisher/references/平台合规规则.md | 92 ++++++++++++++++++ skills/docx-publisher/scripts/gen_docx.js | 81 +++++++++++++++ tmp_gen_docx.js | 59 +++++++++++ 6 files changed, 365 insertions(+) create mode 100644 docx-publisher.skill create mode 100644 skills/docx-publisher/SKILL.md create mode 100644 skills/docx-publisher/references/平台合规规则.md create mode 100644 skills/docx-publisher/scripts/gen_docx.js create mode 100644 tmp_gen_docx.js diff --git a/docx-publisher.skill b/docx-publisher.skill new file mode 100644 index 0000000000000000000000000000000000000000..1a56186a34fca9f42d51641645630ece0a0e7e3d GIT binary patch literal 4360 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主题>/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 '' +``` + +### Step 4:发给用户 + +发 MEDIA: 路径给用户,告知可直接导入平台发布。 + +## docx 格式规范 + +| 元素 | 样式 | +|------|------| +| 标题 | 24pt+,加粗,居中 | +| 副标题/摘要 | 灰色,斜体,居中 | +| 小标题(H2) | 加粗,段落前后间距 | +| 核心观点(加粗句)| 加粗,可带底边线 | +| 正文 | 标准字 | +| 结束语/来源 | 灰色,小字,居中 | +| 图片 | 宽度480-580px,居中 | + +## 禁止出现在发布版的内容 + +- 所有平台禁用词(见 `references/平台合规规则.md`) +- 联系方式(电话/QQ/微信/网址) +- 绝对化用语(最好/第一/国家级/唯一等) +- 未通过合规扫描的内容 +- `[时间] 旁白类型` 格式的分镜指令 diff --git a/skills/docx-publisher/references/平台合规规则.md b/skills/docx-publisher/references/平台合规规则.md new file mode 100644 index 0000000..b763c58 --- /dev/null +++ b/skills/docx-publisher/references/平台合规规则.md @@ -0,0 +1,92 @@ +# 平台合规规则 + +> 生成各平台发布版 docx 前,必须读取本文件并对目标平台应用对应规则。 + +--- + +## 化工仪器网 (chem17.com) + +**审核严格程度:高** + +### 禁用词(直接替换,不得出现) + +| 禁用词 | 替代表达 | +|--------|---------| +| 卓越 | 更高级别 | +| 先进 | 高等级 | +| 领航 | 最高级别 | +| 工信部 | 六部门 | +| 首次 | 首次 → "这一次" | +| 第一 | 首位/领先 | +| 国家级 | 全国性 | +| 行业标杆 | 行业示范 | +| 保证/确保/100% | 通常可达到/通常情况下 | +| 国家级 | 全国性标准 | +| 官方 | 权威 | + +### 特殊规则 + +- **禁止插入联系方式**:电话/QQ/微信/网址均不可出现 +- **图片中不得含联系方式水印** +- **来源处不得写网址**,只写公司简称 +- **数据须注明来源**:补贴金额后加"(以各地政策为准)";案例成效加"(模拟参考值,以实际为准)" + +### 替代表达库 + +``` +"详细方案" → "完整方案" +"完美解决" → "有效改善" +"彻底解决" → "大幅改善" +"遥遥领先" → "具备优势" +"绝对可靠" → "稳定可靠" +"唯一" → "少数具备" +"独家" → "自主研发" +``` + +--- + +## 百家号 + +**审核严格程度:中** + +- 禁用词参照广告法(参见 `brand/banned-words.md`) +- 联系方式:可在文末注明公司名,但不可留具体手机号/微信号 +- 不得出现竞品负面表述 + +--- + +## 搜狐号 + +**审核严格程度:中** + +- 基本同百家号规则 +- 图片中不得含水印联系方式 + +--- + +## 微信公众号 + +**审核严格程度:中** + +- 关注自动回复/外部链接需合规 +- 不得诱导关注/分享 +- 图片须有版权 + +--- + +## CSDN / 博客园 + +**审核严格程度:低** + +- 技术文章为主,相对宽松 +- 代码块须格式正确 +- 可保留技术参考链接 + +--- + +## 通用规则(所有平台) + +1. **数据必须有来源**:补贴金额注明政策依据;案例数据注明"模拟参考值" +2. **禁用绝对化用语**:最好/最佳/第一/唯一/顶级/完美 +3. **竞品中性原则**:不得出现"碾压/完爆/吊打"等贬低竞品词汇 +4. **联系方式处理**:原则上不发手机号/微信号,平台要求除外 diff --git a/skills/docx-publisher/scripts/gen_docx.js b/skills/docx-publisher/scripts/gen_docx.js new file mode 100644 index 0000000..84f3612 --- /dev/null +++ b/skills/docx-publisher/scripts/gen_docx.js @@ -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 '' + */ + +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); }); \ No newline at end of file diff --git a/tmp_gen_docx.js b/tmp_gen_docx.js new file mode 100644 index 0000000..11b322e --- /dev/null +++ b/tmp_gen_docx.js @@ -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); });