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
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import*as
|
|
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(
|
|
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:
|
|
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
|
-
`),
|
|
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
|
|
26
|
-
`+"请勿直接重试同一条命令,先看编辑器当前状态:最可能是有话题/表情联想浮层开着,"+"此时 Enter 被浮层当成「选中候选」吃掉。正文里如果带 # 号尤其容易触发。")}async function nt(t,e,n
|
|
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(!
|
|
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(
|
|
98
|
-
`);let a;if(
|
|
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(
|
|
119
|
-
`))?.ok){await t.screenshot({path:`/tmp/xhs_publish_${
|
|
120
|
-
`);for(let c=0;c<
|
|
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(
|
|
143
|
-
`)}catch(
|
|
144
|
-
`))throw new u(`写入${
|
|
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,
|
|
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:
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
393
|
-
`);if(!(await
|
|
394
|
-
`);for(let a=0;a<i.length;a++){if(a>0&&typeof t.pressKey==="function")await t.pressKey("Enter");if(i[a])await
|
|
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(!
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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((
|
|
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:
|
|
580
|
-
`);if(!
|
|
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
|
|
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(
|
|
609
|
-
`)){
|
|
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
|
-
`)){
|
|
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(
|
|
628
|
-
`),
|
|
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)}}})}});
|