publishport-opencli 1.8.5-pp.36 → 1.8.5-pp.37
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/clis/xiaohongshu/publish.js +73 -59
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as b from"node:fs";import*as D from"node:path";import{CommandExecutionError as H,ArgumentError as F}from"@jackwener/opencli/errors";import{cli as Vz,Strategy as Hz}from"@jackwener/opencli/registry";import{assertLiteralContent as Gz}from"../_shared/content-guard.js";const Yz="https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image",x=9,E=20,u=3000,c="|||",N="基础",d=[["基础","默认兜底,万能"],["边框","金句/要点卡"],["备忘","提醒/随手记"],["清新","日常/清单贴士"],["涂写","随笔/碎碎念"],["便签","笔记/提醒"],["光影","情绪/文艺"],["涂鸦","趣味/童话"],["简约","干货/观点"],["手写","日记/情感"],["插图","生活方式/轻松话题"],["美漫","活力/趣味/故事感"],["弥散","弥散光氛围"],["柔和","柔和/温柔金句"],["印刷","印刷海报/排版"],["科技","科技/产品"],["贺卡","节日祝福"],["札记","艺术/水彩氛围"],["书摘","书摘/引用"],["手帐","手帐拼贴"],["几何","醒目/有力主张"]],Ez=d.map(([z])=>z),R="文字配图",n="再写一张",m="生成图片",i="下一步",_=".tiptap.ProseMirror",Xz=["_onPublish","onPublish","_onSubmit","_handlePublish"],Wz=["_onSave","_onSaveDraft","_onDraft"],A=['[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]"],l=['[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])'],o={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp"};function W(z){if(z&&typeof z==="object"&&typeof z.session==="string"&&Object.prototype.hasOwnProperty.call(z,"data"))return z.data;return z}function Fz(z){return z.map((J)=>{const K=D.resolve(J);if(!b.existsSync(K))throw new F(`Image file not found: ${K}`);const Z=D.extname(K).toLowerCase();if(!o[Z])throw new F(`Unsupported image format "${Z}". Supported: jpg, png, gif, webp`);return K})}const s='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 Oz(z,J=15000){const K=500,Z=Math.max(1,Math.ceil(J/K));for(let Q=0;Q<Z;Q++){if(await z.evaluate(`
|
|
2
2
|
(() => {
|
|
3
|
-
const sels = ${JSON.stringify(
|
|
3
|
+
const sels = ${JSON.stringify(s)};
|
|
4
4
|
return !!document.querySelector(sels);
|
|
5
5
|
})()
|
|
6
|
-
`))return!0;if(Q<Z-1)await z.wait({time:K/1000})}return!1}async function
|
|
6
|
+
`))return!0;if(Q<Z-1)await z.wait({time:K/1000})}return!1}async function r(z,J){if(!await Oz(z))return{ok:!1,count:0,error:"No file input found on page (waited 15s; publish surface did not finish rendering)"};if(z.setFileInput)try{await z.setFileInput(J,s);return{ok:!0,count:J.length}}catch($){const j=$ instanceof Error?$.message:String($);if(j.includes("Unknown action")||j.includes("not supported")||j.includes("Not allowed"));else return{ok:!1,count:0,error:j}}const Z=J.map(($)=>{const j=b.readFileSync($).toString("base64"),Y=D.extname($).toLowerCase();return{name:D.basename($),mimeType:o[Y],base64:j}}),Q=Z.reduce(($,j)=>$+j.base64.length,0);if(Q>500000)console.warn(`[warn] Total image payload is ${(Q/1024/1024).toFixed(1)}MB (base64). This may fail with the browser bridge. Update the extension to v1.6+ for CDP-based upload, or compress images before publishing.`);const q=JSON.stringify(Z);return z.evaluate(`
|
|
7
7
|
(async () => {
|
|
8
8
|
const images = ${q};
|
|
9
9
|
|
|
@@ -35,17 +35,19 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// configurable:true 让同一个 input 能被反复注入(重试 / 追加时 SPA 常复用同一元素,
|
|
39
|
+
// 否则第二次 defineProperty 会抛 "Cannot redefine property: files")。
|
|
40
|
+
Object.defineProperty(input, 'files', { value: dt.files, writable: false, configurable: true });
|
|
39
41
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
40
42
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
41
43
|
|
|
42
44
|
return { ok: true, count: dt.files.length };
|
|
43
45
|
})()
|
|
44
|
-
`)}async function
|
|
46
|
+
`)}async function g(z,J=30000){const K=2000,Z=Math.ceil(J/K);for(let Q=0;Q<Z;Q++){if(!await z.evaluate(`
|
|
45
47
|
() => !!document.querySelector(
|
|
46
48
|
'[class*="upload"][class*="progress"], [class*="uploading"], [class*="loading"][class*="image"]'
|
|
47
49
|
)
|
|
48
|
-
`))return;await z.wait({time:K/1000})}}async function
|
|
50
|
+
`))return;await z.wait({time:K/1000})}}async function p(z,J,K,Z){const Q=await z.evaluate(`
|
|
49
51
|
(function(selectors) {
|
|
50
52
|
const __opencli_xhs_fill_phase = "locate";
|
|
51
53
|
for (const sel of selectors) {
|
|
@@ -167,7 +169,7 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
167
169
|
const actual = normalize(el.innerText || el.textContent || '');
|
|
168
170
|
return { ok: actual === normalize(expectedText), actual };
|
|
169
171
|
})(${JSON.stringify(Q.sel)}, ${JSON.stringify(K)})
|
|
170
|
-
`)}catch{$=await q()}}else $=await q();if(!$?.ok){await z.screenshot({path:`/tmp/xhs_publish_${Z}_debug.png`});const j=typeof $?.actual==="string"?$.actual:"";throw Error(`Failed to set ${Z}. Expected "${K}", got "${j}". Debug screenshot: /tmp/xhs_publish_${Z}_debug.png`)}}async function
|
|
172
|
+
`)}catch{$=await q()}}else $=await q();if(!$?.ok){await z.screenshot({path:`/tmp/xhs_publish_${Z}_debug.png`});const j=typeof $?.actual==="string"?$.actual:"";throw Error(`Failed to set ${Z}. Expected "${K}", got "${j}". Debug screenshot: /tmp/xhs_publish_${Z}_debug.png`)}}async function Uz(z,J){return W(await z.evaluate(`
|
|
171
173
|
(selectors => {
|
|
172
174
|
const el = selectors
|
|
173
175
|
.map(sel => Array.from(document.querySelectorAll(sel)))
|
|
@@ -183,7 +185,7 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
183
185
|
selection?.addRange(range);
|
|
184
186
|
return true;
|
|
185
187
|
})(${JSON.stringify(J)})
|
|
186
|
-
`))}function
|
|
188
|
+
`))}function uz(z,{click:J=!1}={}){return`
|
|
187
189
|
(topicName => {
|
|
188
190
|
const norm = (value) => (value || '').replace(/^#/, '').replace(/\\s+/g, '').trim();
|
|
189
191
|
const want = norm(topicName);
|
|
@@ -223,7 +225,7 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
223
225
|
text: (target.innerText || target.textContent || '').trim().slice(0, 40),
|
|
224
226
|
};
|
|
225
227
|
})(${JSON.stringify(z)})
|
|
226
|
-
`}function
|
|
228
|
+
`}function cz(z,J){return`
|
|
227
229
|
((topicName, selectors) => {
|
|
228
230
|
const norm = (value) => (value || '').replace(/^#/, '').replace(/\\s+/g, '').trim();
|
|
229
231
|
const want = norm(topicName);
|
|
@@ -254,7 +256,7 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
254
256
|
}
|
|
255
257
|
return count;
|
|
256
258
|
})(${JSON.stringify(z)}, ${JSON.stringify(J)})
|
|
257
|
-
`}function
|
|
259
|
+
`}function t(z,J){return`
|
|
258
260
|
((topicName, selectors) => {
|
|
259
261
|
const __opencli_xhs_topic_marker_count = true;
|
|
260
262
|
const marker = '#' + topicName + '[话题]';
|
|
@@ -272,14 +274,14 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
272
274
|
}
|
|
273
275
|
return count;
|
|
274
276
|
})(${JSON.stringify(z)}, ${JSON.stringify(J)})
|
|
275
|
-
`}async function
|
|
277
|
+
`}async function dz(z,J){const K=`#${J}`;if(typeof z.insertText==="function")try{await z.insertText(K);return!0}catch{}return W(await z.evaluate(`
|
|
276
278
|
(text => {
|
|
277
279
|
const ok = document.execCommand('insertText', false, text);
|
|
278
280
|
const active = document.activeElement;
|
|
279
281
|
if (active) active.dispatchEvent(new Event('input', { bubbles: true }));
|
|
280
282
|
return ok;
|
|
281
283
|
})(${JSON.stringify(K)})
|
|
282
|
-
`))}async function
|
|
284
|
+
`))}async function Bz(z,J,K){const Z=[];for(const Q of K){if(!await Uz(z,J))throw new H(`Could not attach topic "${Q}": body editor not found`);const $=Number(W(await z.evaluate(t(Q,J))))||0;if(typeof z.pressKey==="function")try{await z.pressKey("Enter")}catch{}if(typeof z.insertText!=="function")throw new H(`Could not attach topic "${Q}": page.insertText is unavailable`);try{await z.insertText(`#${Q}`)}catch{throw new H(`Could not attach topic "${Q}": failed to type inline topic query`)}await z.wait({time:1.2});if(typeof z.pressKey!=="function")throw new H(`Could not attach topic "${Q}": page.pressKey is unavailable`);try{await z.pressKey("Enter")}catch(Y){throw new H(`Could not attach topic "${Q}": failed to accept suggestion (${Y&&Y.message||Y})`)}await z.wait({time:0.8});if((Number(W(await z.evaluate(t(Q,J))))||0)<=$)throw new H(`Could not attach topic "${Q}": no real topic entity appeared after selection`);Z.push(Q);await z.wait({time:0.4})}return Z}async function vz(z){const J=await z.evaluate(`
|
|
283
285
|
() => {
|
|
284
286
|
const isVisible = (el) => {
|
|
285
287
|
if (!el || el.offsetParent === null) return false;
|
|
@@ -328,7 +330,7 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
328
330
|
}
|
|
329
331
|
return { ok: false, visibleTexts };
|
|
330
332
|
}
|
|
331
|
-
`);if(J?.ok)await z.wait({time:1});return J}async function
|
|
333
|
+
`);if(J?.ok)await z.wait({time:1});return J}async function k(z,J,K=7000){const Z=500,Q=Math.max(1,Math.ceil(K/Z));let q={ok:!1};for(let $=0;$<Q;$++){q=W(await z.evaluate(`
|
|
332
334
|
((cfg) => {
|
|
333
335
|
const __opencli_xhs_click_label = { wantLabel: ${JSON.stringify(J)} };
|
|
334
336
|
const wantLabel = ${JSON.stringify(J)};
|
|
@@ -363,10 +365,10 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
363
365
|
}
|
|
364
366
|
return { ok: false };
|
|
365
367
|
})()
|
|
366
|
-
`));if(q?.ok)return q;await z.wait({time:Z/1000})}return q}async function
|
|
368
|
+
`));if(q?.ok)return q;await z.wait({time:Z/1000})}return q}async function Dz(z){const J=await z.evaluate(`
|
|
367
369
|
(() => {
|
|
368
370
|
const __opencli_xhs_focus_card = true;
|
|
369
|
-
const sel = ${JSON.stringify(
|
|
371
|
+
const sel = ${JSON.stringify(_)};
|
|
370
372
|
const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
|
|
371
373
|
// Prefer the editor inside the active swiper slide if present.
|
|
372
374
|
const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
|
|
@@ -380,34 +382,34 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
380
382
|
selection?.addRange(range);
|
|
381
383
|
return { ok: true };
|
|
382
384
|
})()
|
|
383
|
-
`);return
|
|
385
|
+
`);return W(J)}async function fz(z){const J=await z.evaluate(`
|
|
384
386
|
(() => {
|
|
385
387
|
const __opencli_xhs_card_text = true;
|
|
386
|
-
const sel = ${JSON.stringify(
|
|
388
|
+
const sel = ${JSON.stringify(_)};
|
|
387
389
|
const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
|
|
388
390
|
const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
|
|
389
391
|
const text = active ? (active.innerText || active.textContent || '').trim() : '';
|
|
390
392
|
return { ok: !!text, text };
|
|
391
393
|
})()
|
|
392
|
-
`);return
|
|
394
|
+
`);return W(J)}async function a(z){const J=await z.evaluate(`
|
|
393
395
|
(() => {
|
|
394
396
|
const __opencli_xhs_card_count = true;
|
|
395
|
-
const sel = ${JSON.stringify(
|
|
397
|
+
const sel = ${JSON.stringify(_)};
|
|
396
398
|
const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
|
|
397
399
|
const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
|
|
398
400
|
const activeText = active ? (active.innerText || active.textContent || '').trim() : '';
|
|
399
401
|
return { ok: true, count: editors.length, activeEmpty: !activeText };
|
|
400
402
|
})()
|
|
401
|
-
`);return
|
|
403
|
+
`);return W(J)}async function Nz(z,J=8000){const K=300,Z=Math.ceil(J/K);for(let Q=0;Q<Z;Q++){if((await a(z))?.count>=1)return!0;await z.wait({time:K/1000})}return!1}async function kz(z,J,K=6000){const Z=300,Q=Math.ceil(K/Z);for(let q=0;q<Q;q++){const $=await a(z);if($?.count>=J&&$?.activeEmpty)return!0;await z.wait({time:Z/1000})}return!1}async function hz(z,J,K=4){for(let Z=0;Z<K;Z++){if(!(await k(z,n))?.ok)return!1;if(await kz(z,J,2500))return!0}return!1}async function Lz(z){const J=await z.evaluate(`
|
|
402
404
|
(() => {
|
|
403
405
|
const __opencli_xhs_preview_ready = true;
|
|
404
406
|
const ready = Array.from(document.querySelectorAll('button'))
|
|
405
407
|
.some((b) => b.offsetParent !== null && (b.innerText || '').replace(/\\s+/g, '') === '下一步');
|
|
406
408
|
return { ok: ready };
|
|
407
409
|
})()
|
|
408
|
-
`);return
|
|
409
|
-
`);if(!(await
|
|
410
|
-
`);for(let $=0;$<q.length;$++){if($>0&&typeof z.pressKey==="function")await z.pressKey("Enter");if(q[$])await z.insertText(q[$])}}else await z.evaluate(`(t => document.execCommand('insertText', false, t))(${JSON.stringify(J)})`);await z.wait({time:0.4});if(!(await
|
|
410
|
+
`);return W(J)}async function Pz(z,J=6){const K=400,Z=Math.ceil(3000/K);for(let Q=0;Q<J;Q++){if(!(await k(z,m))?.ok&&Q===0)return!1;for(let $=0;$<Z;$++){if((await Lz(z))?.ok)return!0;await z.wait({time:K/1000})}}return!1}async function Iz(z,J,K){J=String(J).replace(/\\n/g,`
|
|
411
|
+
`);if(!(await Dz(z))?.ok)throw new H(`文字配图: could not focus card editor #${K+1}`);if(typeof z.insertText==="function"){const q=J.split(`
|
|
412
|
+
`);for(let $=0;$<q.length;$++){if($>0&&typeof z.pressKey==="function")await z.pressKey("Enter");if(q[$])await z.insertText(q[$])}}else await z.evaluate(`(t => document.execCommand('insertText', false, t))(${JSON.stringify(J)})`);await z.wait({time:0.4});if(!(await fz(z))?.ok)throw new H(`文字配图: card editor #${K+1} is empty after typing`)}async function Mz(z,J){if(!J||J===N)return N;const K=W(await z.evaluate(`
|
|
411
413
|
(async () => {
|
|
412
414
|
const __opencli_xhs_card_styles = true;
|
|
413
415
|
const want = ${JSON.stringify(J)};
|
|
@@ -436,7 +438,7 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
436
438
|
}
|
|
437
439
|
return { ok: seen.length > 0, styles: seen, found: false };
|
|
438
440
|
})()
|
|
439
|
-
`));if(!K?.found)throw new H(`文字配图: requested style "${J}" is not available for this content (options: ${(K?.styles||[]).join(" / ")||"none"}). Choose an available style or omit --card-style to use ${
|
|
441
|
+
`));if(!K?.found)throw new H(`文字配图: requested style "${J}" is not available for this content (options: ${(K?.styles||[]).join(" / ")||"none"}). Choose an available style or omit --card-style to use ${N}.`);if(!(await k(z,J))?.ok)throw new H(`文字配图: could not click requested style "${J}".`);await z.wait({time:0.6});return J}async function e(z){const J=await z.evaluate(`
|
|
440
442
|
(() => {
|
|
441
443
|
const __opencli_xhs_composer_media_count = true;
|
|
442
444
|
const visibleBox = (el) => {
|
|
@@ -449,7 +451,7 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
449
451
|
const r = el.getBoundingClientRect();
|
|
450
452
|
return r.width >= 48 && r.height >= 48;
|
|
451
453
|
};
|
|
452
|
-
const titleSelectors = ${JSON.stringify(
|
|
454
|
+
const titleSelectors = ${JSON.stringify(A)};
|
|
453
455
|
const titleEl = titleSelectors
|
|
454
456
|
.map((sel) => Array.from(document.querySelectorAll(sel)))
|
|
455
457
|
.flat()
|
|
@@ -468,7 +470,7 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
468
470
|
}
|
|
469
471
|
return { ok: true, count };
|
|
470
472
|
})()
|
|
471
|
-
`);return
|
|
473
|
+
`);return W(J)}async function zz(z,J,K){const Z=await e(z);if(!Z||typeof Z.count!=="number")throw new H(`${K}: could not verify current composer media count`);if(Z.count<J){await z.screenshot({path:"/tmp/xhs_publish_media_debug.png"});throw new H(`${K}: expected at least ${J} visible media item(s), got ${Z.count}. Debug screenshot: /tmp/xhs_publish_media_debug.png`)}}async function wz(z,J,K){if(!(await k(z,R))?.ok){await z.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new H(`文字配图: could not click "${R}" entry. Debug: /tmp/xhs_publish_textimage_debug.png`)}if(!await Nz(z)){await z.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new H(`文字配图: 写文字 card editor did not appear after clicking "${R}". Debug: /tmp/xhs_publish_textimage_debug.png`)}for(let j=0;j<J.length;j++){if(j>0){if(!await hz(z,j+1)){await z.screenshot({path:"/tmp/xhs_publish_addcard_debug.png"});throw new H(`文字配图: new card editor #${j+1} did not render after "${n}". Debug: /tmp/xhs_publish_addcard_debug.png`)}}await Iz(z,J[j],j)}if(!await Pz(z)){await z.screenshot({path:"/tmp/xhs_publish_generate_debug.png"});throw new H(`文字配图: "${m}" did not advance to the 预览图片 step. `+"Debug: /tmp/xhs_publish_generate_debug.png")}const q=await Mz(z,K);if(!(await k(z,i))?.ok){await z.screenshot({path:"/tmp/xhs_publish_next_debug.png"});throw new H(`文字配图: could not click "${i}". Debug: /tmp/xhs_publish_next_debug.png`)}await z.wait({time:2});return q}async function Jz(z){return z.evaluate(`
|
|
472
474
|
() => {
|
|
473
475
|
const text = (document.body?.innerText || '').replace(/s+/g, ' ').trim();
|
|
474
476
|
const hasTitleInput = !!Array.from(document.querySelectorAll('input, textarea')).find((el) => {
|
|
@@ -498,41 +500,53 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
498
500
|
const state = hasTitleInput ? 'editor_ready' : hasImageInput || !hasVideoSurface ? 'image_surface' : 'video_surface';
|
|
499
501
|
return { state, hasTitleInput, hasImageInput, hasVideoSurface };
|
|
500
502
|
}
|
|
501
|
-
`)}async function
|
|
503
|
+
`)}async function bz(z,J=5000){const K=500,Z=Math.max(1,Math.ceil(J/K));let Q=await Jz(z);for(let q=0;q<Z;q++){if(Q.state!=="video_surface")return Q;if(q<Z-1){await z.wait({time:K/1000});Q=await Jz(z)}}return Q}async function yz(z,J=1e4){const K=1000,Z=Math.ceil(J/K);for(let Q=0;Q<Z;Q++){if(await z.evaluate(`
|
|
502
504
|
(() => {
|
|
503
|
-
const sels = ${JSON.stringify(
|
|
505
|
+
const sels = ${JSON.stringify(A)};
|
|
504
506
|
for (const sel of sels) {
|
|
505
507
|
const el = document.querySelector(sel);
|
|
506
508
|
if (el && el.offsetParent !== null) return true;
|
|
507
509
|
}
|
|
508
510
|
return false;
|
|
509
|
-
})()`))return!0;if(Q<Z-1)await z.wait({time:K/1000})}return!1}async function
|
|
511
|
+
})()`))return!0;if(Q<Z-1)await z.wait({time:K/1000})}return!1}async function Rz(z){const J=W(await z.evaluate(`
|
|
510
512
|
(() => {
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
return { restored, cleanSurface };
|
|
513
|
+
const __opencli_xhs_restore_srcs = true;
|
|
514
|
+
return Array.from(document.querySelectorAll('.img-container img'))
|
|
515
|
+
.map((img) => img.currentSrc || img.src || '')
|
|
516
|
+
.filter(Boolean);
|
|
516
517
|
})()
|
|
517
|
-
`));
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
518
|
+
`));return Array.isArray(J)?J:[]}async function Kz(z,J){if(!Array.isArray(J)||J.length===0)return 0;const K=W(await z.evaluate(`
|
|
519
|
+
(async (wanted) => {
|
|
520
|
+
const __opencli_xhs_delete_by_src = true;
|
|
521
|
+
const want = new Set(wanted);
|
|
522
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
523
|
+
let removed = 0;
|
|
524
|
+
// guard 上限防死循环;每轮只删一张(删后 DOM 重排,重新查询)。
|
|
525
|
+
for (let guard = 0; guard < 60; guard++) {
|
|
526
|
+
const cards = Array.from(document.querySelectorAll('.img-container'));
|
|
527
|
+
const target = cards.find((c) => {
|
|
528
|
+
const img = c.querySelector('img');
|
|
529
|
+
const s = img ? (img.currentSrc || img.src || '') : '';
|
|
530
|
+
return want.has(s);
|
|
531
|
+
});
|
|
532
|
+
if (!target) break;
|
|
533
|
+
const img = target.querySelector('img');
|
|
534
|
+
// .close-btn 藏在 hover-mask 里,先补 hover 事件让它显形再点。
|
|
535
|
+
['pointerover', 'mouseover', 'mouseenter', 'mousemove'].forEach((t) => {
|
|
536
|
+
target.dispatchEvent(new MouseEvent(t, { bubbles: true }));
|
|
537
|
+
if (img) img.dispatchEvent(new MouseEvent(t, { bubbles: true }));
|
|
538
|
+
});
|
|
539
|
+
const b = target.querySelector('.close-btn');
|
|
540
|
+
if (!b) break;
|
|
541
|
+
b.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
|
|
542
|
+
b.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
|
|
543
|
+
b.click();
|
|
544
|
+
removed += 1;
|
|
545
|
+
await sleep(350);
|
|
546
|
+
}
|
|
547
|
+
return removed;
|
|
548
|
+
})(${JSON.stringify(J)})
|
|
549
|
+
`));return Number(K)||0}function _z(z){const J=String(z&&(z.url||z.key)||""),K=String(z&&z.method||"").toUpperCase(),Z=Number(z&&z.status||0);if(K!=="PUT")return null;if(!/ros-upload[^/]*\/spectrum\//.test(J))return null;if(Z<200||Z>=300)return null;const Q=J.match(/\/spectrum\/([^?#]+)/);return Q?Q[1]:J}function Qz(z){return Math.min(45,6+z*4)}async function Zz(z,J,K,Z=90000){if(J<=0)return{ok:!0,completed:0};if(!K||typeof z.readNetworkCapture!=="function"){await z.wait({time:Qz(J)});return{ok:!0,completed:null,viaFallback:!0}}const Q=1500,$=Math.ceil(9000/Q),j=Math.ceil(Z/Q),Y=new Set;for(let U=0;U<j;U++){const f=await z.readNetworkCapture().catch(()=>[]);for(const B of Array.isArray(f)?f:[]){const G=_z(B);if(G)Y.add(G)}if(Y.size>=J)return{ok:!0,completed:Y.size};if(U>=$&&Y.size===0){await z.wait({time:Qz(J)});return{ok:!0,completed:null,viaFallback:!0}}await z.wait({time:Q/1000})}return{ok:!1,completed:Y.size}}Vz({site:"xiaohongshu",name:"publish",access:"write",description:"小红书发布图文笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:Hz.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:`文字配图卡片文字,多张卡片用 ${c} 分隔,卡内换行用 \\n`},{name:"card-style",required:!1,help:`文字配图卡片样式,运行时按页面实际选项匹配;找不到会失败。省略时使用${N}。可选: ${d.map(([z,J])=>`${z}(${J})`).join(" ")}`},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号"},{name:"draft",type:"bool",default:!1,help:"保存为草稿,不直接发布"}],columns:["status","detail"],func:async(z,J)=>{if(!z)throw Error("Browser page required");const K=String(J.title??"").trim(),Z=J.file?String(J.file).trim():"";let Q=String(J.content??"").trim();if(Z){if(Q)throw new F("正文位置参数和 --file 只能二选一,不能同时给");const V=D.resolve(Z);if(!b.existsSync(V))throw new F(`--file 指向的文件不存在: ${V}`);Q=b.readFileSync(V,"utf-8").trim();if(!Q)throw new F(`--file 指向的文件内容为空: ${V}`)}const q=J.images?String(J.images).split(",").map((V)=>V.trim()).filter(Boolean):[],$=J.topics?String(J.topics).split(",").map((V)=>V.trim()).filter(Boolean):[],j=Boolean(J.draft),Y=J["card-text"]?String(J["card-text"]):"",U=Y?Y.split(c).map((V)=>V.trim()).filter(Boolean):[],f=J["card-style"]?String(J["card-style"]).trim():"",B=U.length>0;if(!K)throw new F("--title is required");if(K.length>E)throw new F(`Title is ${K.length} chars — must be ≤ ${E}`);if(!Q)throw new F("缺少笔记正文:给位置参数 <content>(字面文本),或用 --file <本机文件路径>");Gz(Q,{fileFlag:"--file"});if(!B&&q.length===0)throw new F("Provide --card-text (text-image mode) or --images (upload mode); neither was given.");if(q.length>x)throw new F(`Too many images: ${q.length} (max ${x})`);if(B&&q.some((V)=>D.extname(V).toLowerCase()===".gif"))throw new F("文字配图模式追加的图片不支持 .gif(编辑器图片入口只接受 jpg/jpeg/png/webp)");const G=Fz(q);await z.goto(Yz);await z.wait({time:1});await z.evaluate("() => { try { window.onbeforeunload = null; } catch (e) {} location.reload(); }").catch(()=>{return});let h="";for(let V=0;V<30;V++){await z.wait({time:0.5});h=await z.evaluate("() => location.href");if(h.includes("creator.xiaohongshu.com"))break}if(!h.includes("creator.xiaohongshu.com")){await z.screenshot({path:"/tmp/xhs_publish_redirect_debug.png"});throw Error(`Redirected away from creator center (landed on ${h}) — session may have expired. `+"Re-capture browser login via: opencli xiaohongshu creator-profile. Debug screenshot: /tmp/xhs_publish_redirect_debug.png")}const L=await vz(z);if((await bz(z,L?.ok?5000:2000)).state==="video_surface"){await z.screenshot({path:"/tmp/xhs_publish_tab_debug.png"});const V=L?.ok?`clicked "${L.text}"`:`visible candidates: ${(L?.visibleTexts||[]).join(" | ")||"none"}`;throw Error("Still on the video publish page after trying to select 图文. "+`Details: ${V}. Debug screenshot: /tmp/xhs_publish_tab_debug.png`)}const P=await Rz(z);let I=f;if(B)I=await wz(z,U,f);else{const V=typeof z.startNetworkCapture==="function"?await z.startNetworkCapture("").catch(()=>!1):!1,O=await r(z,G);if(!O.ok){await z.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new H(`图片注入失败:${O.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_upload_debug.png")}await z.wait({time:u/1000});const v=await Zz(z,G.length,V);if(!v.ok){await z.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new H(`图片上传未全部完成:只确认 ${v.completed}/${G.length} 张传到小红书 CDN,`+"继续发布会丢图(通常只剩封面)。直接重新运行本 publish 命令即可(会自动重置发布页重传);"+"请勿手动修改当前页面。Debug: /tmp/xhs_publish_upload_debug.png")}await g(z);if(P.length){await Kz(z,P);await z.wait({time:0.5})}const w=await e(z);if(w&&typeof w.count==="number"&&w.count>G.length){await z.screenshot({path:"/tmp/xhs_publish_dup_debug.png"});throw new H(`清掉恢复态旧图后编辑器内仍有 ${w.count} 张图,超过本次注入的 ${G.length} 张。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。Debug: /tmp/xhs_publish_dup_debug.png")}}if(!await yz(z)){await z.screenshot({path:"/tmp/xhs_publish_form_debug.png"});throw new H("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 zz(z,U.length,"文字配图 generated images");if(B&&G.length>0){const V=typeof z.startNetworkCapture==="function"?await z.startNetworkCapture("").catch(()=>!1):!1,O=await r(z,G);if(!O.ok){await z.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new H(`追加图片失败:${O.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await z.wait({time:u/1000});const v=await Zz(z,G.length,V);if(!v.ok){await z.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new H(`追加图片上传未全部完成:只确认 ${v.completed}/${G.length} 张传到小红书 CDN。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await g(z);await zz(z,U.length+G.length,"文字配图 appended images")}if(B&&P.length){await Kz(z,P);await z.wait({time:0.5})}await p(z,A,K,"title");await z.wait({time:0.5});await p(z,l,Q,"content");await z.wait({time:0.5});let y=[];if($.length)y=await Bz(z,l,$);const T=j?["暂存离开","存草稿"]:["发布","发布笔记"],X=await z.evaluate(`
|
|
536
550
|
(cfg => {
|
|
537
551
|
const { isDraftMode, publishNames, draftNames, labels } = cfg;
|
|
538
552
|
const isVisible = (el) => {
|
|
@@ -571,8 +585,8 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
571
585
|
}
|
|
572
586
|
}
|
|
573
587
|
return { ok: false, via: 'none', hosts: hosts.length, lastMethodError };
|
|
574
|
-
})(${JSON.stringify({isDraftMode:j,publishNames:
|
|
575
|
-
`);if(!
|
|
588
|
+
})(${JSON.stringify({isDraftMode:j,publishNames:Xz,draftNames:Wz,labels:T})})
|
|
589
|
+
`);if(!X?.ok){if(j){if(await z.evaluate(`
|
|
576
590
|
(() => {
|
|
577
591
|
const labels = ['返回', '关闭', '取消', '离开'];
|
|
578
592
|
const buttons = document.querySelectorAll('button, [role="button"], div, span');
|
|
@@ -601,7 +615,7 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
601
615
|
}
|
|
602
616
|
return false;
|
|
603
617
|
})(${JSON.stringify(O)})
|
|
604
|
-
`)){
|
|
618
|
+
`)){X.ok=!0;X.via="leave-save-fallback"}}if(!X?.ok){await z.wait({time:2});if(await z.evaluate(`
|
|
605
619
|
() => {
|
|
606
620
|
const markers = ['草稿箱(', '保存于', '编辑于'];
|
|
607
621
|
for (const el of document.querySelectorAll('*')) {
|
|
@@ -610,7 +624,7 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
610
624
|
}
|
|
611
625
|
return false;
|
|
612
626
|
}
|
|
613
|
-
`)){
|
|
627
|
+
`)){X.ok=!0;X.via="auto-save"}}}}if(!X?.ok){await z.screenshot({path:"/tmp/xhs_publish_submit_debug.png"});const V=X?.via?` (via=${X.via})`:"",O=X?.error?`, error=${X.error}`:"",v=X?.lastMethodError?`, lastMethodError=${X.lastMethodError}`:"";throw Error(`未能触发「${T[0]}」按钮${V}${O}${v}。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_submit_debug.png")}await z.wait({time:4});const M=await z.evaluate("() => location.href"),$z=j?["草稿已保存","暂存成功","保存成功","保存于","图文笔记("]:["发布成功","上传成功"],S=await z.evaluate(`
|
|
614
628
|
(markers => {
|
|
615
629
|
for (const el of document.querySelectorAll('*')) {
|
|
616
630
|
if (el.tagName === 'STYLE' || el.tagName === 'SCRIPT') continue;
|
|
@@ -619,5 +633,5 @@ import*as y from"node:fs";import*as B from"node:path";import{CommandExecutionErr
|
|
|
619
633
|
if (el.children.length === 0 && markers.some(marker => text.includes(marker))) return text;
|
|
620
634
|
}
|
|
621
635
|
return '';
|
|
622
|
-
})(${JSON.stringify(
|
|
623
|
-
`),
|
|
636
|
+
})(${JSON.stringify($z)})
|
|
637
|
+
`),qz=!M.includes("/publish/publish"),jz=S.length>0||qz,C=j?"暂存成功":"发布成功";if(!jz)throw new H(`${C}无法确认:未观察到成功提示或提交后跳转。`+(M?`当前 URL: ${M}。`:"当前 URL 为空。")+"若确属失败,直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。");return[{status:`✅ ${C}`,detail:[`"${K}"`,B?`${U.length}张文字配图${G.length?` + ${G.length}张图片`:""}${I&&I!==N?` (${I})`:""}`:`${G.length}张图片`,y.length?`话题: ${y.join(" ")}`:"",S||M||""].filter(Boolean).join(" · ")}]}});
|