feat(publish): B站-智能工厂四级补贴首发归档
- drafts/ 按日期+名称分类重整 - 2026-05-09_B站首发归档至published/ - 配图6张永久存档
This commit is contained in:
131
skills/bilibili-publisher/scripts/gen_bilibili_docx.js
Normal file
131
skills/bilibili-publisher/scripts/gen_bilibili_docx.js
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* gen_bilibili_docx.js
|
||||
* 生成B站视频发布版docx文档
|
||||
*
|
||||
* 用法:
|
||||
* node gen_bilibili_docx.js \
|
||||
* --title "视频标题" \
|
||||
* --subtitle "封面副标题" \
|
||||
* --intro "视频简介和标签" \
|
||||
* --output "output.docx" \
|
||||
* --cover "cover.png" \
|
||||
* --opening "opening.png" \
|
||||
* --policy "policy.png" \
|
||||
* --subsidy "subsidy.png" \
|
||||
* --indicators "indicators.png" \
|
||||
* --case "case.png" \
|
||||
* --content-json '<JSON_STRING>'
|
||||
*
|
||||
* JSON_STRING 格式:
|
||||
* {
|
||||
* "sections": [
|
||||
* { "heading": "小标题", "img": "对应图片key(cover/opening/policy/subsidy/indicators/case)", "paragraphs": ["正文1", "正文2"] }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
|
||||
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 = '', intro = '', output = '';
|
||||
let coverImg = '', openingImg = '', policyImg = '', subsidyImg = '', indicatorsImg = '', caseImg = '';
|
||||
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] === '--intro' && args[i+1]) intro = 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] === '--opening' && args[i+1]) openingImg = args[++i];
|
||||
else if (args[i] === '--policy' && args[i+1]) policyImg = args[++i];
|
||||
else if (args[i] === '--subsidy' && args[i+1]) subsidyImg = args[++i];
|
||||
else if (args[i] === '--indicators' && args[i+1]) indicatorsImg = args[++i];
|
||||
else if (args[i] === '--case' && args[i+1]) caseImg = args[++i];
|
||||
else if (args[i] === '--content-json' && args[i+1]) contentJson = args[++i];
|
||||
}
|
||||
|
||||
if (!output) { console.error('Error: --output required'); process.exit(1); }
|
||||
|
||||
const imgMap = { cover: coverImg, opening: openingImg, policy: policyImg, subsidy: subsidyImg, indicators: indicatorsImg, case: caseImg };
|
||||
const loadImg = (k) => { const p = imgMap[k]; return (p && fs.existsSync(p)) ? fs.readFileSync(p) : null; };
|
||||
const imgs = { cover: loadImg('cover'), opening: loadImg('opening'), policy: loadImg('policy'), subsidy: loadImg('subsidy'), indicators: loadImg('indicators'), case: loadImg('case') };
|
||||
|
||||
const E = () => new Paragraph({ text: '' });
|
||||
const TITLE = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 56 })], alignment: 'center', spacing: { before: 0, after: 160 } });
|
||||
const SUB = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 28, color: '666666' })], alignment: 'center', spacing: { before: 0, after: 200 } });
|
||||
const H2 = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 36 })], spacing: { before: 240, after: 120 } });
|
||||
const BODY = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 28 })], spacing: { before: 60, after: 60 } });
|
||||
const HL = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 28, color: '1A1A1A' })], spacing: { before: 120, after: 80 } });
|
||||
const EMP = (t) => new Paragraph({ children: [new TextRun({ text: t, bold: true, size: 28 })], spacing: { before: 160, after: 160 }, border: { bottom: { color: 'CCCCCC', space: 1, style: 'single', size: 4 } } });
|
||||
const END = (t) => new Paragraph({ children: [new TextRun({ text: t, size: 28, italics: true, color: '555555' })], spacing: { before: 80, after: 80 } });
|
||||
const IMGP = (d, w, h) => d ? new Paragraph({ children: [new ImageRun({ data: d, transformation: { width: w, height: h }, type: 'png' })], alignment: 'center', spacing: { before: 80, after: 80 } }) : E();
|
||||
|
||||
let sections = [];
|
||||
try { sections = contentJson ? JSON.parse(contentJson) : {}; } catch(e) { console.error('JSON parse error:', e.message); }
|
||||
|
||||
// Auto-detect which image matches a heading keyword
|
||||
const headingImgMap = {
|
||||
'开场': 'opening', '悬念': 'opening', '引入': 'opening',
|
||||
'政策': 'policy', '全景': 'policy', '四级': 'policy',
|
||||
'补贴': 'subsidy', '金额': 'subsidy', '数字': 'subsidy',
|
||||
'指标': 'indicators', '门槛': 'indicators', '硬指标': 'indicators',
|
||||
'案例': 'case', '真实': 'case', 'SCADA': 'case',
|
||||
};
|
||||
|
||||
const detectImg = (heading) => {
|
||||
if (!heading) return null;
|
||||
for (const [kw, imgKey] of Object.entries(headingImgMap)) {
|
||||
if (heading.includes(kw)) return imgs[imgKey];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const children = [];
|
||||
|
||||
// Cover
|
||||
children.push(IMGP(imgs.cover, 560, 315));
|
||||
children.push(E());
|
||||
children.push(TITLE(title));
|
||||
if (subtitle) children.push(SUB(subtitle));
|
||||
if (intro) { const introPara = new Paragraph({ children: [new TextRun({ text: intro, size: 22, color: '0077CC' })], alignment: 'center', spacing: { before: 0, after: 200 } }); children.push(introPara); }
|
||||
children.push(E());
|
||||
|
||||
// Sections
|
||||
if (sections.sections && Array.isArray(sections.sections)) {
|
||||
for (const sec of sections.sections) {
|
||||
if (sec.heading) children.push(H2(sec.heading));
|
||||
const secImg = sec.img ? imgs[sec.img] : detectImg(sec.heading);
|
||||
if (secImg) { children.push(IMGP(secImg, 480, 270)); 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.includes('——') || clean.match(/^[①②③④]/))) {
|
||||
children.push(BODY(clean));
|
||||
} else if (clean.length < 80 && !clean.includes('。')) {
|
||||
children.push(BODY(clean));
|
||||
} else if (clean.startsWith('!') || clean.startsWith('?')) {
|
||||
children.push(HL(clean));
|
||||
} else {
|
||||
children.push(BODY(clean));
|
||||
}
|
||||
}
|
||||
}
|
||||
children.push(E());
|
||||
}
|
||||
}
|
||||
|
||||
// Source
|
||||
children.push(new Paragraph({
|
||||
children: [new TextRun({ text: '内容来源:上海橙轩智能(Orpaon)· 制造业数字化解决方案 | 官网:www.orpaon.com', size: 20, color: '999999' })],
|
||||
alignment: 'center', spacing: { before: 200, after: 0 },
|
||||
}));
|
||||
|
||||
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); });
|
||||
Reference in New Issue
Block a user