publishport-opencli 1.0.28 → 1.0.30
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/cli-manifest.json +1297 -13
- package/clis/_shared/publish-controls.js +11 -0
- package/clis/douyin/_shared/ai-declaration.js +1 -0
- package/clis/douyin/_shared/browser-fetch.js +31 -13
- package/clis/douyin/_shared/comments.js +1 -0
- package/clis/douyin/_shared/publish-settings.js +1 -0
- package/clis/douyin/activities.js +1 -1
- package/clis/douyin/collections.js +1 -1
- package/clis/douyin/comment-like.js +1 -0
- package/clis/douyin/comment.js +1 -0
- package/clis/douyin/comments.js +1 -0
- package/clis/douyin/draft.js +8 -8
- package/clis/douyin/hashtag.js +1 -1
- package/clis/douyin/location.js +1 -1
- package/clis/douyin/music-categories.js +1 -0
- package/clis/douyin/music.js +1 -0
- package/clis/douyin/publish-image.js +12 -12
- package/clis/douyin/publish.js +9 -8
- package/clis/xiaohongshu/_shared/ai-declaration.js +1 -0
- package/clis/xiaohongshu/_shared/comment-actions.js +9 -0
- package/clis/xiaohongshu/_shared/comment-state.js +1 -0
- package/clis/xiaohongshu/_shared/publish-discovery.js +19 -0
- package/clis/xiaohongshu/_shared/publish-editors.js +1 -0
- package/clis/xiaohongshu/_shared/publish-settings.js +1 -0
- package/clis/xiaohongshu/_shared/topics.js +21 -0
- package/clis/xiaohongshu/activities.js +1 -0
- package/clis/xiaohongshu/collections.js +1 -0
- package/clis/xiaohongshu/comment-like.js +1 -0
- package/clis/xiaohongshu/comment.js +1 -0
- package/clis/xiaohongshu/comments.js +33 -5
- package/clis/xiaohongshu/groups.js +1 -0
- package/clis/xiaohongshu/location.js +1 -0
- package/clis/xiaohongshu/publish-video.js +10 -28
- package/clis/xiaohongshu/publish.js +43 -165
- package/clis/xiaohongshu/replies.js +1 -0
- package/clis/xiaohongshu/reply.js +1 -0
- package/clis/xiaohongshu/topics.js +1 -0
- package/dist/src/cli.js +27 -25
- package/package.json +1 -1
- package/skills/opencli-browser/SKILL.md +14 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as h from"node:fs";import*as s from"node:path";import{ArgumentError as l,CommandExecutionError as f}from"@jackwener/opencli/errors";import{clickControl as u,fillControl as m,verifyControl as c}from"../../_shared/publish-controls.js";export function readChapters(t){if(!t)return;let r;try{r=JSON.parse(h.readFileSync(s.resolve(t),"utf8"))}catch(e){throw new l(`无法读取 --chapters JSON 文件:${e.message}`)}if(!Array.isArray(r)||!r.length)throw new l("--chapters 应为非空数组 [{time:秒数,title:章节名,description:描述}]");let n=-1;for(const e of r){if(!e||!Number.isInteger(e.time)||e.time<=n||typeof e.title!=="string"||!e.title.trim()||[...e.title].length>11||e.description!==void 0&&(typeof e.description!=="string"||[...e.description].length>100))throw new l("章节需按非负整数秒严格递增,名称1至11字,描述最多100字");n=e.time}return r}export async function setXhsChapters(t,r,n){await u(t,".entry-right .add-chapter-btn");await c(t,"!!document.querySelector('.creator-chapter-content .summary-textarea')","章节编辑器");const e=await t.evaluate("document.querySelector('.creator-chapter-content video')?.duration");if(!Number.isFinite(e)||r?.some((i)=>i.time>=e))throw new l("章节时间必须小于视频总时长");if(r&&await t.evaluate("document.querySelectorAll('.creator-chapter-content .chapter-item').length > 0")){await u(t,".creator-chapter-content .chapter-list-header button","清空");await c(t,"document.querySelectorAll('.creator-chapter-content .chapter-item').length===0","清空旧章节")}if(n!==void 0)await m(t,".creator-chapter-content .summary-textarea",n);for(let i=0;i<(r?.length||0);i++){const o=r[i];await t.evaluate(`((time)=>{const video=document.querySelector('.creator-chapter-content video');video.pause();video.currentTime=time;})(${o.time})`);await t.wait({time:0.3});await u(t,".creator-chapter-content button","添加章节");await c(t,`document.querySelectorAll('.creator-chapter-content .chapter-item').length===${i+1}`,"新增章节");const a=`.creator-chapter-content .chapter-item:nth-child(${i+1})`;await c(t,`Number(document.querySelector(${JSON.stringify(a+" .minutes-field")})?.value) * 60 + Number(document.querySelector(${JSON.stringify(a+" .time-field:not(.minutes-field)")})?.value) === ${o.time}`,"章节时间");await m(t,a+" .title-field",o.title);if(o.description!==void 0)await m(t,a+" .description-field",o.description)}await u(t,".creator-chapter-content button","保存章节");await c(t,"!document.querySelector('.creator-chapter-content')","章节保存");const d=await t.evaluate("document.querySelector('.add-chapter-title')?.parentElement?.parentElement?.textContent || ''");if(r?.length&&!d.includes(String(r.length)))throw new f("章节数未正确回显;已停止提交")}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readChapters as v,setXhsChapters as x}from"./publish-editors.js";import*as s from"node:fs";import*as a from"node:path";import{setInputFiles as f}from"../../_shared/file-inject.js";import{ArgumentError as l,CommandExecutionError as m}from"@jackwener/opencli/errors";import{clickControl as c,control as w,fillControl as u,verifyControl as r,publicationTime as q,resolveDeclaration as b}from"../../_shared/publish-controls.js";async function y(t,e){const o=await w(t,".d-select",e);await t.evaluate(`(() => {const el=document.querySelectorAll('.d-select')[${o.nth}].closest('.d-select-wrapper'); el.focus();return true;})()`);await t.wait({time:0.2})}async function h(t,e,o){const n=await w(t,e,o);await t.evaluate(`(() => {document.querySelectorAll(${JSON.stringify(e)})[${n.nth}].click();return true;})()`);await t.wait({time:0.2})}export const XHS_DECLARATIONS={ai:"笔记含AI合成内容",fiction:"虚构演绎,仅供娱乐",marketing:"内容包含营销广告",reprint:"来源转载","self-shot":"自主拍摄"};const d={public:"公开可见",private:"仅自己可见",friends:"仅互关好友可见",include:"只给谁看",exclude:"不给谁看"};export const XHS_PUBLISH_ARGS=[{name:"declaration",choices:Object.keys(XHS_DECLARATIONS),help:"内容声明:ai / fiction / marketing / reprint / self-shot"},{name:"source",help:"转载来源媒体名称(declaration=reprint 必填)"},{name:"shot-location",help:"自主拍摄地点(使用平台候选地点的完整名称)"},{name:"shot-date",help:"自主拍摄日期 YYYY-MM-DD"},{name:"original",type:"boolean",help:"声明原创;true 同意平台原创声明须知,false 取消"},{name:"visibility",choices:Object.keys(d),help:"可见范围;include/exclude 需同时指定 --audience"},{name:"audience",help:"定向可见用户昵称,逗号分隔(最多200人,须在平台可选列表中)"},{name:"schedule",help:"北京时间 YYYY-MM-DD HH:mm / 带时区 ISO8601 / Unix秒;1小时至14天后"},{name:"allow-duet",type:"boolean",help:"允许合拍(true/false,不传保留平台默认)"},{name:"allow-copy",type:"boolean",help:"允许正文复制(true/false,不传保留平台默认)"},{name:"location",help:"地点完整名称,按平台搜索结果精确选择"},{name:"location-address",help:"地点完整地址,使用 xiaohongshu location 返回的地址区分重名"},{name:"activity",help:"关联活动完整名称(平台会自动添加活动话题)"},{name:"quote-note",help:"引用笔记链接(小红书完整链接,包含访问所需参数)"},{name:"quote-title",help:"引用自己已发布笔记的完整标题,与 --quote-note 二选一"},{name:"attachment",help:"文件附件路径:pdf/doc/docx/ppt/pptx,最多100MB"}],XHS_VIDEO_ARGS=[...XHS_PUBLISH_ARGS.filter((t)=>!["allow-duet","allow-copy","attachment"].includes(t.name)),{name:"chapters",help:"章节 JSON 文件路径:[{time:整数秒,title:章节名,description:描述}],视频须至少15秒"},{name:"summary",help:"视频内容总结(最多100字)"},{name:"cover",help:"自定义视频封面图片路径(jpg/png/webp)"},{name:"cover-ratio",choices:["original","1:1","3:4","4:3"],help:"封面裁剪比例,需同时提供 --cover"}];export function validateXhsSettings(t){if(t["location-address"]&&!t.location)throw new l("--location-address 需要 --location");const e=v(t.chapters);if(t.summary!==void 0&&[...String(t.summary)].length>100)throw new l("--summary 最多100字");if(t["cover-ratio"]&&!t.cover)throw new l("--cover-ratio 需要 --cover");if(t.cover){const i=a.resolve(t.cover);if(!s.existsSync(i)||!s.statSync(i).isFile()||![".jpg",".jpeg",".png",".webp"].includes(a.extname(i).toLowerCase()))throw new l("封面需为存在的 jpg/png/webp 文件")}if(t["quote-note"]&&t["quote-title"])throw new l("--quote-note 与 --quote-title 二选一");if(t["quote-note"]){let i;try{i=new URL(t["quote-note"])}catch{throw new l("--quote-note 需要完整的小红书笔记链接")}if(i.protocol!=="https:"||!["xiaohongshu.com","www.xiaohongshu.com","xhslink.com"].includes(i.hostname))throw new l("--quote-note 需要 HTTPS 小红书笔记链接")}if(t.attachment){const i=a.resolve(t.attachment);if(!s.existsSync(i)||!s.statSync(i).isFile())throw new l("附件文件不存在: "+i);if(![".pdf",".doc",".docx",".ppt",".pptx"].includes(a.extname(i).toLowerCase())||s.statSync(i).size>104857600)throw new l("附件需为100MB以内的 pdf/doc/docx/ppt/pptx")}const o=b(t,XHS_DECLARATIONS);if(o==="reprint"&&!String(t.source||"").trim())throw new l("--declaration reprint 需要 --source 媒体名称");if(t.source&&o!=="reprint")throw new l("--source 仅用于 --declaration reprint");if(o==="reprint"&&t.original)throw new l("转载来源声明不能与 --original true 同时使用");if(o==="self-shot"&&(!t["shot-location"]||!/^\d{4}-\d{2}-\d{2}$/.test(t["shot-date"]||"")))throw new l("自主拍摄需 --shot-location 和 --shot-date YYYY-MM-DD");if((t["shot-date"]||t["shot-location"])&&o!=="self-shot")throw new l("拍摄地点和日期仅用于 --declaration self-shot");if(t.visibility!==void 0&&!Object.hasOwn(d,t.visibility))throw new l("不支持的 --visibility");const n=String(t.audience||"").split(",").map((i)=>i.trim()).filter(Boolean);if(["include","exclude"].includes(t.visibility)&&(!n.length||n.length>200||new Set(n).size!==n.length))throw new l("定向可见需要1至200个不重复的 --audience 用户");if(n.length&&!["include","exclude"].includes(t.visibility))throw new l("--audience 仅用于 include/exclude 可见范围");return{declaration:o,schedule:q(t.schedule,1),audience:n,chapters:e}}async function p(t,e,o){return t.evaluate(`([...document.querySelectorAll(${JSON.stringify(e)})].filter(e=>e.getClientRects().length && e.textContent.trim()===${JSON.stringify(o)}).length === 1)`)}export async function xhsSwitch(t,e,o){const i=await(()=>t.evaluate(`((label)=>{const rows=[...document.querySelectorAll('.custom-switch-card')].filter(e=>e.querySelector('.custom-switch-text-content > span')?.textContent.trim()===label);if(rows.length!==1)return null; const input=rows[0].querySelector('input[type="checkbox"]');return input ? {checked:input.checked,nth:[...document.querySelectorAll('.custom-switch-card .d-switch')].indexOf(rows[0].querySelector('.d-switch'))}:null;})(${JSON.stringify(e)})`))();if(!i||i.nth<0)throw new m("未找到发布设置开关:"+e+";已停止提交");if(i?.checked===o)return;await t.click(".custom-switch-card .d-switch",{nth:i.nth});await t.wait({time:0.2});if(e==="原创声明"&&o&&await t.evaluate("!!document.querySelector('.originalContainer')")){await c(t,".originalContainer .d-checkbox");await c(t,".originalContainer button","声明原创")}await r(t,`((label,value)=>{const rows=[...document.querySelectorAll('.custom-switch-card')].filter(e=>e.querySelector('.custom-switch-text-content > span')?.textContent.trim()===label);return rows.length===1 && rows[0].querySelector('input[type="checkbox"]')?.checked===value;})(${JSON.stringify(e)},${o})`,e)}async function C(t,e,o){const n=XHS_DECLARATIONS[o],i=await p(t,".d-select-description",n);if(i&&!["reprint","self-shot"].includes(o))return;await c(t,".d-select",i?n:"添加内容类型声明");if(o==="reprint"||o==="self-shot")await c(t,".declaration-drop-down .d-option-name","内容来源声明");await c(t,".d-popover .d-option-content",n);if(o==="reprint"){await u(t,'input[placeholder="请输入媒体名称"]',e.source);await c(t,".self-shooting-contain button","确认")}if(o==="self-shot"){await u(t,'input[placeholder="下拉选择地点"]',e["shot-location"],!1);await r(t,`!![...document.querySelectorAll('.el-autocomplete-suggestion .name')].find(e=>e.textContent.trim()===${JSON.stringify(e["shot-location"])})`,"拍摄地点候选");await c(t,".el-autocomplete-suggestion .name",e["shot-location"],!1);await u(t,".self-shooting-contain .d-datepicker-input-filter input",e["shot-date"]);await c(t,".self-shooting-contain button","确认")}await r(t,`([...document.querySelectorAll('.d-select-description')].filter(e=>e.textContent.trim()===${JSON.stringify(n)}).length===1) && !document.querySelector('.self-shooting-contain')`,n)}export async function setXhsPublishSettings(t,e){const o=validateXhsSettings(e);if(e.original!==void 0)await xhsSwitch(t,"原创声明",e.original);if(o.declaration)await C(t,e,o.declaration);if(e["allow-duet"]!==void 0)await xhsSwitch(t,"允许合拍",e["allow-duet"]);if(e["allow-copy"]!==void 0)await xhsSwitch(t,"允许正文复制",e["allow-copy"]);if(e.visibility){const n=d[e.visibility];if(!await p(t,".d-select-description",n)){const i=await t.evaluate(`([...document.querySelectorAll('.d-select-description')].filter(e=>${JSON.stringify(Object.values(d))}.includes(e.textContent.trim())).map(e=>e.textContent.trim()))`);if(i.length!==1)throw new m("无法确定当前可见范围;已停止提交");await y(t,i[0]);await h(t,".custom-option .name",n)}if(o.audience.length){for(const i of o.audience){await u(t,'.d-modal input[placeholder="搜索用户"]',i,!1);await r(t,`!![...document.querySelectorAll('.d-modal .left .user-list *')].find(e=>!e.children.length&&e.textContent.trim()===${JSON.stringify(i)})`,"可选用户 "+i);await c(t,".d-modal .left .user-list .user-name",i)}await c(t,".d-modal .confirm-btn","确认")}await r(t,`([...document.querySelectorAll('.d-select-description')].filter(e=>e.textContent.trim()===${JSON.stringify(n)}).length===1)`,"可见范围")}if(o.schedule){await xhsSwitch(t,"定时发布",!0);await u(t,".d-datepicker-input-filter input",o.schedule)}if(e.location){await y(t,"添加地点");await u(t,".d-select.focus input",e.location,!1);const n=`(({name,address})=>{const all=[...document.querySelectorAll('.d-popover .option-name')];const hits=all.filter(e=>e.getClientRects().length && e.textContent.trim()===name && (!address || e.parentElement.querySelector('.option-subname')?.textContent.trim()===address));return hits.length===1 ? all.indexOf(hits[0]) : -1;})(${JSON.stringify({name:e.location,address:e["location-address"]})})`;await r(t,`(${n})>=0`,"唯一地点(重名时指定 --location-address)");const i=await t.evaluate(n);if(i<0)throw new m("地点候选已变化,停止提交");await t.evaluate(`document.querySelectorAll('.d-popover .option-name')[${i}].click()`);await r(t,`!![...document.querySelectorAll('.d-select-description')].find(e=>e.textContent.trim()===${JSON.stringify(e.location)})`,"地点")}if(e["quote-note"]||e["quote-title"]){await c(t,".setting-card-text","引用笔记");if(e["quote-note"]){await c(t,".select-note-modal__tab","他人笔记");await u(t,'input[placeholder="请粘贴笔记链接 http://..."]',e["quote-note"],!1)}const n=e["quote-title"];await r(t,`([...document.querySelectorAll('.select-note-modal .note-card__title')].filter(e=>e.getClientRects().length ${n?"&& e.textContent.trim()==="+JSON.stringify(n):""}).length===1)`,"待引用笔记");const i=await w(t,".select-note-modal .note-card__title",n),S=await t.evaluate(`document.querySelectorAll('.select-note-modal .note-card__title')[${i.nth}].textContent.trim()`);await t.click(i.selector,{nth:i.nth});await c(t,".select-note-modal button","确认引用");await r(t,`!document.querySelector('.select-note-modal') && document.body.innerText.includes(${JSON.stringify("《")} + ${JSON.stringify(S)} + '》')`,"引用笔记")}if(e.activity){await h(t,".more","更多");const n=`((name)=>{const cards=[...document.querySelectorAll('.activity-card-drawer')].filter(e=>e.querySelector('.activity-name')?.textContent.trim()===name);return cards.length===1 ? {nth:[...document.querySelectorAll('.activity-card-drawer .activity-action')].indexOf(cards[0].querySelector('.activity-action')),selected:cards[0].querySelector('.activity-action')?.textContent.trim()==='取消关联'} : null;})(${JSON.stringify(e.activity)})`;await r(t,`!!(${n})`,"活动 "+e.activity);const i=await t.evaluate(n);if(!i.selected)await t.evaluate(`document.querySelectorAll('.activity-card-drawer .activity-action')[${i.nth}].click()`);await r(t,`(${n})?.selected === true`,"关联活动");await h(t,".d-drawer-close")}if(e.attachment){await f(t,'.file-relation-container input[type="file"]',[a.resolve(e.attachment)]);await r(t,`document.querySelector('.file-relation-container .setting-card-text')?.textContent.trim() === ${JSON.stringify(a.basename(e.attachment))} && !document.querySelector('.file-relation-container')?.textContent.includes('上传中')`,"附件上传",60000)}if(o.chapters||e.summary!==void 0)await x(t,o.chapters,e.summary);if(e.cover){const n=await t.evaluate("document.querySelector('.cover-plugin-preview > .cover > .default')?.style.backgroundImage");await c(t,".cover-plugin-preview > .cover > .default");await c(t,".cover-plugin-preview .cover-edit-entry");await c(t,".d-modal .d-tabs-header-label","上传封面");await f(t,'.d-modal input[type="file"]',[a.resolve(e.cover)]);await r(t,"!!document.querySelector('#upload-cover-containner .cropper-canvas img')?.getAttribute('src')","封面图片载入",30000);if(e["cover-ratio"]){const i=e["cover-ratio"]==="original"?"原始":e["cover-ratio"];await c(t,".d-modal .crop-ratio-item",i);await r(t,`document.querySelector('.d-modal .crop-ratio-item-active')?.textContent.trim()===${JSON.stringify(i)}`,"封面裁剪比例")}await c(t,".d-modal button","确定");await r(t,`!document.querySelector('#upload-cover-containner') && document.querySelector('.cover-plugin-preview')?.getAttribute('aria-busy')==='false' && document.querySelector('.cover-plugin-preview > .cover > .default')?.style.backgroundImage !== ${JSON.stringify(n)}`,"视频封面上传",60000)}}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import{ArgumentError as s,CommandExecutionError as r}from"@jackwener/opencli/errors";import{humanType as l}from"../human.js";export const TOPIC_ID_ARG={name:"topic-ids",help:"topics 查询返回的 ID,逗号分隔,与 --topics 一一对应;防止同名话题选错"};export function parseTopics(e){const i=e["topic-ids"]?String(e["topic-ids"]).split(",").map((t)=>t.trim()):[],n=e.topics?String(e.topics).split(",").map((t)=>(i.length?t:t.trim()).replace(/^#/,"")):[];if(n.some((t)=>!t.trim())||n.length>10)throw new s("--topics 需为 1–10 个非空话题");if(i.length&&(i.length!==n.length||i.some((t)=>!/^[a-f\d]{24}$/i.test(t))))throw new s("--topic-ids 需为与 --topics 一一对应的 24 位话题 ID");return n.map((t,a)=>({name:t,id:i[a]||null}))}export async function attachTopics(e,i,n){for(const t of n){const a=await e.evaluate(`((selectors) => {
|
|
2
|
+
const editor = selectors.map(s => document.querySelector(s)).find(e => e && e.offsetParent !== null && e.isContentEditable);
|
|
3
|
+
if (!editor) return null;
|
|
4
|
+
editor.focus(); const range = document.createRange(); range.selectNodeContents(editor); range.collapse(false);
|
|
5
|
+
const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range);
|
|
6
|
+
return [...editor.querySelectorAll('a[data-topic]')].map(e => JSON.parse(e.getAttribute('data-topic')));
|
|
7
|
+
})(${JSON.stringify(i)})`);if(a===null)throw new r("未找到话题正文编辑器");await l(e,` #${t.name}`);let o=null;for(let c=0;c<12;c++){await e.wait({time:0.5});o=await e.evaluate(`((topic) => {
|
|
8
|
+
const items = [...document.querySelectorAll('#creator-editor-topic-container .item')].filter(e => e.offsetParent !== null);
|
|
9
|
+
const candidates = items.map(el => ({ el, name: el.querySelector('.name')?.textContent.replace(/^#/, ''), id: JSON.parse(el.getAttribute('data-impression') || '{}').tagTarget?.value?.tagId }));
|
|
10
|
+
const matches = candidates.filter(c => topic.id ? c.id === topic.id : c.name === topic.name);
|
|
11
|
+
if (matches.length !== 1) return { count: matches.length };
|
|
12
|
+
const match = matches[0];
|
|
13
|
+
if (!match.id) return { count: 1, error: '候选缺少话题 ID' };
|
|
14
|
+
match.el.click();
|
|
15
|
+
return { count: 1, id: match.id, name: match.name };
|
|
16
|
+
})(${JSON.stringify(t)})`);if(o?.id||o?.error||o?.count>1)break}if(!o?.id)throw new r(`话题「${t.name}」没有唯一可选候选。请先运行 xiaohongshu topics 查询并传入 topic-ids;未提交笔记。`);await e.wait({time:0.3});if(!await e.evaluate(`((cfg) => {
|
|
17
|
+
const editor = cfg.selectors.map(s => document.querySelector(s)).find(e => e && e.offsetParent !== null && e.isContentEditable);
|
|
18
|
+
const entities = [...(editor?.querySelectorAll('a[data-topic]') || [])].map(e => JSON.parse(e.getAttribute('data-topic')));
|
|
19
|
+
const matches = list => list.filter(t => t.id === cfg.id && t.name === cfg.name).length;
|
|
20
|
+
return matches(entities) > matches(cfg.before);
|
|
21
|
+
})(${JSON.stringify({selectors:i,before:a,id:o.id,name:o.name})})`))throw new r(`话题「${t.name}」未生成所选 ID 的真实实体,已停止提交`)}return n.map((t)=>t.name)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as o,Strategy as n}from"@jackwener/opencli/registry";import{CommandExecutionError as c}from"@jackwener/opencli/errors";import{prepareCreatorDiscovery as m,creatorDiscoveryRequest as d,discoveryLimit as l,listField as y,activityQuery as u}from"./_shared/publish-discovery.js";o({site:"xiaohongshu",name:"activities",access:"read",domain:"creator.xiaohongshu.com",description:"查看/筛选当前可关联活动及参与说明、截止时间、所需话题和发布参数",strategy:n.COOKIE,navigateBefore:!1,args:[{name:"query",help:"在当前完整候选列表中按名称或活动说明筛选"},{name:"limit",type:"int",default:20}],columns:["id","name","description","starts_at","ends_at","topics","details_url","publish_args"],func:async(i,e)=>{const r=l(e.limit);await m(i);const a=await d(i,"activities",u),s=String(e.query||"").toLowerCase();return y(a,"activityList").filter((t)=>[t.activityName,t.activityReward].join(" ").toLowerCase().includes(s)).slice(0,r).map((t)=>{if(!t.activityId||!t.activityName)throw new c("活动缺少 ID 或名称");return{id:String(t.activityId),name:t.activityName,description:t.activityReward||"",starts_at:t.startTime?new Date(t.startTime).toISOString():null,ends_at:t.endTime?new Date(t.endTime).toISOString():null,topics:t.topicInfos||[],details_url:t.activityLink||"",publish_args:{activity:t.activityName}}})}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CommandExecutionError as s}from"@jackwener/opencli/errors";import{cli as a,Strategy as i}from"@jackwener/opencli/registry";import{prepareCreatorDiscovery as n,creatorDiscoveryRequest as c,listField as m}from"./_shared/publish-discovery.js";a({site:"xiaohongshu",name:"collections",access:"read",domain:"creator.xiaohongshu.com",strategy:i.COOKIE,navigateBefore:!1,description:"查看当前账号图文/视频合集候选(普通合集);当前尚未接入发布选择,不能把 ID 直接作为发布参数",args:[{name:"cursor",default:"",help:"上一页的 next_cursor;按平台完整页返回,不截断候选"}],columns:["id","name","type","next_cursor","has_more","publish_supported"],func:async(o,t)=>{await n(o);const e=await c(o,"collections",{cursor:t.cursor||"",needTypeList:[0],targetUid:""});return m(e,"collectionInfoList").map((r)=>{if(!r.id||!r.name)throw new s("合集缺少 ID 或名称");return{id:String(r.id),name:r.name,type:r.type,next_cursor:e.nextCursor||"",has_more:Boolean(e.hasMore),publish_supported:!1}})}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as e,Strategy as t}from"@jackwener/opencli/registry";import{runCommentLike as o,writeArgs as i}from"./_shared/comment-actions.js";e({site:"xiaohongshu",name:"comment-like",access:"write",description:"点赞/取消点赞小红书评论(含楼中楼);默认 dry-run,--execute 才操作",domain:"www.xiaohongshu.com",strategy:t.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"note-id",positional:!0,required:!0,help:"Full signed note URL with xsec_token"},{name:"comment-id",positional:!0,required:!0,help:"Exact comment ID from comments/replies"},{name:"undo",type:"boolean",default:!1,help:"Ensure the comment is not liked"},...i],columns:["status","note_id","id","root_id","liked","url"],func:o});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as e,Strategy as t}from"@jackwener/opencli/registry";import{runComment as o,writeArgs as r,writeColumns as n}from"./_shared/comment-actions.js";e({site:"xiaohongshu",name:"comment",access:"write",description:"发布小红书评论或定向回复;默认验证草稿,--execute 才发送",domain:"www.xiaohongshu.com",strategy:t.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"note-id",positional:!0,required:!0,help:"Full signed note URL with xsec_token"},{name:"text",positional:!0,required:!0,help:"Exact plain-text comment"},{name:"reply-to",help:"Exact comment ID to reply to, including a nested reply"},...r],columns:n,func:o});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{cli as h,Strategy as d}from"@jackwener/opencli/registry";import{AuthRequiredError as y,CommandExecutionError as a,EmptyResultError as g}from"@jackwener/opencli/errors";import{parseNoteId as w,buildNoteUrl as x}from"./note-helpers.js";import{readXhsDetailPage as b}from"./risk-control.js";import{readCommentPage as _}from"./_shared/comment-state.js";const k='.author-wrapper a[href*="/user/profile/"], a.name[href*="/user/profile/"], a.user-name[href*="/user/profile/"], a[href*="/user/profile/"]';export function parseXhsProfileHref(r,t="www.xiaohongshu.com"){const e=typeof r==="string"?r.trim():"";if(!e)return"";const n=String(t||"www.xiaohongshu.com").toLowerCase();let s;try{s=new URL(e,`https://${n}`)}catch{return""}if(s.protocol!=="https:")return"";if(s.hostname.toLowerCase()!==n)return"";return s.pathname.match(/^\/user\/profile\/([a-zA-Z0-9]+)\/?$/)?.[1]??""}export function buildXhsProfileUrl(r,t="www.xiaohongshu.com"){const e=parseXhsProfileHref(r,t);if(!e)return"";return`https://${t}/user/profile/${e}`}export function parseCommentLimit(r,t=20){const e=Number(r);if(!Number.isFinite(e))return t;return Math.max(1,Math.min(Math.floor(e),50))}export function parseXhsLikeCountText(r){const t=/^(?:\d+|\d{1,3}(?:[,,]\d{3})+)\+?$/u,e=/^((?:\d+|\d{1,3}(?:[,,]\d{3})+)(?:\.\d+)?)([wWkK万千])\+?$/u,n=String(r??"").replace(/\s+/g,"");if(!n)return 0;if(t.test(n))return Number(n.replace(/[,+,]/g,""));const s=n.match(e);if(!s)return 0;const i=Number(s[1].replace(/[,,]/g,""));if(!Number.isFinite(i))return 0;const l=s[2].toLowerCase();return Math.round(i*(l==="w"||l==="万"?1e4:1000))}function c(r,t,e){if(r==null)return"";if(typeof r!=="string")throw new a(`${e}: malformed comment row ${t}`);return r}export function normalizeCommentImages(r,t){if(r==null)return[];if(!Array.isArray(r))throw new a(`${t}: malformed comment row images`);const e=[];for(const n of r){if(typeof n!=="string")throw new a(`${t}: malformed comment row image URL`);const s=n.trim();let i;try{i=new URL(s)}catch{throw new a(`${t}: malformed comment row image URL`)}if(i.protocol!=="https:"&&i.protocol!=="http:"||i.username||i.password)throw new a(`${t}: malformed comment row image URL`);const l=i.toString();if(!e.includes(l))e.push(l)}return e}export function normalizeCommentRows(r,t="xiaohongshu/comments"){if(r==null)return[];if(!Array.isArray(r))throw new a(`${t}: malformed comments payload`);return r.map((e,n)=>{if(!e||typeof e!=="object"||Array.isArray(e))throw new a(`${t}: malformed comment row at index ${n}`);const s=c(e.text,"text",t);if(!s)throw new a(`${t}: malformed comment row text`);const i=Number(e.likes);if(!Number.isInteger(i)||i<0)throw new a(`${t}: malformed comment row likes`);if(typeof e.is_reply!=="boolean")throw new a(`${t}: malformed comment row is_reply`);return{id:e.id??null,root_id:e.root_id??null,parent_id:e.parent_id??null,liked:e.liked??null,reply_count:e.reply_count??null,replies_complete:e.replies_complete??null,author:c(e.author,"author",t),authorHrefRaw:c(e.authorHrefRaw,"authorHrefRaw",t),text:s,likes:i,time:c(e.time,"time",t),is_reply:e.is_reply,reply_to:c(e.reply_to,"reply_to",t),images:normalizeCommentImages(e.images,t)}})}export function buildCommentsExtractJs(r,t=20,e=null){const n=parseXhsLikeCountText.toString();return`
|
|
2
2
|
(async () => {
|
|
3
3
|
const wait = (ms) => new Promise(r => setTimeout(r, ms))
|
|
4
4
|
const withReplies = ${r}
|
|
@@ -48,11 +48,11 @@ import{cli as p,Strategy as d}from"@jackwener/opencli/registry";import{AuthRequi
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const clean = (el) => (el?.textContent || '').replace(/\\s+/g, ' ').trim()
|
|
51
|
-
const parseLikeCountText = ${
|
|
51
|
+
const parseLikeCountText = ${n}
|
|
52
52
|
const parseLikes = (el) => {
|
|
53
53
|
return parseLikeCountText(clean(el))
|
|
54
54
|
}
|
|
55
|
-
const HREF_SELECTOR = ${JSON.stringify(
|
|
55
|
+
const HREF_SELECTOR = ${JSON.stringify(k)}
|
|
56
56
|
const extractAuthorHref = (el) => {
|
|
57
57
|
if (!el) return ''
|
|
58
58
|
const anchor = el.querySelector(HREF_SELECTOR)
|
|
@@ -75,6 +75,21 @@ import{cli as p,Strategy as d}from"@jackwener/opencli/registry";import{AuthRequi
|
|
|
75
75
|
}
|
|
76
76
|
const expandReplyThreads = async (root) => {
|
|
77
77
|
if (!withReplies || !root) return
|
|
78
|
+
if (${JSON.stringify(e)} !== null) {
|
|
79
|
+
for (let round = 0; round < 20; round++) {
|
|
80
|
+
const el = root.querySelector('.show-more')
|
|
81
|
+
if (!el) break
|
|
82
|
+
const before = root.querySelectorAll('.comment-item-sub').length
|
|
83
|
+
el.click()
|
|
84
|
+
let progressed = false
|
|
85
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
86
|
+
await wait(300)
|
|
87
|
+
if (root.querySelectorAll('.comment-item-sub').length > before) { progressed = true; break }
|
|
88
|
+
}
|
|
89
|
+
if (!progressed) break
|
|
90
|
+
}
|
|
91
|
+
return
|
|
92
|
+
}
|
|
78
93
|
const clickedTexts = new Set()
|
|
79
94
|
for (let round = 0; round < 3; round++) {
|
|
80
95
|
const expanders = Array.from(root.querySelectorAll('button, [role="button"], span, div')).filter(el => {
|
|
@@ -96,7 +111,8 @@ import{cli as p,Strategy as d}from"@jackwener/opencli/registry";import{AuthRequi
|
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
const results = []
|
|
99
|
-
const
|
|
114
|
+
const allParents = Array.from(document.querySelectorAll('.parent-comment'))
|
|
115
|
+
const parents = ${JSON.stringify(e)} === null ? allParents : allParents.slice(0, targetCount)
|
|
100
116
|
for (const p of parents) {
|
|
101
117
|
const item = p.querySelector('.comment-item')
|
|
102
118
|
if (!item) continue
|
|
@@ -110,6 +126,7 @@ import{cli as p,Strategy as d}from"@jackwener/opencli/registry";import{AuthRequi
|
|
|
110
126
|
|
|
111
127
|
if (!text) continue
|
|
112
128
|
results.push({ author, authorHrefRaw, text, likes, time, is_reply: false, reply_to: '', images })
|
|
129
|
+
if (${JSON.stringify(e)} !== null) results[results.length - 1].id = item.id.replace(/^comment-/, '')
|
|
113
130
|
|
|
114
131
|
// Extract nested replies (楼中楼)
|
|
115
132
|
if (withReplies) {
|
|
@@ -123,10 +140,21 @@ import{cli as p,Strategy as d}from"@jackwener/opencli/registry";import{AuthRequi
|
|
|
123
140
|
const sImages = extractImages(sub)
|
|
124
141
|
if (!sText) return
|
|
125
142
|
results.push({ author: sAuthor, authorHrefRaw: sAuthorHrefRaw, text: sText, likes: sLikes, time: sTime, is_reply: true, reply_to: author, images: sImages })
|
|
143
|
+
if (${JSON.stringify(e)} !== null) results[results.length - 1].id = sub.id.replace(/^comment-/, '')
|
|
126
144
|
})
|
|
127
145
|
}
|
|
128
146
|
}
|
|
129
147
|
|
|
148
|
+
if (${JSON.stringify(e)} !== null && !loginWall && !securityBlock) {
|
|
149
|
+
const state = (${_.toString()})(${JSON.stringify(e)})
|
|
150
|
+
if (state.error) return { pageUrl: location.href, securityBlock, loginWall, identityError: state.error }
|
|
151
|
+
for (const row of results) {
|
|
152
|
+
const entry = state.rows.find(c => c.id === row.id)
|
|
153
|
+
if (!entry) return { pageUrl: location.href, securityBlock, loginWall, identityError: 'comment identity unavailable' }
|
|
154
|
+
Object.assign(row, { root_id: entry.root_id, parent_id: entry.parent_id, liked: entry.liked,
|
|
155
|
+
reply_count: entry.reply_count, replies_complete: entry.replies_complete, reply_to: entry.reply_to })
|
|
156
|
+
}
|
|
157
|
+
}
|
|
130
158
|
return { pageUrl: location.href, securityBlock, loginWall, results }
|
|
131
159
|
})()
|
|
132
|
-
`}export const command=
|
|
160
|
+
`}export const command=h({site:"xiaohongshu",name:"comments",access:"read",description:"获取小红书笔记评论(支持楼中楼子回复)",domain:"www.xiaohongshu.com",strategy:d.COOKIE,navigateBefore:!1,args:[{name:"note-id",required:!0,positional:!0,help:"Full Xiaohongshu note URL with xsec_token"},{name:"limit",type:"int",default:20,help:"Number of top-level comments (max 50)"},{name:"with-replies",type:"boolean",default:!1,help:"Include nested replies (楼中楼)"}],columns:["rank","id","root_id","parent_id","author","userId","profileUrl","text","likes","liked","time","is_reply","reply_to","reply_count","replies_complete","images"],func:async(r,t)=>{const e=parseCommentLimit(t.limit),n=Boolean(t["with-replies"]),s=String(t["note-id"]),i=w(s),l=await b(r,{url:x(s,{commandName:"xiaohongshu comments"}),extractJs:buildCommentsExtractJs(n,e,i),securityHelp:/^https?:\/\//.test(s)?"The page may be temporarily restricted. Try again later or from a different session.":"Try using a full URL from search results (with xsec_token) instead of a bare note ID."});if(!l||typeof l!=="object")throw new g("xiaohongshu/comments","Unexpected evaluate response");if(l.loginWall)throw new y("www.xiaohongshu.com","Note comments require login");if(l.identityError)throw new a(`xiaohongshu/comments: ${l.identityError}`);const m=normalizeCommentRows(l.results,"xiaohongshu/comments"),p=(o,u)=>({rank:u+1,id:o.id,root_id:o.root_id,parent_id:o.parent_id,author:o.author,userId:o.authorHrefRaw?parseXhsProfileHref(o.authorHrefRaw):"",profileUrl:o.authorHrefRaw?buildXhsProfileUrl(o.authorHrefRaw):"",text:o.text,likes:o.likes,liked:o.liked,time:o.time,is_reply:o.is_reply,reply_to:o.reply_to,reply_count:o.reply_count,replies_complete:o.replies_complete,images:o.images??[]});if(n){const o=[];let u=0;for(const f of m){if(!f.is_reply)u++;if(u>e)break;o.push(f)}return o.map(p)}return m.slice(0,e).map(p)}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CommandExecutionError as s}from"@jackwener/opencli/errors";import{cli as p,Strategy as d}from"@jackwener/opencli/registry";import{prepareCreatorDiscovery as m,creatorDiscoveryRequest as u,listField as i}from"./_shared/publish-discovery.js";p({site:"xiaohongshu",name:"groups",access:"read",domain:"creator.xiaohongshu.com",strategy:d.COOKIE,navigateBefore:!1,description:"查看当前账号创建/管理的群聊及平台可绑定状态;发布绑定尚未接入,不创建群聊",args:[],columns:["id","name","role","bindable","description","publish_supported"],func:async(o)=>{await m(o);const r=await u(o,"groups",{noteId:""}),a=i(r,"groupList"),n=i(r,"adminGroupList"),t=new Set(a.map((e)=>e.groupId));return[...a.map((e)=>({...e,role:"owner"})),...n.filter((e)=>!t.has(e.groupId)).map((e)=>({...e,role:"admin"}))].map((e)=>{if(!e.groupId||!e.groupName)throw new s("群聊缺少 ID 或名称");return{id:String(e.groupId),name:e.groupName,role:e.role,bindable:Boolean(e.linkable),description:e.linkable?e.desc||"":"平台提示存在生态问题,暂不支持绑定",publish_supported:!1}})}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as s,Strategy as n}from"@jackwener/opencli/registry";import{ArgumentError as d,CommandExecutionError as l}from"@jackwener/opencli/errors";import{prepareCreatorDiscovery as m,creatorDiscoveryRequest as u,discoveryLimit as c,listField as f}from"./_shared/publish-discovery.js";s({site:"xiaohongshu",name:"location",access:"read",domain:"creator.xiaohongshu.com",description:"搜索发布地点,返回完整地址以区分同名地点,以及可直接使用的发布参数",strategy:n.COOKIE,navigateBefore:!1,args:[{name:"query",positional:!0,required:!0,help:"地点关键词"},{name:"limit",type:"int",default:20}],columns:["poi_id","name","address","city","publish_args"],func:async(r,i)=>{const t=c(i.limit);if(!String(i.query||"").trim())throw new d("请输入地点关键词");await m(r);const a=await u(r,"location",{latitude:0,longitude:0,keyword:i.query,page:1,size:t,source:"WEB",type:3}),o=new Set;return f(a,"poiList").filter((e)=>{if(o.has(e.poiId))return!1;o.add(e.poiId);return!0}).slice(0,t).map((e)=>{if(!e.poiId||!e.name||!e.fullAddress)throw new l("地点缺少 ID、名称或完整地址");return{poi_id:String(e.poiId),name:e.name,address:e.fullAddress,city:e.cityName||"",publish_args:{location:e.name,"location-address":e.fullAddress}}})}});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import{TOPIC_ID_ARG as C,parseTopics as I,attachTopics as q}from"./_shared/topics.js";import{verifyControl as L}from"../_shared/publish-controls.js";import{XHS_VIDEO_ARGS as D,validateXhsSettings as R,setXhsPublishSettings as F}from"./_shared/publish-settings.js";import*as _ from"node:fs";import*as p from"node:path";import{cli as B,Strategy as U}from"@jackwener/opencli/registry";import{ArgumentError as s,CommandExecutionError as a}from"@jackwener/opencli/errors";import{assertLiteralContent as k}from"../_shared/content-guard.js";import{setInputFiles as V}from"../_shared/file-inject.js";const J="https://creator.xiaohongshu.com/publish/publish?from=homepage&target=video",y=20,x=1000,S=10,E=[".mp4",".mov",".flv",".f4v",".mkv",".rm",".rmvb",".m4v",".mpg",".mpeg",".ts"],T='input.upload-input, input[type="file"][accept*=".mp4"]',X='input[placeholder*="标题"]',A=['input[placeholder*="标题"]','input.d-text[placeholder*="标题"]'],b=".tiptap.ProseMirror",H=["_onPublish","onPublish","_onSubmit","_handlePublish"],j=["_onSave","_onSaveDraft","_onDraft"];function G(t){const e=p.resolve(t);if(!_.existsSync(e))throw new s(`视频文件不存在: ${e}`);const i=p.extname(e).toLowerCase();if(!E.includes(i))throw new s(`不支持的视频格式 "${i}",支持: ${E.join(" ")}`);return e}async function Y(t,e=15000){const i=500,n=Math.max(1,Math.ceil(e/i));for(let o=0;o<n;o++){if(await t.evaluate(`(() => !!document.querySelector(${JSON.stringify(T)}))()`))return!0;if(o<n-1)await t.wait({time:i/1000})}return!1}async function K(t,e){if(!await Y(t))return{ok:!1,error:"视频发布页未渲染出上传输入框(等待 15s 超时)"};try{await V(t,T,[e]);return{ok:!0}}catch(n){return{ok:!1,error:n instanceof Error?n.message:String(n)}}}async function z(t,e=600000){const i=2000,n=Math.ceil(e/i);for(let o=0;o<n;o++){if((await t.evaluate(`(() => {
|
|
2
2
|
const b = document.querySelector('xhs-publish-btn');
|
|
3
3
|
if (!b) return { ready: false, reason: 'no_button' };
|
|
4
4
|
return {
|
|
@@ -6,28 +6,7 @@ import*as b from"node:fs";import*as w from"node:path";import{cli as q,Strategy a
|
|
|
6
6
|
submitDisabled: b.getAttribute('submit-disabled'),
|
|
7
7
|
isPublish: b.getAttribute('is-publish'),
|
|
8
8
|
};
|
|
9
|
-
})()`))?.ready)return!0;await t.wait({time:i/1000})}return!1}async function
|
|
10
|
-
const el = document.querySelector(s);
|
|
11
|
-
if (!el) return false;
|
|
12
|
-
el.focus();
|
|
13
|
-
const sel = window.getSelection();
|
|
14
|
-
const range = document.createRange();
|
|
15
|
-
range.selectNodeContents(el);
|
|
16
|
-
range.collapse(false);
|
|
17
|
-
sel?.removeAllRanges();
|
|
18
|
-
sel?.addRange(range);
|
|
19
|
-
return true;
|
|
20
|
-
})(${JSON.stringify(f)})`)}async function K(t,e){const i=[];for(const n of e){await Y(t);await t.typeText(f,` #${n}`);let o=!1;for(let c=0;c<12;c++){await t.wait({time:0.5});o=await t.evaluate(`(() => { const c = document.querySelector(${JSON.stringify(N)}); if (!c || c.offsetParent === null) return false; return !!c.querySelector('.item'); })()`);if(o)break}if(!o)continue;if(await t.evaluate(`((cfg) => {
|
|
21
|
-
const c = document.querySelector(cfg.dropdown);
|
|
22
|
-
if (!c) return false;
|
|
23
|
-
const items = Array.from(c.querySelectorAll('.item')).filter((el) => el.offsetParent !== null);
|
|
24
|
-
if (!items.length) return false;
|
|
25
|
-
// 优先精确匹配话题名,否则取首项
|
|
26
|
-
const norm = (v) => (v || '').replace(/[#\\s]/g, '').trim();
|
|
27
|
-
const exact = items.find((el) => norm(el.innerText || el.textContent).includes(norm(cfg.topic)));
|
|
28
|
-
(exact || items[0]).click();
|
|
29
|
-
return true;
|
|
30
|
-
})(${JSON.stringify({dropdown:N,topic:n})})`)){i.push(n);await t.wait({time:0.5})}}return i}async function W(t,e){const i=e?["暂存离开","存草稿"]:["发布","发布笔记"];return t.evaluate(`
|
|
9
|
+
})()`))?.ready)return!0;await t.wait({time:i/1000})}return!1}async function Q(t,e=20000){const i=500,n=Math.ceil(e/i);for(let o=0;o<n;o++){if(await t.evaluate(`(() => ${JSON.stringify(A)}.some((s) => !!document.querySelector(s)) && !!document.querySelector(${JSON.stringify(b)}))()`))return!0;await t.wait({time:i/1000})}return!1}async function W(t,e){await t.typeText(X,e);await t.wait({time:0.3});const i=await t.evaluate(`((sels) => { const el = sels.map((s) => document.querySelector(s)).find(Boolean); return el ? (el.value || '') : ''; })(${JSON.stringify(A)})`);if(i.trim()!==e){await t.screenshot({path:"/tmp/xhs_video_title_debug.png"});throw new a(`填写标题失败,期望 "${e}",实际 "${i}"。截图: /tmp/xhs_video_title_debug.png`)}}async function Z(t,e){await t.typeText(b,e);await t.wait({time:0.5});const i=await t.evaluate(`((s) => { const el = document.querySelector(s); return el ? (el.innerText || el.textContent || '').replace(/\\u200b/g, '').trim() : ''; })(${JSON.stringify(b)})`),n=e.replace(/\s+/g,"").slice(0,20);if(n&&!i.replace(/\s+/g,"").includes(n)){await t.screenshot({path:"/tmp/xhs_video_body_debug.png"});throw new a(`正文写入校验失败,实际读到 "${i.slice(0,80)}"。截图: /tmp/xhs_video_body_debug.png`)}}async function tt(t,e){const i=e?["暂存离开","存草稿"]:["发布","发布笔记"];return t.evaluate(`
|
|
31
10
|
((cfg) => {
|
|
32
11
|
const { isDraftMode, publishNames, draftNames, labels } = cfg;
|
|
33
12
|
const isVisible = (el) => {
|
|
@@ -54,9 +33,12 @@ import*as b from"node:fs";import*as w from"node:path";import{cli as q,Strategy a
|
|
|
54
33
|
}
|
|
55
34
|
}
|
|
56
35
|
return { ok: false, via: 'none', hosts: hosts.length, lastMethodError };
|
|
57
|
-
})(${JSON.stringify({isDraftMode:e,publishNames:
|
|
58
|
-
`)}
|
|
59
|
-
|
|
36
|
+
})(${JSON.stringify({isDraftMode:e,publishNames:H,draftNames:j,labels:i})})
|
|
37
|
+
`)}B({site:"xiaohongshu",name:"publish-video",access:"write",description:"小红书发布视频笔记(creator center UI 自动化)",domain:"creator.xiaohongshu.com",strategy:U.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"content",required:!1,positional:!0,help:"笔记正文(字面文本,不展开 @文件 引用;长正文用 --file)"},{name:"file",required:!1,help:"从本机文件读取正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"笔记标题(≤20字)"},{name:"video",required:!0,help:"视频文件路径 (mp4/mov/mkv/...)"},{name:"topics",required:!1,help:"话题名称,逗号分隔,最多10个;先用 topics 查询。无法准确选中时停止提交"},C,{name:"draft",type:"bool",default:!1,help:"暂存为草稿,不直接发布"},{name:"ai-generated",type:"boolean",help:"声明内容由 AI 生成(发布和草稿均支持;设置失败时停止提交)"},...D],columns:["status","detail"],func:async(t,e)=>{R(e);if(!t)throw Error("Browser page required");const i=String(e.title??"").trim(),n=e.file?String(e.file).trim():"";let o=String(e.content??"").trim();if(n){if(o)throw new s("正文位置参数和 --file 只能二选一");const r=p.resolve(n);if(!_.existsSync(r))throw new s(`--file 指向的文件不存在: ${r}`);o=_.readFileSync(r,"utf-8").trim();if(!o)throw new s(`--file 指向的文件内容为空: ${r}`)}const c=I(e),h=c.map((r)=>r.name),l=Boolean(e.draft);if(!i)throw new s("--title 必填");if(i.length>y)throw new s(`标题 ${i.length} 字,需 ≤ ${y} 字`);if(!o)throw new s("缺少正文:给位置参数 <content>,或用 --file <本机文件路径>");k(o,{fileFlag:"--file"});if(o.length>x)throw new s(`正文 ${o.length} 字,需 ≤ ${x} 字`);if(!e.video)throw new s("--video 必填");if(h.length>S)throw new s(`话题过多: ${h.length}(最多 ${S} 个)`);const v=G(String(e.video));await t.goto(J);await t.evaluate(`(() => {
|
|
38
|
+
document.documentElement.setAttribute('data-ppcli-composer-reload', 'pending');
|
|
39
|
+
setTimeout(() => location.reload(), 100);
|
|
40
|
+
})()`);await L(t,`!document.documentElement.hasAttribute('data-ppcli-composer-reload') && !!document.querySelector('input[type="file"]')`,"新的视频发布表单",30000);let d="";for(let r=0;r<30;r++){await t.wait({time:0.5});d=await t.evaluate("() => location.href");if(d.includes("creator.xiaohongshu.com"))break}if(!d.includes("creator.xiaohongshu.com")){await t.screenshot({path:"/tmp/xhs_video_redirect_debug.png"});throw new a(`被重定向离开创作者中心(当前 ${d})——登录态可能已过期。`+"用 ppcli xiaohongshu creator-profile 重新登录。截图: /tmp/xhs_video_redirect_debug.png")}const g=await K(t,v);if(!g.ok){await t.screenshot({path:"/tmp/xhs_video_upload_debug.png"});throw new a(`视频上传失败: ${g.error??"unknown"}。截图: /tmp/xhs_video_upload_debug.png`)}if(!await Q(t)){await t.screenshot({path:"/tmp/xhs_video_form_debug.png"});throw new a("上传后编辑表单未就绪(标题/正文未出现)。截图: /tmp/xhs_video_form_debug.png")}process.stderr.write(` 等待视频转码(可发布状态)...
|
|
41
|
+
`);if(!await z(t)){await t.screenshot({path:"/tmp/xhs_video_transcode_debug.png"});throw new a("视频转码超时(10min),发布按钮未解禁。截图: /tmp/xhs_video_transcode_debug.png")}await W(t,i);await t.wait({time:0.4});await Z(t,o);await t.wait({time:0.5});let m=[];if(h.length){m=await q(t,[b],c);const r=h.filter((P)=>!m.includes(P));if(r.length){await t.screenshot({path:"/tmp/xhs_video_topic_debug.png"});throw new a(`话题未能生成实体: ${r.join(", ")}(联想下拉未出现或无匹配项)。截图: /tmp/xhs_video_topic_debug.png`)}}await F(t,e);const M=await t.evaluate("(() => { const b = document.querySelector('xhs-publish-btn'); return !!b && b.getAttribute('submit-disabled') === 'false'; })()");if(!l&&!M){await t.screenshot({path:"/tmp/xhs_video_prepublish_debug.png"});throw new a("发布前复查:发布按钮不可用(submit-disabled≠false)。截图: /tmp/xhs_video_prepublish_debug.png")}const f=await tt(t,l);if(!f?.ok){await t.screenshot({path:"/tmp/xhs_video_submit_debug.png"});throw new a(`找不到「${l?"暂存离开":"发布"}」触发方式(hosts=${f?.hosts??0}${f?.lastMethodError?`, lastMethodError=${f.lastMethodError}`:""})。截图: /tmp/xhs_video_submit_debug.png`)}await t.wait({time:4});let u=await t.evaluate("() => location.href");const N=l?["草稿已保存","暂存成功","保存成功","保存于"]:["发布成功","上传成功"],w=await t.evaluate(`
|
|
60
42
|
((markers) => {
|
|
61
43
|
for (const el of document.querySelectorAll('*')) {
|
|
62
44
|
if (el.tagName === 'STYLE' || el.tagName === 'SCRIPT') continue;
|
|
@@ -65,5 +47,5 @@ import*as b from"node:fs";import*as w from"node:path";import{cli as q,Strategy a
|
|
|
65
47
|
if (text && text.length <= 40 && markers.some((m) => text.includes(m))) return text;
|
|
66
48
|
}
|
|
67
49
|
return '';
|
|
68
|
-
})(${JSON.stringify(
|
|
69
|
-
`),
|
|
50
|
+
})(${JSON.stringify(N)})
|
|
51
|
+
`),O=!u.includes("/publish/publish");if(!w&&!O){await t.wait({time:3});u=await t.evaluate("() => location.href")}if(!(w.length>0||!u.includes("/publish/publish"))){await t.screenshot({path:"/tmp/xhs_video_verify_debug.png"});throw new a(`${l?"暂存":"发布"}结果无法确认:未见成功提示、也未跳离发布页。当前 URL: ${u}。截图: /tmp/xhs_video_verify_debug.png`)}return[{status:`✅ ${l?"暂存成功":"发布成功"}`,detail:[`"${i}"`,p.basename(v),m.length?`话题: ${m.map((r)=>`#${r}`).join(" ")}`:"",w||u].filter(Boolean).join(" · ")}]}});
|