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.
Files changed (40) hide show
  1. package/cli-manifest.json +1297 -13
  2. package/clis/_shared/publish-controls.js +11 -0
  3. package/clis/douyin/_shared/ai-declaration.js +1 -0
  4. package/clis/douyin/_shared/browser-fetch.js +31 -13
  5. package/clis/douyin/_shared/comments.js +1 -0
  6. package/clis/douyin/_shared/publish-settings.js +1 -0
  7. package/clis/douyin/activities.js +1 -1
  8. package/clis/douyin/collections.js +1 -1
  9. package/clis/douyin/comment-like.js +1 -0
  10. package/clis/douyin/comment.js +1 -0
  11. package/clis/douyin/comments.js +1 -0
  12. package/clis/douyin/draft.js +8 -8
  13. package/clis/douyin/hashtag.js +1 -1
  14. package/clis/douyin/location.js +1 -1
  15. package/clis/douyin/music-categories.js +1 -0
  16. package/clis/douyin/music.js +1 -0
  17. package/clis/douyin/publish-image.js +12 -12
  18. package/clis/douyin/publish.js +9 -8
  19. package/clis/xiaohongshu/_shared/ai-declaration.js +1 -0
  20. package/clis/xiaohongshu/_shared/comment-actions.js +9 -0
  21. package/clis/xiaohongshu/_shared/comment-state.js +1 -0
  22. package/clis/xiaohongshu/_shared/publish-discovery.js +19 -0
  23. package/clis/xiaohongshu/_shared/publish-editors.js +1 -0
  24. package/clis/xiaohongshu/_shared/publish-settings.js +1 -0
  25. package/clis/xiaohongshu/_shared/topics.js +21 -0
  26. package/clis/xiaohongshu/activities.js +1 -0
  27. package/clis/xiaohongshu/collections.js +1 -0
  28. package/clis/xiaohongshu/comment-like.js +1 -0
  29. package/clis/xiaohongshu/comment.js +1 -0
  30. package/clis/xiaohongshu/comments.js +33 -5
  31. package/clis/xiaohongshu/groups.js +1 -0
  32. package/clis/xiaohongshu/location.js +1 -0
  33. package/clis/xiaohongshu/publish-video.js +10 -28
  34. package/clis/xiaohongshu/publish.js +43 -165
  35. package/clis/xiaohongshu/replies.js +1 -0
  36. package/clis/xiaohongshu/reply.js +1 -0
  37. package/clis/xiaohongshu/topics.js +1 -0
  38. package/dist/src/cli.js +27 -25
  39. package/package.json +1 -1
  40. package/skills/opencli-browser/SKILL.md +14 -3
@@ -1,13 +1,13 @@
1
- import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionError as u,ArgumentError as b}from"@jackwener/opencli/errors";import{cli as mt,Strategy as pt}from"@jackwener/opencli/registry";import{assertLiteralContent as wt}from"../_shared/content-guard.js";import{setInputFiles as bt}from"../_shared/file-inject.js";import{humanType as L,humanWait as v,humanMoveTo as gt,elementViewportCenter as xt}from"./human.js";const yt="https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image",V=18,z=20,K=3000,G="|||",S="基础",X=[["基础","默认兜底,万能"],["边框","金句/要点卡"],["备忘","提醒/随手记"],["清新","日常/清单贴士"],["涂写","随笔/碎碎念"],["便签","笔记/提醒"],["光影","情绪/文艺"],["涂鸦","趣味/童话"],["简约","干货/观点"],["手写","日记/情感"],["插图","生活方式/轻松话题"],["美漫","活力/趣味/故事感"],["弥散","弥散光氛围"],["柔和","柔和/温柔金句"],["印刷","印刷海报/排版"],["科技","科技/产品"],["贺卡","节日祝福"],["札记","艺术/水彩氛围"],["书摘","书摘/引用"],["手帐","手帐拼贴"],["几何","醒目/有力主张"]],Zt=X.map(([t])=>t),B="文字配图",H="再写一张",Y="生成图片",W="下一步",J=".tiptap.ProseMirror",_t=["_onPublish","onPublish","_onSubmit","_handlePublish"],vt=["_onSave","_onSaveDraft","_onDraft"],F=['[contenteditable="true"][placeholder*="标题"]','[contenteditable="true"][placeholder*="赞"]','input[placeholder*="标题"]','input[placeholder*="title" i]','[contenteditable="true"][class*="title"]','input[maxlength="20"]','input[class*="title"]',".title-input input",".note-title input","input[maxlength]"],Q=['[contenteditable="true"][class*="content"]','[contenteditable="true"][class*="editor"]','[contenteditable="true"][placeholder*="描述"]','[contenteditable="true"][placeholder*="正文"]','[contenteditable="true"][placeholder*="内容"]','.note-content [contenteditable="true"]','.editor-content [contenteditable="true"]','[contenteditable="true"]:not([placeholder*="标题"]):not([placeholder*="赞"]):not([placeholder*="title" i])'],St={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp"};function h(t){if(t&&typeof t==="object"&&typeof t.session==="string"&&Object.prototype.hasOwnProperty.call(t,"data"))return t.data;return t}function Tt(t){return t.map((e)=>{const n=M.resolve(e);if(!D.existsSync(n))throw new b(`Image file not found: ${n}`);const o=M.extname(n).toLowerCase();if(!St[o])throw new b(`Unsupported image format "${o}". Supported: jpg, png, gif, webp`);return n})}const Z='input[type="file"][accept*="image"],input[type="file"][accept*=".jpg"],input[type="file"][accept*=".jpeg"],input[type="file"][accept*=".png"],input[type="file"][accept*=".gif"],input[type="file"][accept*=".webp"]';async function kt(t,e=15000){const n=500,o=Math.max(1,Math.ceil(e/n));for(let r=0;r<o;r++){if(await t.evaluate(`
1
+ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/topics.js";import{XHS_PUBLISH_ARGS as pt,validateXhsSettings as bt,setXhsPublishSettings as wt}from"./_shared/publish-settings.js";import*as L from"node:fs";import*as I from"node:path";import{CommandExecutionError as u,ArgumentError as p}from"@jackwener/opencli/errors";import{cli as gt,Strategy as xt}from"@jackwener/opencli/registry";import{assertLiteralContent as _t}from"../_shared/content-guard.js";import{setInputFiles as yt}from"../_shared/file-inject.js";import{humanType as j,humanWait as N,humanMoveTo as vt,elementViewportCenter as St}from"./human.js";const Tt="https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image",V=18,z=20,X=3000,H="|||",S="基础",G=[["基础","默认兜底,万能"],["边框","金句/要点卡"],["备忘","提醒/随手记"],["清新","日常/清单贴士"],["涂写","随笔/碎碎念"],["便签","笔记/提醒"],["光影","情绪/文艺"],["涂鸦","趣味/童话"],["简约","干货/观点"],["手写","日记/情感"],["插图","生活方式/轻松话题"],["美漫","活力/趣味/故事感"],["弥散","弥散光氛围"],["柔和","柔和/温柔金句"],["印刷","印刷海报/排版"],["科技","科技/产品"],["贺卡","节日祝福"],["札记","艺术/水彩氛围"],["书摘","书摘/引用"],["手帐","手帐拼贴"],["几何","醒目/有力主张"]],ne=G.map(([t])=>t),O="文字配图",Y="再写一张",K="生成图片",W="下一步",B=".tiptap.ProseMirror",kt=["_onPublish","onPublish","_onSubmit","_handlePublish"],At=["_onSave","_onSaveDraft","_onDraft"],U=['[contenteditable="true"][placeholder*="标题"]','[contenteditable="true"][placeholder*="赞"]','input[placeholder*="标题"]','input[placeholder*="title" i]','[contenteditable="true"][class*="title"]','input[maxlength="20"]','input[class*="title"]',".title-input input",".note-title input","input[maxlength]"],Q=['[contenteditable="true"][class*="content"]','[contenteditable="true"][class*="editor"]','[contenteditable="true"][placeholder*="描述"]','[contenteditable="true"][placeholder*="正文"]','[contenteditable="true"][placeholder*="内容"]','.note-content [contenteditable="true"]','.editor-content [contenteditable="true"]','[contenteditable="true"]:not([placeholder*="标题"]):not([placeholder*="赞"]):not([placeholder*="title" i])'],Et={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp"};function g(t){if(t&&typeof t==="object"&&typeof t.session==="string"&&Object.prototype.hasOwnProperty.call(t,"data"))return t.data;return t}function $t(t){return t.map((e)=>{const r=I.resolve(e);if(!L.existsSync(r))throw new p(`Image file not found: ${r}`);const n=I.extname(r).toLowerCase();if(!Et[n])throw new p(`Unsupported image format "${n}". Supported: jpg, png, gif, webp`);return r})}const Z='input[type="file"][accept*="image"],input[type="file"][accept*=".jpg"],input[type="file"][accept*=".jpeg"],input[type="file"][accept*=".png"],input[type="file"][accept*=".gif"],input[type="file"][accept*=".webp"]';async function Ct(t,e=15000){const r=500,n=Math.max(1,Math.ceil(e/r));for(let o=0;o<n;o++){if(await t.evaluate(`
2
2
  (() => {
3
3
  const sels = ${JSON.stringify(Z)};
4
4
  return !!document.querySelector(sels);
5
5
  })()
6
- `))return!0;if(r<o-1)await t.wait({time:n/1000})}return!1}async function tt(t,e){if(!await kt(t))return{ok:!1,count:0,error:"No file input found on page (waited 15s; publish surface did not finish rendering)"};try{return{ok:!0,count:(await bt(t,Z,e)).count??e.length}}catch(o){return{ok:!1,count:0,error:o instanceof Error?o.message:String(o)}}}async function et(t,e=30000){const n=2000,o=Math.ceil(e/n);for(let r=0;r<o;r++){if(!await t.evaluate(`
6
+ `))return!0;if(o<n-1)await t.wait({time:r/1000})}return!1}async function tt(t,e){if(!await Ct(t))return{ok:!1,count:0,error:"No file input found on page (waited 15s; publish surface did not finish rendering)"};try{return{ok:!0,count:(await yt(t,Z,e)).count??e.length}}catch(n){return{ok:!1,count:0,error:n instanceof Error?n.message:String(n)}}}async function et(t,e=30000){const r=2000,n=Math.ceil(e/r);for(let o=0;o<n;o++){if(!await t.evaluate(`
7
7
  () => !!document.querySelector(
8
8
  '[class*="upload"][class*="progress"], [class*="uploading"], [class*="loading"][class*="image"]'
9
9
  )
10
- `))return;await t.wait({time:n/1000})}}export function describeTextMismatch(t,e){const n=String(t??""),o=String(e??""),r=(m)=>m.split(/\n\s*\n/).filter((w)=>w.trim()).length;let i=0;while(i<n.length&&i<o.length&&n[i]===o[i])i++;const a=(m)=>JSON.stringify(m.slice(Math.max(0,i-15),i+25)),s=`内容校验不一致:期望 ${n.length} 字符 / ${r(n)} 段,实际 ${o.length} 字符 / ${r(o)} 段。`,d=i>=n.length&&i>=o.length?"":` 首个差异在第 ${i+1} 个字符:期望 ${a(n)},实际 ${a(o)}。`,c=r(n)>1&&r(o)<=1?" 段落数塌成 1 段,通常是编辑器把换行吃掉了。":"";return s+d+c}async function Et(t,e,n){const o=()=>t.evaluate(`
10
+ `))return;await t.wait({time:r/1000})}}export function describeTextMismatch(t,e){const r=String(t??""),n=String(e??""),o=(f)=>f.split(/\n\s*\n/).filter((y)=>y.trim()).length;let i=0;while(i<r.length&&i<n.length&&r[i]===n[i])i++;const a=(f)=>JSON.stringify(f.slice(Math.max(0,i-15),i+25)),d=`内容校验不一致:期望 ${r.length} 字符 / ${o(r)} 段,实际 ${n.length} 字符 / ${o(n)} 段。`,s=i>=r.length&&i>=n.length?"":` 首个差异在第 ${i+1} 个字符:期望 ${a(r)},实际 ${a(n)}。`,c=o(r)>1&&o(n)<=1?" 段落数塌成 1 段,通常是编辑器把换行吃掉了。":"";return d+s+c}async function Mt(t,e,r){const n=()=>t.evaluate(`
11
11
  ((sel) => {
12
12
  const el = Array.from(document.querySelectorAll(sel)).find(n => n && n.offsetParent !== null);
13
13
  if (!el) return -1;
@@ -15,15 +15,15 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
15
15
  // (innerText 会被 CSS 影响,而且空段落根本不产生换行符)。
16
16
  return el.querySelectorAll('p, div[data-node-type], li').length || el.childElementCount;
17
17
  })(${JSON.stringify(e)})
18
- `),r=await o();let i="unavailable";if(typeof t.pressKey==="function"){i=await t.pressKey("Enter")||"native";await t.wait({time:0.15})}let a=await o();if(a>r)return;await t.evaluate(`
18
+ `),o=await n();let i="unavailable";if(typeof t.pressKey==="function"){i=await t.pressKey("Enter")||"native";await t.wait({time:0.15})}let a=await n();if(a>o)return;await t.evaluate(`
19
19
  ((sel) => {
20
20
  const el = Array.from(document.querySelectorAll(sel)).find(n => n && n.offsetParent !== null);
21
21
  if (!el) return false;
22
22
  el.focus();
23
23
  return document.execCommand('insertParagraph', false);
24
24
  })(${JSON.stringify(e)})
25
- `);await t.wait({time:0.2});a=await o();if(a>r)return;throw new u(`正文第 ${n} 处换行没能插入段落分隔(编辑器块数仍为 ${r},`+`按键通道=${i},execCommand 兜底也没生效)。继续打字会把整篇正文塞进一段,因此中止——`+`没换行的笔记发出去只能删了重发。
26
- `+"请勿直接重试同一条命令,先看编辑器当前状态:最可能是有话题/表情联想浮层开着,"+"此时 Enter 被浮层当成「选中候选」吃掉。正文里如果带 # 号尤其容易触发。")}async function nt(t,e,n,o){const r=await t.evaluate(`
25
+ `);await t.wait({time:0.2});a=await n();if(a>o)return;throw new u(`正文第 ${r} 处换行没能插入段落分隔(编辑器块数仍为 ${o},`+`按键通道=${i},execCommand 兜底也没生效)。继续打字会把整篇正文塞进一段,因此中止——`+`没换行的笔记发出去只能删了重发。
26
+ `+"请勿直接重试同一条命令,先看编辑器当前状态:最可能是有话题/表情联想浮层开着,"+"此时 Enter 被浮层当成「选中候选」吃掉。正文里如果带 # 号尤其容易触发。")}async function nt(t,e,r,n){const o=await t.evaluate(`
27
27
  (function(selectors) {
28
28
  const __opencli_xhs_fill_phase = "locate";
29
29
  for (const sel of selectors) {
@@ -38,7 +38,7 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
38
38
  }
39
39
  return { ok: false };
40
40
  })(${JSON.stringify(e)})
41
- `);if(!r.ok){await t.screenshot({path:`/tmp/xhs_publish_${o}_debug.png`});throw Error(`Could not find ${o} input. Debug screenshot: /tmp/xhs_publish_${o}_debug.png`)}const i=()=>t.evaluate(`
41
+ `);if(!o.ok){await t.screenshot({path:`/tmp/xhs_publish_${n}_debug.png`});throw Error(`Could not find ${n} input. Debug screenshot: /tmp/xhs_publish_${n}_debug.png`)}const i=()=>t.evaluate(`
42
42
  ((selector, expectedText) => {
43
43
  const __opencli_xhs_fill_phase = "apply";
44
44
  const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
@@ -94,8 +94,8 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
94
94
  el.blur();
95
95
  const actual = normalize(el.innerText || el.textContent || '');
96
96
  return { ok: actual === normalize(expectedText), actual };
97
- })(${JSON.stringify(r.sel)}, ${JSON.stringify(n)})
98
- `);let a;if(r.kind==="contenteditable"&&t.insertText){if(!(await t.evaluate(`
97
+ })(${JSON.stringify(o.sel)}, ${JSON.stringify(r)})
98
+ `);let a;if(o.kind==="contenteditable"&&t.insertText){if(!(await t.evaluate(`
99
99
  ((selector) => {
100
100
  const __opencli_xhs_fill_phase = "prepare";
101
101
  const el = Array.from(document.querySelectorAll(selector)).find(node => node && node.offsetParent !== null);
@@ -115,9 +115,9 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
115
115
  selection?.removeAllRanges();
116
116
  selection?.addRange(range);
117
117
  return { ok: true };
118
- })(${JSON.stringify(r.sel)})
119
- `))?.ok){await t.screenshot({path:`/tmp/xhs_publish_${o}_debug.png`});throw Error(`Could not prepare ${o} input. Debug screenshot: /tmp/xhs_publish_${o}_debug.png`)}await t.wait({time:0.35});try{const d=String(n).split(`
120
- `);for(let c=0;c<d.length;c++){if(c>0)await Et(t,r.sel,c);if(d[c])await L(t,d[c])}a=await t.evaluate(`
118
+ })(${JSON.stringify(o.sel)})
119
+ `))?.ok){await t.screenshot({path:`/tmp/xhs_publish_${n}_debug.png`});throw Error(`Could not prepare ${n} input. Debug screenshot: /tmp/xhs_publish_${n}_debug.png`)}await t.wait({time:0.35});try{const s=String(r).split(`
120
+ `);for(let c=0;c<s.length;c++){if(c>0)await Mt(t,o.sel,c);if(s[c])await j(t,s[c])}a=await t.evaluate(`
121
121
  ((selector, expectedText) => {
122
122
  const __opencli_xhs_fill_phase = "verify";
123
123
  const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
@@ -139,131 +139,9 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
139
139
  el.blur();
140
140
  const actual = normalize(el.innerText || el.textContent || '');
141
141
  return { ok: actual === normalize(expectedText), actual };
142
- })(${JSON.stringify(r.sel)}, ${JSON.stringify(n)})
143
- `)}catch(d){if(d instanceof u)throw d;if(String(n).includes(`
144
- `))throw new u(`写入${o}失败:${String(d?.message||d)}。`+"正文含换行,不能回退到单段写入方式(那会把多段正文塞进一段),因此中止。"+"⚠️ 编辑器可能已自动保存草稿:请勿直接重试,先用 `ppcli xiaohongshu drafts` 查看。");a=await i()}}else a=await i();if(!a?.ok){await t.screenshot({path:`/tmp/xhs_publish_${o}_debug.png`});const s=typeof a?.actual==="string"?a.actual:"";throw Error(`Failed to set ${o}. ${describeTextMismatch(n,s)}`+" ⚠️ 编辑器可能已自动保存草稿:请勿直接重试,先用 `ppcli xiaohongshu drafts` 查看,"+"已存在则改为在该草稿上继续操作或先删除,避免建出重复草稿。"+` Debug screenshot: /tmp/xhs_publish_${o}_debug.png`)}}async function $t(t,e){return h(await t.evaluate(`
145
- (selectors => {
146
- const el = selectors
147
- .map(sel => Array.from(document.querySelectorAll(sel)))
148
- .flat()
149
- .find(node => node && node.offsetParent !== null && node.isContentEditable);
150
- if (!el) return false;
151
- el.focus();
152
- const selection = window.getSelection();
153
- const range = document.createRange();
154
- range.selectNodeContents(el);
155
- range.collapse(false);
156
- selection?.removeAllRanges();
157
- selection?.addRange(range);
158
- return true;
159
- })(${JSON.stringify(e)})
160
- `))}function te(t,{click:e=!1}={}){return`
161
- (topicName => {
162
- const norm = (value) => (value || '').replace(/^#/, '').replace(/\\s+/g, '').trim();
163
- const want = norm(topicName);
164
- const SUGGESTION_SELECTORS = [
165
- '[class*="topic-item"]',
166
- '[class*="hashtag-item"]',
167
- '[class*="suggest-item"]',
168
- '[class*="suggestion"] li',
169
- '[class*="mention"] li',
170
- '[class*="dropdown"] li',
171
- '[id*="topic"] li',
172
- '[class*="topic"] li',
173
- ];
174
- const seen = new Set();
175
- const items = [];
176
- for (const sel of SUGGESTION_SELECTORS) {
177
- for (const node of document.querySelectorAll(sel)) {
178
- if (!node || seen.has(node)) continue;
179
- if (node.offsetParent === null) continue;
180
- const rect = node.getBoundingClientRect();
181
- if (rect.width <= 0 || rect.height <= 0) continue;
182
- seen.add(node);
183
- items.push(node);
184
- }
185
- }
186
- if (!items.length) return { ok: false, count: 0 };
187
- let target = items.find(node => norm(node.innerText || node.textContent) === want);
188
- if (!target) target = items.find(node => norm(node.innerText || node.textContent).includes(want));
189
- if (!target) target = items[0];
190
- const rect = target.getBoundingClientRect();
191
- ${e?"try { target.click(); } catch (e) { return { ok: false, count: items.length, message: String(e) }; }":""}
192
- return {
193
- ok: true,
194
- count: items.length,
195
- x: Math.round(rect.left + rect.width / 2),
196
- y: Math.round(rect.top + rect.height / 2),
197
- text: (target.innerText || target.textContent || '').trim().slice(0, 40),
198
- };
199
- })(${JSON.stringify(t)})
200
- `}function ee(t,e){return`
201
- ((topicName, selectors) => {
202
- const norm = (value) => (value || '').replace(/^#/, '').replace(/\\s+/g, '').trim();
203
- const want = norm(topicName);
204
- const editor = selectors
205
- .map(sel => Array.from(document.querySelectorAll(sel)))
206
- .flat()
207
- .find(node => node && node.offsetParent !== null && node.isContentEditable);
208
- if (!editor || !want) return 0;
209
- const hasTopicSignal = (node) => {
210
- const tag = (node.tagName || '').toLowerCase();
211
- const role = (node.getAttribute && node.getAttribute('role')) || '';
212
- const cls = String(node.className || '');
213
- const id = String(node.id || '');
214
- const href = (node.getAttribute && node.getAttribute('href')) || '';
215
- const dataKeys = node.dataset ? Object.keys(node.dataset).join(' ') : '';
216
- const haystack = [tag, role, cls, id, href, dataKeys].join(' ');
217
- return tag === 'a'
218
- || /link/i.test(role)
219
- || /topic|hashtag|hash-tag|tag|mention|keyword/i.test(haystack)
220
- || node.isContentEditable === false;
221
- };
222
- let count = 0;
223
- for (const node of Array.from(editor.querySelectorAll('*'))) {
224
- if (!node || node.offsetParent === null) continue;
225
- if (!hasTopicSignal(node)) continue;
226
- const text = norm(node.innerText || node.textContent || '');
227
- if (text === want || text === '#' + want) count += 1;
228
- }
229
- return count;
230
- })(${JSON.stringify(t)}, ${JSON.stringify(e)})
231
- `}function ot(t,e){return`
232
- ((topicName, selectors) => {
233
- const __opencli_xhs_topic_marker_count = true;
234
- const marker = '#' + topicName + '[话题]';
235
- const editor = selectors
236
- .map(sel => Array.from(document.querySelectorAll(sel)))
237
- .flat()
238
- .find(node => node && node.offsetParent !== null && node.isContentEditable);
239
- if (!editor || !marker) return 0;
240
- const text = editor.innerText || editor.textContent || '';
241
- let count = 0;
242
- let index = text.indexOf(marker);
243
- while (index !== -1) {
244
- count += 1;
245
- index = text.indexOf(marker, index + marker.length);
246
- }
247
- return count;
248
- })(${JSON.stringify(t)}, ${JSON.stringify(e)})
249
- `}async function ne(t,e){const n=`#${e}`;if(typeof t.insertText==="function")try{await t.insertText(n);return!0}catch{}return h(await t.evaluate(`
250
- (text => {
251
- const ok = document.execCommand('insertText', false, text);
252
- const active = document.activeElement;
253
- if (active) active.dispatchEvent(new Event('input', { bubbles: true }));
254
- return ok;
255
- })(${JSON.stringify(n)})
256
- `))}function rt(t){return`
257
- (selectors => {
258
- let editor = null;
259
- for (const sel of selectors) {
260
- editor = document.querySelector(sel);
261
- if (editor) break;
262
- }
263
- if (!editor) return -1;
264
- return (editor.innerText || editor.textContent || '').length;
265
- })(${JSON.stringify(t)})
266
- `}async function At(t,e,n){if(!await $t(t,e))throw new u(`Could not attach topic "${n}": body editor not found`);const r=Number(h(await t.evaluate(ot(n,e))))||0;if(typeof t.pressKey==="function")try{await t.pressKey("Enter")}catch{}if(typeof t.insertText!=="function")throw new u(`Could not attach topic "${n}": page.insertText is unavailable`);try{await L(t,`#${n}`)}catch{throw new u(`Could not attach topic "${n}": failed to type inline topic query`)}await v(t,1.2);if(typeof t.pressKey!=="function")throw new u(`Could not attach topic "${n}": page.pressKey is unavailable`);try{await t.pressKey("Enter")}catch(a){throw new u(`Could not attach topic "${n}": failed to accept suggestion (${a&&a.message||a})`)}await t.wait({time:0.8});if((Number(h(await t.evaluate(ot(n,e))))||0)<=r)throw new u(`Could not attach topic "${n}": no real topic entity appeared after selection`)}async function Ct(t,e,n){const o=[],r=[];for(const i of n){let a=-1;try{const s=Number(h(await t.evaluate(rt(e))));a=Number.isFinite(s)?s:-1}catch{}try{await At(t,e,i);o.push(i);await t.wait({time:0.4})}catch(s){r.push({topic:i,reason:s&&s.message||String(s)});if(a>=0&&typeof t.pressKey==="function")try{const d=i.length+6;for(let c=0;c<d;c++){const m=Number(h(await t.evaluate(rt(e))));if(!Number.isFinite(m)||m<0||m<=a)break;await t.pressKey("Backspace");await t.wait({time:0.15})}}catch{}}}return{added:o,failed:r}}async function Nt(t){const e=await t.evaluate(`
142
+ })(${JSON.stringify(o.sel)}, ${JSON.stringify(r)})
143
+ `)}catch(s){if(s instanceof u)throw s;if(String(r).includes(`
144
+ `))throw new u(`写入${n}失败:${String(s?.message||s)}。`+"正文含换行,不能回退到单段写入方式(那会把多段正文塞进一段),因此中止。"+"⚠️ 编辑器可能已自动保存草稿:请勿直接重试,先用 `ppcli xiaohongshu drafts` 查看。");a=await i()}}else a=await i();if(!a?.ok){await t.screenshot({path:`/tmp/xhs_publish_${n}_debug.png`});const d=typeof a?.actual==="string"?a.actual:"";throw Error(`Failed to set ${n}. ${describeTextMismatch(r,d)}`+" ⚠️ 编辑器可能已自动保存草稿:请勿直接重试,先用 `ppcli xiaohongshu drafts` 查看,"+"已存在则改为在该草稿上继续操作或先删除,避免建出重复草稿。"+` Debug screenshot: /tmp/xhs_publish_${n}_debug.png`)}}async function It(t){const e=await t.evaluate(`
267
145
  () => {
268
146
  const isVisible = (el) => {
269
147
  if (!el || el.offsetParent === null) return false;
@@ -312,7 +190,7 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
312
190
  }
313
191
  return { ok: false, visibleTexts };
314
192
  }
315
- `);if(e?.ok)await t.wait({time:1});return e}async function T(t,e,n=7000){const o=500,r=Math.max(1,Math.ceil(n/o));let i={ok:!1};for(let a=0;a<r;a++){i=h(await t.evaluate(`
193
+ `);if(e?.ok)await t.wait({time:1});return e}async function T(t,e,r=7000){const n=500,o=Math.max(1,Math.ceil(r/n));let i={ok:!1};for(let a=0;a<o;a++){i=g(await t.evaluate(`
316
194
  ((cfg) => {
317
195
  const __opencli_xhs_click_label = { wantLabel: ${JSON.stringify(e)} };
318
196
  const wantLabel = ${JSON.stringify(e)};
@@ -347,10 +225,10 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
347
225
  }
348
226
  return { ok: false };
349
227
  })()
350
- `));if(i?.ok)return i;await t.wait({time:o/1000})}return i}async function Mt(t){const e=await t.evaluate(`
228
+ `));if(i?.ok)return i;await t.wait({time:n/1000})}return i}async function Nt(t){const e=await t.evaluate(`
351
229
  (() => {
352
230
  const __opencli_xhs_focus_card = true;
353
- const sel = ${JSON.stringify(J)};
231
+ const sel = ${JSON.stringify(B)};
354
232
  const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
355
233
  // Prefer the editor inside the active swiper slide if present.
356
234
  const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
@@ -364,34 +242,34 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
364
242
  selection?.addRange(range);
365
243
  return { ok: true };
366
244
  })()
367
- `);return h(e)}async function Rt(t){const e=await t.evaluate(`
245
+ `);return g(e)}async function Rt(t){const e=await t.evaluate(`
368
246
  (() => {
369
247
  const __opencli_xhs_card_text = true;
370
- const sel = ${JSON.stringify(J)};
248
+ const sel = ${JSON.stringify(B)};
371
249
  const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
372
250
  const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
373
251
  const text = active ? (active.innerText || active.textContent || '').trim() : '';
374
252
  return { ok: !!text, text };
375
253
  })()
376
- `);return h(e)}async function it(t){const e=await t.evaluate(`
254
+ `);return g(e)}async function rt(t){const e=await t.evaluate(`
377
255
  (() => {
378
256
  const __opencli_xhs_card_count = true;
379
- const sel = ${JSON.stringify(J)};
257
+ const sel = ${JSON.stringify(B)};
380
258
  const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
381
259
  const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
382
260
  const activeText = active ? (active.innerText || active.textContent || '').trim() : '';
383
261
  return { ok: true, count: editors.length, activeEmpty: !activeText };
384
262
  })()
385
- `);return h(e)}async function It(t,e=8000){const n=300,o=Math.ceil(e/n);for(let r=0;r<o;r++){if((await it(t))?.count>=1)return!0;await t.wait({time:n/1000})}return!1}async function Ot(t,e,n=6000){const o=300,r=Math.ceil(n/o);for(let i=0;i<r;i++){const a=await it(t);if(a?.count>=e&&a?.activeEmpty)return!0;await t.wait({time:o/1000})}return!1}async function qt(t,e,n=4){for(let o=0;o<n;o++){if(!(await T(t,H))?.ok)return!1;if(await Ot(t,e,2500))return!0}return!1}async function Pt(t){const e=await t.evaluate(`
263
+ `);return g(e)}async function Pt(t,e=8000){const r=300,n=Math.ceil(e/r);for(let o=0;o<n;o++){if((await rt(t))?.count>=1)return!0;await t.wait({time:r/1000})}return!1}async function Dt(t,e,r=6000){const n=300,o=Math.ceil(r/n);for(let i=0;i<o;i++){const a=await rt(t);if(a?.count>=e&&a?.activeEmpty)return!0;await t.wait({time:n/1000})}return!1}async function qt(t,e,r=4){for(let n=0;n<r;n++){if(!(await T(t,Y))?.ok)return!1;if(await Dt(t,e,2500))return!0}return!1}async function Lt(t){const e=await t.evaluate(`
386
264
  (() => {
387
265
  const __opencli_xhs_preview_ready = true;
388
266
  const ready = Array.from(document.querySelectorAll('button'))
389
267
  .some((b) => b.offsetParent !== null && (b.innerText || '').replace(/\\s+/g, '') === '下一步');
390
268
  return { ok: ready };
391
269
  })()
392
- `);return h(e)}async function Dt(t,e=6){const n=400,o=Math.ceil(3000/n);for(let r=0;r<e;r++){if(!(await T(t,Y))?.ok&&r===0)return!1;for(let a=0;a<o;a++){if((await Pt(t))?.ok)return!0;await t.wait({time:n/1000})}}return!1}async function Lt(t,e,n){e=String(e).replace(/\\n/g,`
393
- `);if(!(await Mt(t))?.ok)throw new u(`文字配图: could not focus card editor #${n+1}`);if(typeof t.insertText==="function"){const i=e.split(`
394
- `);for(let a=0;a<i.length;a++){if(a>0&&typeof t.pressKey==="function")await t.pressKey("Enter");if(i[a])await L(t,i[a])}}else await t.evaluate(`(t => document.execCommand('insertText', false, t))(${JSON.stringify(e)})`);await t.wait({time:0.4});if(!(await Rt(t))?.ok)throw new u(`文字配图: card editor #${n+1} is empty after typing`)}async function Bt(t,e){if(!e||e===S)return S;const n=h(await t.evaluate(`
270
+ `);return g(e)}async function Ot(t,e=6){const r=400,n=Math.ceil(3000/r);for(let o=0;o<e;o++){if(!(await T(t,K))?.ok&&o===0)return!1;for(let a=0;a<n;a++){if((await Lt(t))?.ok)return!0;await t.wait({time:r/1000})}}return!1}async function Bt(t,e,r){e=String(e).replace(/\\n/g,`
271
+ `);if(!(await Nt(t))?.ok)throw new u(`文字配图: could not focus card editor #${r+1}`);if(typeof t.insertText==="function"){const i=e.split(`
272
+ `);for(let a=0;a<i.length;a++){if(a>0&&typeof t.pressKey==="function")await t.pressKey("Enter");if(i[a])await j(t,i[a])}}else await t.evaluate(`(t => document.execCommand('insertText', false, t))(${JSON.stringify(e)})`);await t.wait({time:0.4});if(!(await Rt(t))?.ok)throw new u(`文字配图: card editor #${r+1} is empty after typing`)}async function Ut(t,e){if(!e||e===S)return S;const r=g(await t.evaluate(`
395
273
  (async () => {
396
274
  const __opencli_xhs_card_styles = true;
397
275
  const want = ${JSON.stringify(e)};
@@ -420,7 +298,7 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
420
298
  }
421
299
  return { ok: seen.length > 0, styles: seen, found: false };
422
300
  })()
423
- `));if(!n?.found)throw new u(`文字配图: requested style "${e}" is not available for this content (options: ${(n?.styles||[]).join(" / ")||"none"}). Choose an available style or omit --card-style to use ${S}.`);if(!(await T(t,e))?.ok)throw new u(`文字配图: could not click requested style "${e}".`);await t.wait({time:0.6});return e}async function at(t){const e=await t.evaluate(`
301
+ `));if(!r?.found)throw new u(`文字配图: requested style "${e}" is not available for this content (options: ${(r?.styles||[]).join(" / ")||"none"}). Choose an available style or omit --card-style to use ${S}.`);if(!(await T(t,e))?.ok)throw new u(`文字配图: could not click requested style "${e}".`);await t.wait({time:0.6});return e}async function ot(t){const e=await t.evaluate(`
424
302
  (() => {
425
303
  const __opencli_xhs_composer_media_count = true;
426
304
  const visibleBox = (el) => {
@@ -433,7 +311,7 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
433
311
  const r = el.getBoundingClientRect();
434
312
  return r.width >= 48 && r.height >= 48;
435
313
  };
436
- const titleSelectors = ${JSON.stringify(F)};
314
+ const titleSelectors = ${JSON.stringify(U)};
437
315
  const titleEl = titleSelectors
438
316
  .map((sel) => Array.from(document.querySelectorAll(sel)))
439
317
  .flat()
@@ -452,7 +330,7 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
452
330
  }
453
331
  return { ok: true, count };
454
332
  })()
455
- `);return h(e)}async function st(t,e,n){const o=await at(t);if(!o||typeof o.count!=="number")throw new u(`${n}: could not verify current composer media count`);if(o.count<e){await t.screenshot({path:"/tmp/xhs_publish_media_debug.png"});throw new u(`${n}: expected at least ${e} visible media item(s), got ${o.count}. Debug screenshot: /tmp/xhs_publish_media_debug.png`)}}async function Jt(t,e,n){if(!(await T(t,B))?.ok){await t.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new u(`文字配图: could not click "${B}" entry. Debug: /tmp/xhs_publish_textimage_debug.png`)}if(!await It(t)){await t.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new u(`文字配图: 写文字 card editor did not appear after clicking "${B}". Debug: /tmp/xhs_publish_textimage_debug.png`)}for(let s=0;s<e.length;s++){if(s>0){if(!await qt(t,s+1)){await t.screenshot({path:"/tmp/xhs_publish_addcard_debug.png"});throw new u(`文字配图: new card editor #${s+1} did not render after "${H}". Debug: /tmp/xhs_publish_addcard_debug.png`)}}await Lt(t,e[s],s)}if(!await Dt(t)){await t.screenshot({path:"/tmp/xhs_publish_generate_debug.png"});throw new u(`文字配图: "${Y}" did not advance to the 预览图片 step. `+"Debug: /tmp/xhs_publish_generate_debug.png")}const i=await Bt(t,n);if(!(await T(t,W))?.ok){await t.screenshot({path:"/tmp/xhs_publish_next_debug.png"});throw new u(`文字配图: could not click "${W}". Debug: /tmp/xhs_publish_next_debug.png`)}await t.wait({time:2});return i}async function lt(t){return t.evaluate(`
333
+ `);return g(e)}async function it(t,e,r){const n=await ot(t);if(!n||typeof n.count!=="number")throw new u(`${r}: could not verify current composer media count`);if(n.count<e){await t.screenshot({path:"/tmp/xhs_publish_media_debug.png"});throw new u(`${r}: expected at least ${e} visible media item(s), got ${n.count}. Debug screenshot: /tmp/xhs_publish_media_debug.png`)}}async function Ft(t,e,r){if(!(await T(t,O))?.ok){await t.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new u(`文字配图: could not click "${O}" entry. Debug: /tmp/xhs_publish_textimage_debug.png`)}if(!await Pt(t)){await t.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new u(`文字配图: 写文字 card editor did not appear after clicking "${O}". Debug: /tmp/xhs_publish_textimage_debug.png`)}for(let d=0;d<e.length;d++){if(d>0){if(!await qt(t,d+1)){await t.screenshot({path:"/tmp/xhs_publish_addcard_debug.png"});throw new u(`文字配图: new card editor #${d+1} did not render after "${Y}". Debug: /tmp/xhs_publish_addcard_debug.png`)}}await Bt(t,e[d],d)}if(!await Ot(t)){await t.screenshot({path:"/tmp/xhs_publish_generate_debug.png"});throw new u(`文字配图: "${K}" did not advance to the 预览图片 step. `+"Debug: /tmp/xhs_publish_generate_debug.png")}const i=await Ut(t,r);if(!(await T(t,W))?.ok){await t.screenshot({path:"/tmp/xhs_publish_next_debug.png"});throw new u(`文字配图: could not click "${W}". Debug: /tmp/xhs_publish_next_debug.png`)}await t.wait({time:2});return i}async function at(t){return t.evaluate(`
456
334
  () => {
457
335
  const text = (document.body?.innerText || '').replace(/s+/g, ' ').trim();
458
336
  const hasTitleInput = !!Array.from(document.querySelectorAll('input, textarea')).find((el) => {
@@ -482,22 +360,22 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
482
360
  const state = hasTitleInput ? 'editor_ready' : hasImageInput || !hasVideoSurface ? 'image_surface' : 'video_surface';
483
361
  return { state, hasTitleInput, hasImageInput, hasVideoSurface };
484
362
  }
485
- `)}async function Ft(t,e=5000){const n=500,o=Math.max(1,Math.ceil(e/n));let r=await lt(t);for(let i=0;i<o;i++){if(r.state!=="video_surface")return r;if(i<o-1){await t.wait({time:n/1000});r=await lt(t)}}return r}async function Ut(t,e=1e4){const n=1000,o=Math.ceil(e/n);for(let r=0;r<o;r++){if(await t.evaluate(`
363
+ `)}async function Jt(t,e=5000){const r=500,n=Math.max(1,Math.ceil(e/r));let o=await at(t);for(let i=0;i<n;i++){if(o.state!=="video_surface")return o;if(i<n-1){await t.wait({time:r/1000});o=await at(t)}}return o}async function jt(t,e=1e4){const r=1000,n=Math.ceil(e/r);for(let o=0;o<n;o++){if(await t.evaluate(`
486
364
  (() => {
487
- const sels = ${JSON.stringify(F)};
365
+ const sels = ${JSON.stringify(U)};
488
366
  for (const sel of sels) {
489
367
  const el = document.querySelector(sel);
490
368
  if (el && el.offsetParent !== null) return true;
491
369
  }
492
370
  return false;
493
- })()`))return!0;if(r<o-1)await t.wait({time:n/1000})}return!1}async function jt(t){const e=h(await t.evaluate(`
371
+ })()`))return!0;if(o<n-1)await t.wait({time:r/1000})}return!1}async function Vt(t){const e=g(await t.evaluate(`
494
372
  (() => {
495
373
  const __opencli_xhs_restore_srcs = true;
496
374
  return Array.from(document.querySelectorAll('.img-container img'))
497
375
  .map((img) => img.currentSrc || img.src || '')
498
376
  .filter(Boolean);
499
377
  })()
500
- `));return Array.isArray(e)?e:[]}async function ct(t,e){if(!Array.isArray(e)||e.length===0)return 0;const n=h(await t.evaluate(`
378
+ `));return Array.isArray(e)?e:[]}async function st(t,e){if(!Array.isArray(e)||e.length===0)return 0;const r=g(await t.evaluate(`
501
379
  (async (wanted) => {
502
380
  const __opencli_xhs_delete_by_src = true;
503
381
  const want = new Set(wanted);
@@ -528,7 +406,7 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
528
406
  }
529
407
  return removed;
530
408
  })(${JSON.stringify(e)})
531
- `));return Number(n)||0}function Vt(t){const e=String(t&&(t.url||t.key)||""),n=String(t&&t.method||"").toUpperCase(),o=Number(t&&t.status||0);if(n!=="PUT")return null;if(!/ros-upload[^/]*\/spectrum\//.test(e))return null;if(o<200||o>=300)return null;const r=e.match(/\/spectrum\/([^?#]+)/);return r?r[1]:e}function ut(t){return Math.min(45,6+t*4)}async function dt(t,e,n,o=90000){if(e<=0)return{ok:!0,completed:0};if(!n||typeof t.readNetworkCapture!=="function"){await t.wait({time:ut(e)});return{ok:!0,completed:null,viaFallback:!0}}const r=1500,a=Math.ceil(9000/r),s=Math.ceil(o/r),d=new Set;for(let c=0;c<s;c++){const m=await t.readNetworkCapture().catch(()=>[]);for(const w of Array.isArray(m)?m:[]){const f=Vt(w);if(f)d.add(f)}if(d.size>=e)return{ok:!0,completed:d.size};if(c>=a&&d.size===0){await t.wait({time:ut(e)});return{ok:!0,completed:null,viaFallback:!0}}await t.wait({time:r/1000})}return{ok:!1,completed:d.size}}async function zt(t,e,n){try{await t.goto("https://creator.xiaohongshu.com/new/note-manager")}catch{return null}const o=e.trim();for(let r=0;r<n;r++){await t.wait({time:1.5});let i;try{i=h(await t.evaluate(`(() => {
409
+ `));return Number(r)||0}function zt(t){const e=String(t&&(t.url||t.key)||""),r=String(t&&t.method||"").toUpperCase(),n=Number(t&&t.status||0);if(r!=="PUT")return null;if(!/ros-upload[^/]*\/spectrum\//.test(e))return null;if(n<200||n>=300)return null;const o=e.match(/\/spectrum\/([^?#]+)/);return o?o[1]:e}function lt(t){return Math.min(45,6+t*4)}async function ct(t,e,r,n=90000){if(e<=0)return{ok:!0,completed:0};if(!r||typeof t.readNetworkCapture!=="function"){await t.wait({time:lt(e)});return{ok:!0,completed:null,viaFallback:!0}}const o=1500,a=Math.ceil(9000/o),d=Math.ceil(n/o),s=new Set;for(let c=0;c<d;c++){const f=await t.readNetworkCapture().catch(()=>[]);for(const y of Array.isArray(f)?f:[]){const b=zt(y);if(b)s.add(b)}if(s.size>=e)return{ok:!0,completed:s.size};if(c>=a&&s.size===0){await t.wait({time:lt(e)});return{ok:!0,completed:null,viaFallback:!0}}await t.wait({time:o/1000})}return{ok:!1,completed:s.size}}async function Xt(t,e,r){try{await t.goto("https://creator.xiaohongshu.com/new/note-manager")}catch{return null}const n=e.trim();for(let o=0;o<r;o++){await t.wait({time:1.5});let i;try{i=g(await t.evaluate(`(() => {
532
410
  const noteIdRe = /"noteId":"([0-9a-f]{24})"/;
533
411
  return Array.from(document.querySelectorAll('div.note[data-impression], div.note')).slice(0, 3).map((card) => {
534
412
  const impression = card.getAttribute('data-impression') || '';
@@ -537,7 +415,7 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
537
415
  const dateText = (card.querySelector('.time_status, .time')?.innerText || '').trim();
538
416
  return { id, title, dateText };
539
417
  }).filter((entry) => entry.title);
540
- })()`))}catch{return null}if(Array.isArray(i)){const a=i.find((s)=>{const d=String(s.title||"").trim();if(!(d===o||d.startsWith(o)||o.startsWith(d)))return!1;const c=String(s.dateText||"").match(/(\d{4})年(\d{2})月(\d{2})日 (\d{2}):(\d{2})/);if(!c)return!0;const m=Date.UTC(Number(c[1]),Number(c[2])-1,Number(c[3]),Number(c[4]),Number(c[5]))-28800000;return Math.abs(Date.now()-m)<=93600000});if(a)return a}}return null}mt({site:"xiaohongshu",name:"publish",access:"write",description:"小红书发布图文笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:pt.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:"images",required:!1,help:"图片路径,逗号分隔,最多9张 (jpg/png/gif/webp)"},{name:"card-text",required:!1,help:`文字配图卡片文字,多张卡片用 ${G} 分隔,卡内换行用 \\n`},{name:"card-style",required:!1,help:`文字配图卡片样式,运行时按页面实际选项匹配;找不到会失败。省略时使用${S}。可选: ${X.map(([t,e])=>`${t}(${e})`).join(" ")}`},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号。平台话题库匹配不上的会自动跳过并在结果中提示,不影响发布"},{name:"draft",type:"bool",default:!1,help:"保存为草稿,不直接发布"},{name:"timeout",type:"int",default:300,help:"整段发布流程的等待预算(秒);超时后先用 creator-notes 回查,勿直接重复发布"}],columns:["status","detail","url"],func:async(t,e)=>{if(!t)throw Error("Browser page required");const n=String(e.title??"").trim(),o=e.file?String(e.file).trim():"";let r=String(e.content??"").trim();if(o){if(r)throw new b("正文位置参数和 --file 只能二选一,不能同时给");const l=M.resolve(o);if(!D.existsSync(l))throw new b(`--file 指向的文件不存在: ${l}`);r=D.readFileSync(l,"utf-8").trim();if(!r)throw new b(`--file 指向的文件内容为空: ${l}`)}const i=e.images?String(e.images).split(",").map((l)=>l.trim()).filter(Boolean):[],a=e.topics?String(e.topics).split(",").map((l)=>l.trim()).filter(Boolean):[],s=Boolean(e.draft),d=e["card-text"]?String(e["card-text"]):"",c=d?d.split(G).map((l)=>l.trim()).filter(Boolean):[],m=e["card-style"]?String(e["card-style"]).trim():"",w=c.length>0;if(!n)throw new b("--title is required");if(n.length>z)throw new b(`Title is ${n.length} chars — must be ≤ ${z}`);if(!r)throw new b("缺少笔记正文:给位置参数 <content>(字面文本),或用 --file <本机文件路径>");wt(r,{fileFlag:"--file"});if(!w&&i.length===0)throw new b("Provide --card-text (text-image mode) or --images (upload mode); neither was given.");if(i.length>V)throw new b(`Too many images: ${i.length} (max ${V})`);if(w&&i.some((l)=>M.extname(l).toLowerCase()===".gif"))throw new b("文字配图模式追加的图片不支持 .gif(编辑器图片入口只接受 jpg/jpeg/png/webp)");const f=Tt(i);await t.goto(yt);await t.wait({time:1});await t.evaluate("() => { try { window.onbeforeunload = null; } catch (e) {} location.reload(); }").catch(()=>{return});let k="";for(let l=0;l<30;l++){await t.wait({time:0.5});k=await t.evaluate("() => location.href");if(k.includes("creator.xiaohongshu.com"))break}if(!k.includes("creator.xiaohongshu.com")){await t.screenshot({path:"/tmp/xhs_publish_redirect_debug.png"});throw Error(`Redirected away from creator center (landed on ${k}) — session may have expired. `+"Re-capture browser login via: ppcli xiaohongshu creator-profile. Debug screenshot: /tmp/xhs_publish_redirect_debug.png")}const E=await Nt(t);if((await Ft(t,E?.ok?5000:2000)).state==="video_surface"){await t.screenshot({path:"/tmp/xhs_publish_tab_debug.png"});const l=E?.ok?`clicked "${E.text}"`:`visible candidates: ${(E?.visibleTexts||[]).join(" | ")||"none"}`;throw Error("Still on the video publish page after trying to select 图文. "+`Details: ${l}. Debug screenshot: /tmp/xhs_publish_tab_debug.png`)}const A=await jt(t);let C=m;if(w)C=await Jt(t,c,m);else{const l=typeof t.startNetworkCapture==="function"?await t.startNetworkCapture("").catch(()=>!1):!1,g=await tt(t,f);if(!g.ok){await t.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new u(`图片注入失败:${g.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_upload_debug.png")}await t.wait({time:K/1000});const x=await dt(t,f.length,l);if(!x.ok){await t.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new u(`图片上传未全部完成:只确认 ${x.completed}/${f.length} 张传到小红书 CDN,`+"继续发布会丢图(通常只剩封面)。直接重新运行本 publish 命令即可(会自动重置发布页重传);"+"请勿手动修改当前页面。Debug: /tmp/xhs_publish_upload_debug.png")}await et(t);if(A.length){await ct(t,A);await t.wait({time:0.5})}const N=await at(t);if(N&&typeof N.count==="number"&&N.count>f.length){await t.screenshot({path:"/tmp/xhs_publish_dup_debug.png"});throw new u(`清掉恢复态旧图后编辑器内仍有 ${N.count} 张图,超过本次注入的 ${f.length} 张。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。Debug: /tmp/xhs_publish_dup_debug.png")}}if(!await Ut(t)){await t.screenshot({path:"/tmp/xhs_publish_form_debug.png"});throw new u("Editing form did not appear after image acquisition. The page layout may have changed. Debug screenshot: /tmp/xhs_publish_form_debug.png")}if(w)await st(t,c.length,"文字配图 generated images");if(w&&f.length>0){const l=typeof t.startNetworkCapture==="function"?await t.startNetworkCapture("").catch(()=>!1):!1,g=await tt(t,f);if(!g.ok){await t.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new u(`追加图片失败:${g.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await t.wait({time:K/1000});const x=await dt(t,f.length,l);if(!x.ok){await t.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new u(`追加图片上传未全部完成:只确认 ${x.completed}/${f.length} 张传到小红书 CDN。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await et(t);await st(t,c.length+f.length,"文字配图 appended images")}if(w&&A.length){await ct(t,A);await t.wait({time:0.5})}await nt(t,F,n,"title");await v(t,0.6);await nt(t,Q,r,"content");await v(t,0.6);let R=[],I=[];if(a.length){const l=await Ct(t,Q,a);R=l.added;I=l.failed}await v(t,1,0.6);const O=await xt(t,["xhs-publish-btn"]);if(O){await gt(t,O.x,O.y);await v(t,0.4)}const U=s?["暂存离开","存草稿"]:["发布","发布笔记"],p=await t.evaluate(`
418
+ })()`))}catch{return null}if(Array.isArray(i)){const a=i.find((d)=>{const s=String(d.title||"").trim();if(!(s===n||s.startsWith(n)||n.startsWith(s)))return!1;const c=String(d.dateText||"").match(/(\d{4})年(\d{2})月(\d{2})日 (\d{2}):(\d{2})/);if(!c)return!0;const f=Date.UTC(Number(c[1]),Number(c[2])-1,Number(c[3]),Number(c[4]),Number(c[5]))-28800000;return Math.abs(Date.now()-f)<=93600000});if(a)return a}}return null}gt({site:"xiaohongshu",name:"publish",access:"write",description:"小红书发布图文笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:xt.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:"images",required:!1,help:"图片路径,逗号分隔,最多9张 (jpg/png/gif/webp)"},{name:"card-text",required:!1,help:`文字配图卡片文字,多张卡片用 ${H} 分隔,卡内换行用 \\n`},{name:"card-style",required:!1,help:`文字配图卡片样式,运行时按页面实际选项匹配;找不到会失败。省略时使用${S}。可选: ${G.map(([t,e])=>`${t}(${e})`).join(" ")}`},{name:"topics",required:!1,help:"话题名称,逗号分隔,最多10个;先用 topics 查询。无法准确选中时停止提交"},ft,{name:"draft",type:"bool",default:!1,help:"保存为草稿,不直接发布"},{name:"timeout",type:"int",default:300,help:"整段发布流程的等待预算(秒);超时后先用 creator-notes 回查,勿直接重复发布"},{name:"ai-generated",type:"boolean",help:"声明内容由 AI 生成(发布和草稿均支持;设置失败时停止提交)"},...pt],columns:["status","detail","url"],func:async(t,e)=>{bt(e);if(!t)throw Error("Browser page required");const r=String(e.title??"").trim(),n=e.file?String(e.file).trim():"";let o=String(e.content??"").trim();if(n){if(o)throw new p("正文位置参数和 --file 只能二选一,不能同时给");const l=I.resolve(n);if(!L.existsSync(l))throw new p(`--file 指向的文件不存在: ${l}`);o=L.readFileSync(l,"utf-8").trim();if(!o)throw new p(`--file 指向的文件内容为空: ${l}`)}const i=e.images?String(e.images).split(",").map((l)=>l.trim()).filter(Boolean):[],a=ht(e),d=a.map((l)=>l.name),s=Boolean(e.draft),c=e["card-text"]?String(e["card-text"]):"",f=c?c.split(H).map((l)=>l.trim()).filter(Boolean):[],y=e["card-style"]?String(e["card-style"]).trim():"",b=f.length>0;if(!r)throw new p("--title is required");if(r.length>z)throw new p(`Title is ${r.length} chars — must be ≤ ${z}`);if(!o)throw new p("缺少笔记正文:给位置参数 <content>(字面文本),或用 --file <本机文件路径>");_t(o,{fileFlag:"--file"});if(!b&&i.length===0)throw new p("Provide --card-text (text-image mode) or --images (upload mode); neither was given.");if(i.length>V)throw new p(`Too many images: ${i.length} (max ${V})`);if(b&&i.some((l)=>I.extname(l).toLowerCase()===".gif"))throw new p("文字配图模式追加的图片不支持 .gif(编辑器图片入口只接受 jpg/jpeg/png/webp)");const h=$t(i);await t.goto(Tt);await t.wait({time:1});await t.evaluate("() => { try { window.onbeforeunload = null; } catch (e) {} location.reload(); }").catch(()=>{return});let k="";for(let l=0;l<30;l++){await t.wait({time:0.5});k=await t.evaluate("() => location.href");if(k.includes("creator.xiaohongshu.com"))break}if(!k.includes("creator.xiaohongshu.com")){await t.screenshot({path:"/tmp/xhs_publish_redirect_debug.png"});throw Error(`Redirected away from creator center (landed on ${k}) — session may have expired. `+"Re-capture browser login via: ppcli xiaohongshu creator-profile. Debug screenshot: /tmp/xhs_publish_redirect_debug.png")}const A=await It(t);if((await Jt(t,A?.ok?5000:2000)).state==="video_surface"){await t.screenshot({path:"/tmp/xhs_publish_tab_debug.png"});const l=A?.ok?`clicked "${A.text}"`:`visible candidates: ${(A?.visibleTexts||[]).join(" | ")||"none"}`;throw Error("Still on the video publish page after trying to select 图文. "+`Details: ${l}. Debug screenshot: /tmp/xhs_publish_tab_debug.png`)}const E=await Vt(t);let C=y;if(b)C=await Ft(t,f,y);else{const l=typeof t.startNetworkCapture==="function"?await t.startNetworkCapture("").catch(()=>!1):!1,w=await tt(t,h);if(!w.ok){await t.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new u(`图片注入失败:${w.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_upload_debug.png")}await t.wait({time:X/1000});const x=await ct(t,h.length,l);if(!x.ok){await t.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new u(`图片上传未全部完成:只确认 ${x.completed}/${h.length} 张传到小红书 CDN,`+"继续发布会丢图(通常只剩封面)。直接重新运行本 publish 命令即可(会自动重置发布页重传);"+"请勿手动修改当前页面。Debug: /tmp/xhs_publish_upload_debug.png")}await et(t);if(E.length){await st(t,E);await t.wait({time:0.5})}const M=await ot(t);if(M&&typeof M.count==="number"&&M.count>h.length){await t.screenshot({path:"/tmp/xhs_publish_dup_debug.png"});throw new u(`清掉恢复态旧图后编辑器内仍有 ${M.count} 张图,超过本次注入的 ${h.length} 张。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。Debug: /tmp/xhs_publish_dup_debug.png")}}if(!await jt(t)){await t.screenshot({path:"/tmp/xhs_publish_form_debug.png"});throw new u("Editing form did not appear after image acquisition. The page layout may have changed. Debug screenshot: /tmp/xhs_publish_form_debug.png")}if(b)await it(t,f.length,"文字配图 generated images");if(b&&h.length>0){const l=typeof t.startNetworkCapture==="function"?await t.startNetworkCapture("").catch(()=>!1):!1,w=await tt(t,h);if(!w.ok){await t.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new u(`追加图片失败:${w.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await t.wait({time:X/1000});const x=await ct(t,h.length,l);if(!x.ok){await t.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new u(`追加图片上传未全部完成:只确认 ${x.completed}/${h.length} 张传到小红书 CDN。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await et(t);await it(t,f.length+h.length,"文字配图 appended images")}if(b&&E.length){await st(t,E);await t.wait({time:0.5})}await nt(t,U,r,"title");await N(t,0.6);await nt(t,Q,o,"content");await N(t,0.6);let R=[];if(d.length)R=await mt(t,Q,a);await wt(t,e);await N(t,1,0.6);const P=await St(t,["xhs-publish-btn"]);if(P){await vt(t,P.x,P.y);await N(t,0.4)}const F=s?["暂存离开","存草稿"]:["发布","发布笔记"],m=await t.evaluate(`
541
419
  (cfg => {
542
420
  const { isDraftMode, publishNames, draftNames, labels } = cfg;
543
421
  const isVisible = (el) => {
@@ -576,8 +454,8 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
576
454
  }
577
455
  }
578
456
  return { ok: false, via: 'none', hosts: hosts.length, lastMethodError };
579
- })(${JSON.stringify({isDraftMode:s,publishNames:_t,draftNames:vt,labels:U})})
580
- `);if(!p?.ok){if(s){if(await t.evaluate(`
457
+ })(${JSON.stringify({isDraftMode:s,publishNames:kt,draftNames:At,labels:F})})
458
+ `);if(!m?.ok){if(s){if(await t.evaluate(`
581
459
  (() => {
582
460
  const labels = ['返回', '关闭', '取消', '离开'];
583
461
  const buttons = document.querySelectorAll('button, [role="button"], div, span');
@@ -590,7 +468,7 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
590
468
  }
591
469
  return false;
592
470
  })()
593
- `)){await t.wait({time:1});const g=["暂存离开","存草稿","保存草稿"];if(await t.evaluate(`
471
+ `)){await t.wait({time:1});const w=["暂存离开","存草稿","保存草稿"];if(await t.evaluate(`
594
472
  (labels => {
595
473
  const buttons = document.querySelectorAll('button, [role="button"]');
596
474
  for (const btn of buttons) {
@@ -605,8 +483,8 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
605
483
  }
606
484
  }
607
485
  return false;
608
- })(${JSON.stringify(g)})
609
- `)){p.ok=!0;p.via="leave-save-fallback"}}if(!p?.ok){await t.wait({time:2});if(await t.evaluate(`
486
+ })(${JSON.stringify(w)})
487
+ `)){m.ok=!0;m.via="leave-save-fallback"}}if(!m?.ok){await t.wait({time:2});if(await t.evaluate(`
610
488
  () => {
611
489
  const markers = ['草稿箱(', '保存于', '编辑于'];
612
490
  for (const el of document.querySelectorAll('*')) {
@@ -615,7 +493,7 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
615
493
  }
616
494
  return false;
617
495
  }
618
- `)){p.ok=!0;p.via="auto-save"}}}}if(!p?.ok){await t.screenshot({path:"/tmp/xhs_publish_submit_debug.png"});const l=p?.via?` (via=${p.via})`:"",g=p?.error?`, error=${p.error}`:"",x=p?.lastMethodError?`, lastMethodError=${p.lastMethodError}`:"";throw Error(`未能触发「${U[0]}」按钮${l}${g}${x}。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_submit_debug.png")}await t.wait({time:4});const _=await t.evaluate("() => location.href"),ft=s?["草稿已保存","暂存成功","保存成功","保存于","图文笔记("]:["发布成功","上传成功"],j=await t.evaluate(`
496
+ `)){m.ok=!0;m.via="auto-save"}}}}if(!m?.ok){await t.screenshot({path:"/tmp/xhs_publish_submit_debug.png"});const l=m?.via?` (via=${m.via})`:"",w=m?.error?`, error=${m.error}`:"",x=m?.lastMethodError?`, lastMethodError=${m.lastMethodError}`:"";throw Error(`未能触发「${F[0]}」按钮${l}${w}${x}。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_submit_debug.png")}await t.wait({time:4});const v=await t.evaluate("() => location.href"),ut=s?["草稿已保存","暂存成功","保存成功","保存于","图文笔记("]:["发布成功","上传成功"],J=await t.evaluate(`
619
497
  (markers => {
620
498
  for (const el of document.querySelectorAll('*')) {
621
499
  if (el.tagName === 'STYLE' || el.tagName === 'SCRIPT') continue;
@@ -624,5 +502,5 @@ import*as D from"node:fs";import*as M from"node:path";import{CommandExecutionErr
624
502
  if (el.children.length === 0 && markers.some(marker => text.includes(marker))) return text;
625
503
  }
626
504
  return '';
627
- })(${JSON.stringify(ft)})
628
- `),ht=!_.includes("/publish/publish");let q=j.length>0||ht;const P=s?"暂存成功":"发布成功";let y=null;if(!s){y=await zt(t,n,q?4:8);if(y)q=!0}if(!q){if(s)throw new u(`${P}无法确认:未观察到成功提示或提交后跳转。`+(_?`当前 URL: ${_}。`:"当前 URL 为空。")+"先用 `xiaohongshu drafts` 查草稿箱确认是否已存上;确认没有再重新运行本命令,避免堆出重复草稿。");throw new u(`${P}无法确认:未观察到成功提示或提交后跳转,创作者后台也暂未查到同标题笔记。`+(_?`当前 URL: ${_}。`:"")+"注意:发布按钮已被触发,笔记仍可能延迟入库。禁止立即重发——请等 1-2 分钟后用 `xiaohongshu creator-notes` 回查本篇标题是否已出现;确认不存在后才重新运行本 publish 命令,否则会重复发布。")}return[{status:`✅ ${P}`,detail:[`"${n}"`,w?`${c.length}张文字配图${f.length?` + ${f.length}张图片`:""}${C&&C!==S?` (${C})`:""}`:`${f.length}张图片`,R.length?`话题: ${R.join(" ")}`:"",I.length?`⚠️ 话题未附上(已自动跳过,不影响发布): ${I.map((l)=>l.topic).join(" ")}`:"",y?.id?`笔记ID: ${y.id}`:"",j||(y?"已在创作者后台确认":"")||_||""].filter(Boolean).join(" · "),url:y?.id?`https://www.xiaohongshu.com/explore/${y.id}`:""}]}});
505
+ })(${JSON.stringify(ut)})
506
+ `),dt=!v.includes("/publish/publish");let D=J.length>0||dt;const q=s?"暂存成功":"发布成功";let _=null;if(!s){_=await Xt(t,r,D?4:8);if(_)D=!0}if(!D){if(s)throw new u(`${q}无法确认:未观察到成功提示或提交后跳转。`+(v?`当前 URL: ${v}。`:"当前 URL 为空。")+"先用 `xiaohongshu drafts` 查草稿箱确认是否已存上;确认没有再重新运行本命令,避免堆出重复草稿。");throw new u(`${q}无法确认:未观察到成功提示或提交后跳转,创作者后台也暂未查到同标题笔记。`+(v?`当前 URL: ${v}。`:"")+"注意:发布按钮已被触发,笔记仍可能延迟入库。禁止立即重发——请等 1-2 分钟后用 `xiaohongshu creator-notes` 回查本篇标题是否已出现;确认不存在后才重新运行本 publish 命令,否则会重复发布。")}return[{status:`✅ ${q}`,detail:[`"${r}"`,b?`${f.length}张文字配图${h.length?` + ${h.length}张图片`:""}${C&&C!==S?` (${C})`:""}`:`${h.length}张图片`,R.length?`话题: ${R.join(" ")}`:"",_?.id?`笔记ID: ${_.id}`:"",J||(_?"已在创作者后台确认":"")||v||""].filter(Boolean).join(" · "),url:_?.id?`https://www.xiaohongshu.com/explore/${_.id}`:""}]}});
@@ -0,0 +1 @@
1
+ import{cli as p,Strategy as f}from"@jackwener/opencli/registry";import{ArgumentError as a}from"@jackwener/opencli/errors";import{commentId as w,commentNote as h,expandCommentThread as y,locateComment as g,openCommentPage as _}from"./_shared/comment-state.js";p({site:"xiaohongshu",name:"replies",access:"read",description:"读取指定主评论下的楼中楼,返回精确回复关系与加载完整性",domain:"www.xiaohongshu.com",strategy:f.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"note-id",positional:!0,required:!0,help:"Full signed note URL with xsec_token"},{name:"root-id",positional:!0,required:!0,help:"Top-level comment ID from comments"},{name:"limit",type:"int",default:50,help:"Maximum nested replies to return (1-200)"}],columns:["id","reply_count","returned","complete","replies"],func:async(r,i)=>{const{url:c,noteId:n}=h(i["note-id"]),e=w(i["root-id"],"root-id"),t=Number(i.limit??50);if(!Number.isInteger(t)||t<1||t>200)throw new a("--limit must be an integer from 1 to 200");await _(r,c,n);const{target:d}=await g(r,n,e);if(d.is_reply)throw new a("root-id must identify a top-level comment");const s=await y(r,n,e,t),m=s.rows.find((o)=>o.id===e),l=s.rows.filter((o)=>o.is_reply&&o.root_id===e).slice(0,t).map(({rendered:o,...u})=>u);return[{id:e,reply_count:m.reply_count,returned:l.length,complete:m.replies_complete&&l.length>=m.reply_count,replies:l}]}});
@@ -0,0 +1 @@
1
+ import{cli as r,Strategy as o}from"@jackwener/opencli/registry";import{runComment as i,writeArgs as n,writeColumns as m}from"./_shared/comment-actions.js";r({site:"xiaohongshu",name:"reply",access:"write",description:"回复指定小红书评论(含楼中楼);默认 dry-run,--execute 才发送",domain:"www.xiaohongshu.com",strategy:o.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:"text",positional:!0,required:!0,help:"Exact plain-text reply"},...n],columns:m,func:(t,e)=>i(t,{...e,"reply-to":e["comment-id"]})});
@@ -0,0 +1 @@
1
+ import{cli as a,Strategy as s}from"@jackwener/opencli/registry";import{ArgumentError as c,CommandExecutionError as m}from"@jackwener/opencli/errors";import{prepareCreatorDiscovery as u,creatorDiscoveryRequest as l,discoveryLimit as p,listField as d}from"./_shared/publish-discovery.js";a({site:"xiaohongshu",name:"topics",access:"read",domain:"creator.xiaohongshu.com",description:"搜索发布可用话题,返回浏览量、关联活动及 publish/publish-video 可直接使用的 publish_args",strategy:s.COOKIE,navigateBefore:!1,args:[{name:"query",positional:!0,required:!0,help:"话题关键词,不带 #"},{name:"context",default:"",help:"拟发布正文,供平台排序参考"},{name:"limit",type:"int",default:20}],columns:["id","name","views","activity","publish_args"],func:async(e,t)=>{const o=p(t.limit);if(!String(t.query||"").trim())throw new c("请输入话题关键词");await u(e);const r=await l(e,"topics",{keyword:t.query,content:t.context||"",topicRoundStartTime:Date.now()});return d(r,"topics").slice(0,o).map((n)=>{const i=n.topicInfoDto;if(!i?.id||!i.name)throw new m("话题缺少 ID 或名称,停止返回不可用候选");return{id:String(i.id),name:i.name,views:i.viewNum??null,activity:i.activityInfo??null,publish_args:{topics:i.name,"topic-ids":String(i.id)}}})}});