publishport-opencli 1.0.7 → 1.0.9

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.
@@ -1,9 +1,9 @@
1
- import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionError as V,ArgumentError as F}from"@jackwener/opencli/errors";import{cli as Yz,Strategy as Xz}from"@jackwener/opencli/registry";import{assertLiteralContent as Wz}from"../_shared/content-guard.js";import{humanType as A,humanWait as N,humanMoveTo as Fz,elementViewportCenter as Oz}from"./human.js";const Uz="https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image",u=9,d=20,n=3000,m="|||",k="基础",i=[["基础","默认兜底,万能"],["边框","金句/要点卡"],["备忘","提醒/随手记"],["清新","日常/清单贴士"],["涂写","随笔/碎碎念"],["便签","笔记/提醒"],["光影","情绪/文艺"],["涂鸦","趣味/童话"],["简约","干货/观点"],["手写","日记/情感"],["插图","生活方式/轻松话题"],["美漫","活力/趣味/故事感"],["弥散","弥散光氛围"],["柔和","柔和/温柔金句"],["印刷","印刷海报/排版"],["科技","科技/产品"],["贺卡","节日祝福"],["札记","艺术/水彩氛围"],["书摘","书摘/引用"],["手帐","手帐拼贴"],["几何","醒目/有力主张"]],iz=i.map(([z])=>z),T="文字配图",l="再写一张",o="生成图片",s="下一步",S=".tiptap.ProseMirror",vz=["_onPublish","onPublish","_onSubmit","_handlePublish"],Bz=["_onSave","_onSaveDraft","_onDraft"],C=['[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]"],r=['[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])'],g={".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 Dz(z){return z.map((J)=>{const K=D.resolve(J);if(!_.existsSync(K))throw new F(`Image file not found: ${K}`);const Z=D.extname(K).toLowerCase();if(!g[Z])throw new F(`Unsupported image format "${Z}". Supported: jpg, png, gif, webp`);return K})}const p='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 fz(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(`
1
+ import*as w from"node:fs";import*as I from"node:path";import{CommandExecutionError as H,ArgumentError as U}from"@jackwener/opencli/errors";import{cli as Fz,Strategy as Oz}from"@jackwener/opencli/registry";import{assertLiteralContent as Uz}from"../_shared/content-guard.js";import{humanType as x,humanWait as P,humanMoveTo as Bz,elementViewportCenter as vz}from"./human.js";const Dz="https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image",n=9,l=20,i=3000,o="|||",M="基础",s=[["基础","默认兜底,万能"],["边框","金句/要点卡"],["备忘","提醒/随手记"],["清新","日常/清单贴士"],["涂写","随笔/碎碎念"],["便签","笔记/提醒"],["光影","情绪/文艺"],["涂鸦","趣味/童话"],["简约","干货/观点"],["手写","日记/情感"],["插图","生活方式/轻松话题"],["美漫","活力/趣味/故事感"],["弥散","弥散光氛围"],["柔和","柔和/温柔金句"],["印刷","印刷海报/排版"],["科技","科技/产品"],["贺卡","节日祝福"],["札记","艺术/水彩氛围"],["书摘","书摘/引用"],["手帐","手帐拼贴"],["几何","醒目/有力主张"]],rz=s.map(([z])=>z),E="文字配图",g="再写一张",r="生成图片",p="下一步",u=".tiptap.ProseMirror",kz=["_onPublish","onPublish","_onSubmit","_handlePublish"],Nz=["_onSave","_onSaveDraft","_onDraft"],m=['[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]"],t=['[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])'],a={".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 Iz(z){return z.map((J)=>{const K=I.resolve(J);if(!w.existsSync(K))throw new U(`Image file not found: ${K}`);const Q=I.extname(K).toLowerCase();if(!a[Q])throw new U(`Unsupported image format "${Q}". Supported: jpg, png, gif, webp`);return K})}const e='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 Pz(z,J=15000){const K=500,Q=Math.max(1,Math.ceil(J/K));for(let Z=0;Z<Q;Z++){if(await z.evaluate(`
2
2
  (() => {
3
- const sels = ${JSON.stringify(p)};
3
+ const sels = ${JSON.stringify(e)};
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 t(z,J){if(!await fz(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,p);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=_.readFileSync($).toString("base64"),Y=D.extname($).toLowerCase();return{name:D.basename($),mimeType:g[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(`
6
+ `))return!0;if(Z<Q-1)await z.wait({time:K/1000})}return!1}async function zz(z,J){if(!await Pz(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,e);return{ok:!0,count:J.length}}catch($){const V=$ instanceof Error?$.message:String($);if(V.includes("Unknown action")||V.includes("not supported")||V.includes("Not allowed"));else return{ok:!1,count:0,error:V}}const Q=J.map(($)=>{const V=w.readFileSync($).toString("base64"),Y=I.extname($).toLowerCase();return{name:I.basename($),mimeType:a[Y],base64:V}}),Z=Q.reduce(($,V)=>$+V.base64.length,0);if(Z>500000)console.warn(`[warn] Total image payload is ${(Z/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(Q);return z.evaluate(`
7
7
  (async () => {
8
8
  const images = ${q};
9
9
 
@@ -43,11 +43,11 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
43
43
 
44
44
  return { ok: true, count: dt.files.length };
45
45
  })()
46
- `)}async function a(z,J=30000){const K=2000,Z=Math.ceil(J/K);for(let Q=0;Q<Z;Q++){if(!await z.evaluate(`
46
+ `)}async function Jz(z,J=30000){const K=2000,Q=Math.ceil(J/K);for(let Z=0;Z<Q;Z++){if(!await z.evaluate(`
47
47
  () => !!document.querySelector(
48
48
  '[class*="upload"][class*="progress"], [class*="uploading"], [class*="loading"][class*="image"]'
49
49
  )
50
- `))return;await z.wait({time:K/1000})}}async function e(z,J,K,Z){const Q=await z.evaluate(`
50
+ `))return;await z.wait({time:K/1000})}}async function Kz(z,J,K,Q){const Z=await z.evaluate(`
51
51
  (function(selectors) {
52
52
  const __opencli_xhs_fill_phase = "locate";
53
53
  for (const sel of selectors) {
@@ -62,7 +62,7 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
62
62
  }
63
63
  return { ok: false };
64
64
  })(${JSON.stringify(J)})
65
- `);if(!Q.ok){await z.screenshot({path:`/tmp/xhs_publish_${Z}_debug.png`});throw Error(`Could not find ${Z} input. Debug screenshot: /tmp/xhs_publish_${Z}_debug.png`)}const q=()=>z.evaluate(`
65
+ `);if(!Z.ok){await z.screenshot({path:`/tmp/xhs_publish_${Q}_debug.png`});throw Error(`Could not find ${Q} input. Debug screenshot: /tmp/xhs_publish_${Q}_debug.png`)}const q=()=>z.evaluate(`
66
66
  ((selector, expectedText) => {
67
67
  const __opencli_xhs_fill_phase = "apply";
68
68
  const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
@@ -118,8 +118,8 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
118
118
  el.blur();
119
119
  const actual = normalize(el.innerText || el.textContent || '');
120
120
  return { ok: actual === normalize(expectedText), actual };
121
- })(${JSON.stringify(Q.sel)}, ${JSON.stringify(K)})
122
- `);let $;if(Q.kind==="contenteditable"&&z.insertText){if(!(await z.evaluate(`
121
+ })(${JSON.stringify(Z.sel)}, ${JSON.stringify(K)})
122
+ `);let $;if(Z.kind==="contenteditable"&&z.insertText){if(!(await z.evaluate(`
123
123
  ((selector, nextText) => {
124
124
  const __opencli_xhs_fill_phase = "prepare";
125
125
  const fireBeforeInput = (el, value) => {
@@ -145,8 +145,8 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
145
145
  selection?.addRange(range);
146
146
  fireBeforeInput(el, nextText);
147
147
  return { ok: true };
148
- })(${JSON.stringify(Q.sel)}, ${JSON.stringify(K)})
149
- `))?.ok){await z.screenshot({path:`/tmp/xhs_publish_${Z}_debug.png`});throw Error(`Could not prepare ${Z} input. Debug screenshot: /tmp/xhs_publish_${Z}_debug.png`)}try{await A(z,K);$=await z.evaluate(`
148
+ })(${JSON.stringify(Z.sel)}, ${JSON.stringify(K)})
149
+ `))?.ok){await z.screenshot({path:`/tmp/xhs_publish_${Q}_debug.png`});throw Error(`Could not prepare ${Q} input. Debug screenshot: /tmp/xhs_publish_${Q}_debug.png`)}try{await x(z,K);$=await z.evaluate(`
150
150
  ((selector, expectedText) => {
151
151
  const __opencli_xhs_fill_phase = "verify";
152
152
  const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
@@ -168,8 +168,8 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
168
168
  el.blur();
169
169
  const actual = normalize(el.innerText || el.textContent || '');
170
170
  return { ok: actual === normalize(expectedText), actual };
171
- })(${JSON.stringify(Q.sel)}, ${JSON.stringify(K)})
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 Nz(z,J){return W(await z.evaluate(`
171
+ })(${JSON.stringify(Z.sel)}, ${JSON.stringify(K)})
172
+ `)}catch{$=await q()}}else $=await q();if(!$?.ok){await z.screenshot({path:`/tmp/xhs_publish_${Q}_debug.png`});const V=typeof $?.actual==="string"?$.actual:"";throw Error(`Failed to set ${Q}. Expected "${K}", got "${V}". Debug screenshot: /tmp/xhs_publish_${Q}_debug.png`)}}async function Mz(z,J){return W(await z.evaluate(`
173
173
  (selectors => {
174
174
  const el = selectors
175
175
  .map(sel => Array.from(document.querySelectorAll(sel)))
@@ -185,7 +185,7 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
185
185
  selection?.addRange(range);
186
186
  return true;
187
187
  })(${JSON.stringify(J)})
188
- `))}function lz(z,{click:J=!1}={}){return`
188
+ `))}function pz(z,{click:J=!1}={}){return`
189
189
  (topicName => {
190
190
  const norm = (value) => (value || '').replace(/^#/, '').replace(/\\s+/g, '').trim();
191
191
  const want = norm(topicName);
@@ -225,7 +225,7 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
225
225
  text: (target.innerText || target.textContent || '').trim().slice(0, 40),
226
226
  };
227
227
  })(${JSON.stringify(z)})
228
- `}function oz(z,J){return`
228
+ `}function tz(z,J){return`
229
229
  ((topicName, selectors) => {
230
230
  const norm = (value) => (value || '').replace(/^#/, '').replace(/\\s+/g, '').trim();
231
231
  const want = norm(topicName);
@@ -256,7 +256,7 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
256
256
  }
257
257
  return count;
258
258
  })(${JSON.stringify(z)}, ${JSON.stringify(J)})
259
- `}function zz(z,J){return`
259
+ `}function Qz(z,J){return`
260
260
  ((topicName, selectors) => {
261
261
  const __opencli_xhs_topic_marker_count = true;
262
262
  const marker = '#' + topicName + '[话题]';
@@ -274,14 +274,24 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
274
274
  }
275
275
  return count;
276
276
  })(${JSON.stringify(z)}, ${JSON.stringify(J)})
277
- `}async function sz(z,J){const K=`#${J}`;if(typeof z.insertText==="function")try{await z.insertText(K);return!0}catch{}return W(await z.evaluate(`
277
+ `}async function az(z,J){const K=`#${J}`;if(typeof z.insertText==="function")try{await z.insertText(K);return!0}catch{}return W(await z.evaluate(`
278
278
  (text => {
279
279
  const ok = document.execCommand('insertText', false, text);
280
280
  const active = document.activeElement;
281
281
  if (active) active.dispatchEvent(new Event('input', { bubbles: true }));
282
282
  return ok;
283
283
  })(${JSON.stringify(K)})
284
- `))}async function kz(z,J,K){const Z=[];for(const Q of K){if(!await Nz(z,J))throw new V(`Could not attach topic "${Q}": body editor not found`);const $=Number(W(await z.evaluate(zz(Q,J))))||0;if(typeof z.pressKey==="function")try{await z.pressKey("Enter")}catch{}if(typeof z.insertText!=="function")throw new V(`Could not attach topic "${Q}": page.insertText is unavailable`);try{await A(z,`#${Q}`)}catch{throw new V(`Could not attach topic "${Q}": failed to type inline topic query`)}await N(z,1.2);if(typeof z.pressKey!=="function")throw new V(`Could not attach topic "${Q}": page.pressKey is unavailable`);try{await z.pressKey("Enter")}catch(Y){throw new V(`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(zz(Q,J))))||0)<=$)throw new V(`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 Lz(z){const J=await z.evaluate(`
284
+ `))}function Zz(z){return`
285
+ (selectors => {
286
+ let editor = null;
287
+ for (const sel of selectors) {
288
+ editor = document.querySelector(sel);
289
+ if (editor) break;
290
+ }
291
+ if (!editor) return -1;
292
+ return (editor.innerText || editor.textContent || '').length;
293
+ })(${JSON.stringify(z)})
294
+ `}async function Lz(z,J,K){if(!await Mz(z,J))throw new H(`Could not attach topic "${K}": body editor not found`);const Z=Number(W(await z.evaluate(Qz(K,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 "${K}": page.insertText is unavailable`);try{await x(z,`#${K}`)}catch{throw new H(`Could not attach topic "${K}": failed to type inline topic query`)}await P(z,1.2);if(typeof z.pressKey!=="function")throw new H(`Could not attach topic "${K}": page.pressKey is unavailable`);try{await z.pressKey("Enter")}catch($){throw new H(`Could not attach topic "${K}": failed to accept suggestion (${$&&$.message||$})`)}await z.wait({time:0.8});if((Number(W(await z.evaluate(Qz(K,J))))||0)<=Z)throw new H(`Could not attach topic "${K}": no real topic entity appeared after selection`)}async function _z(z,J,K){const Q=[],Z=[];for(const q of K){let $=-1;try{const V=Number(W(await z.evaluate(Zz(J))));$=Number.isFinite(V)?V:-1}catch{}try{await Lz(z,J,q);Q.push(q);await z.wait({time:0.4})}catch(V){Z.push({topic:q,reason:V&&V.message||String(V)});if($>=0&&typeof z.pressKey==="function")try{const Y=q.length+6;for(let G=0;G<Y;G++){const O=Number(W(await z.evaluate(Zz(J))));if(!Number.isFinite(O)||O<0||O<=$)break;await z.pressKey("Backspace");await z.wait({time:0.15})}}catch{}}}return{added:Q,failed:Z}}async function hz(z){const J=await z.evaluate(`
285
295
  () => {
286
296
  const isVisible = (el) => {
287
297
  if (!el || el.offsetParent === null) return false;
@@ -330,7 +340,7 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
330
340
  }
331
341
  return { ok: false, visibleTexts };
332
342
  }
333
- `);if(J?.ok)await z.wait({time:1});return J}async function L(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(`
343
+ `);if(J?.ok)await z.wait({time:1});return J}async function L(z,J,K=7000){const Q=500,Z=Math.max(1,Math.ceil(K/Q));let q={ok:!1};for(let $=0;$<Z;$++){q=W(await z.evaluate(`
334
344
  ((cfg) => {
335
345
  const __opencli_xhs_click_label = { wantLabel: ${JSON.stringify(J)} };
336
346
  const wantLabel = ${JSON.stringify(J)};
@@ -365,10 +375,10 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
365
375
  }
366
376
  return { ok: false };
367
377
  })()
368
- `));if(q?.ok)return q;await z.wait({time:Z/1000})}return q}async function Pz(z){const J=await z.evaluate(`
378
+ `));if(q?.ok)return q;await z.wait({time:Q/1000})}return q}async function fz(z){const J=await z.evaluate(`
369
379
  (() => {
370
380
  const __opencli_xhs_focus_card = true;
371
- const sel = ${JSON.stringify(S)};
381
+ const sel = ${JSON.stringify(u)};
372
382
  const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
373
383
  // Prefer the editor inside the active swiper slide if present.
374
384
  const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
@@ -382,34 +392,34 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
382
392
  selection?.addRange(range);
383
393
  return { ok: true };
384
394
  })()
385
- `);return W(J)}async function Iz(z){const J=await z.evaluate(`
395
+ `);return W(J)}async function Az(z){const J=await z.evaluate(`
386
396
  (() => {
387
397
  const __opencli_xhs_card_text = true;
388
- const sel = ${JSON.stringify(S)};
398
+ const sel = ${JSON.stringify(u)};
389
399
  const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
390
400
  const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
391
401
  const text = active ? (active.innerText || active.textContent || '').trim() : '';
392
402
  return { ok: !!text, text };
393
403
  })()
394
- `);return W(J)}async function Jz(z){const J=await z.evaluate(`
404
+ `);return W(J)}async function $z(z){const J=await z.evaluate(`
395
405
  (() => {
396
406
  const __opencli_xhs_card_count = true;
397
- const sel = ${JSON.stringify(S)};
407
+ const sel = ${JSON.stringify(u)};
398
408
  const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
399
409
  const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
400
410
  const activeText = active ? (active.innerText || active.textContent || '').trim() : '';
401
411
  return { ok: true, count: editors.length, activeEmpty: !activeText };
402
412
  })()
403
- `);return W(J)}async function hz(z,J=8000){const K=300,Z=Math.ceil(J/K);for(let Q=0;Q<Z;Q++){if((await Jz(z))?.count>=1)return!0;await z.wait({time:K/1000})}return!1}async function Mz(z,J,K=6000){const Z=300,Q=Math.ceil(K/Z);for(let q=0;q<Q;q++){const $=await Jz(z);if($?.count>=J&&$?.activeEmpty)return!0;await z.wait({time:Z/1000})}return!1}async function wz(z,J,K=4){for(let Z=0;Z<K;Z++){if(!(await L(z,l))?.ok)return!1;if(await Mz(z,J,2500))return!0}return!1}async function Rz(z){const J=await z.evaluate(`
413
+ `);return W(J)}async function Rz(z,J=8000){const K=300,Q=Math.ceil(J/K);for(let Z=0;Z<Q;Z++){if((await $z(z))?.count>=1)return!0;await z.wait({time:K/1000})}return!1}async function wz(z,J,K=6000){const Q=300,Z=Math.ceil(K/Q);for(let q=0;q<Z;q++){const $=await $z(z);if($?.count>=J&&$?.activeEmpty)return!0;await z.wait({time:Q/1000})}return!1}async function yz(z,J,K=4){for(let Q=0;Q<K;Q++){if(!(await L(z,g))?.ok)return!1;if(await wz(z,J,2500))return!0}return!1}async function bz(z){const J=await z.evaluate(`
404
414
  (() => {
405
415
  const __opencli_xhs_preview_ready = true;
406
416
  const ready = Array.from(document.querySelectorAll('button'))
407
417
  .some((b) => b.offsetParent !== null && (b.innerText || '').replace(/\\s+/g, '') === '下一步');
408
418
  return { ok: ready };
409
419
  })()
410
- `);return W(J)}async function _z(z,J=6){const K=400,Z=Math.ceil(3000/K);for(let Q=0;Q<J;Q++){if(!(await L(z,o))?.ok&&Q===0)return!1;for(let $=0;$<Z;$++){if((await Rz(z))?.ok)return!0;await z.wait({time:K/1000})}}return!1}async function bz(z,J,K){J=String(J).replace(/\\n/g,`
411
- `);if(!(await Pz(z))?.ok)throw new V(`文字配图: 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 A(z,q[$])}}else await z.evaluate(`(t => document.execCommand('insertText', false, t))(${JSON.stringify(J)})`);await z.wait({time:0.4});if(!(await Iz(z))?.ok)throw new V(`文字配图: card editor #${K+1} is empty after typing`)}async function yz(z,J){if(!J||J===k)return k;const K=W(await z.evaluate(`
420
+ `);return W(J)}async function Cz(z,J=6){const K=400,Q=Math.ceil(3000/K);for(let Z=0;Z<J;Z++){if(!(await L(z,r))?.ok&&Z===0)return!1;for(let $=0;$<Q;$++){if((await bz(z))?.ok)return!0;await z.wait({time:K/1000})}}return!1}async function Tz(z,J,K){J=String(J).replace(/\\n/g,`
421
+ `);if(!(await fz(z))?.ok)throw new H(`文字配图: could not focus card editor #${K+1}`);if(typeof z.insertText==="function"){const q=J.split(`
422
+ `);for(let $=0;$<q.length;$++){if($>0&&typeof z.pressKey==="function")await z.pressKey("Enter");if(q[$])await x(z,q[$])}}else await z.evaluate(`(t => document.execCommand('insertText', false, t))(${JSON.stringify(J)})`);await z.wait({time:0.4});if(!(await Az(z))?.ok)throw new H(`文字配图: card editor #${K+1} is empty after typing`)}async function Sz(z,J){if(!J||J===M)return M;const K=W(await z.evaluate(`
413
423
  (async () => {
414
424
  const __opencli_xhs_card_styles = true;
415
425
  const want = ${JSON.stringify(J)};
@@ -438,7 +448,7 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
438
448
  }
439
449
  return { ok: seen.length > 0, styles: seen, found: false };
440
450
  })()
441
- `));if(!K?.found)throw new V(`文字配图: requested style "${J}" is not available for this content (options: ${(K?.styles||[]).join(" / ")||"none"}). Choose an available style or omit --card-style to use ${k}.`);if(!(await L(z,J))?.ok)throw new V(`文字配图: could not click requested style "${J}".`);await z.wait({time:0.6});return J}async function Kz(z){const J=await z.evaluate(`
451
+ `));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 ${M}.`);if(!(await L(z,J))?.ok)throw new H(`文字配图: could not click requested style "${J}".`);await z.wait({time:0.6});return J}async function qz(z){const J=await z.evaluate(`
442
452
  (() => {
443
453
  const __opencli_xhs_composer_media_count = true;
444
454
  const visibleBox = (el) => {
@@ -451,7 +461,7 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
451
461
  const r = el.getBoundingClientRect();
452
462
  return r.width >= 48 && r.height >= 48;
453
463
  };
454
- const titleSelectors = ${JSON.stringify(C)};
464
+ const titleSelectors = ${JSON.stringify(m)};
455
465
  const titleEl = titleSelectors
456
466
  .map((sel) => Array.from(document.querySelectorAll(sel)))
457
467
  .flat()
@@ -470,7 +480,7 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
470
480
  }
471
481
  return { ok: true, count };
472
482
  })()
473
- `);return W(J)}async function Qz(z,J,K){const Z=await Kz(z);if(!Z||typeof Z.count!=="number")throw new V(`${K}: could not verify current composer media count`);if(Z.count<J){await z.screenshot({path:"/tmp/xhs_publish_media_debug.png"});throw new V(`${K}: expected at least ${J} visible media item(s), got ${Z.count}. Debug screenshot: /tmp/xhs_publish_media_debug.png`)}}async function Az(z,J,K){if(!(await L(z,T))?.ok){await z.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new V(`文字配图: could not click "${T}" entry. Debug: /tmp/xhs_publish_textimage_debug.png`)}if(!await hz(z)){await z.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new V(`文字配图: 写文字 card editor did not appear after clicking "${T}". Debug: /tmp/xhs_publish_textimage_debug.png`)}for(let j=0;j<J.length;j++){if(j>0){if(!await wz(z,j+1)){await z.screenshot({path:"/tmp/xhs_publish_addcard_debug.png"});throw new V(`文字配图: new card editor #${j+1} did not render after "${l}". Debug: /tmp/xhs_publish_addcard_debug.png`)}}await bz(z,J[j],j)}if(!await _z(z)){await z.screenshot({path:"/tmp/xhs_publish_generate_debug.png"});throw new V(`文字配图: "${o}" did not advance to the 预览图片 step. `+"Debug: /tmp/xhs_publish_generate_debug.png")}const q=await yz(z,K);if(!(await L(z,s))?.ok){await z.screenshot({path:"/tmp/xhs_publish_next_debug.png"});throw new V(`文字配图: could not click "${s}". Debug: /tmp/xhs_publish_next_debug.png`)}await z.wait({time:2});return q}async function Zz(z){return z.evaluate(`
483
+ `);return W(J)}async function Vz(z,J,K){const Q=await qz(z);if(!Q||typeof Q.count!=="number")throw new H(`${K}: could not verify current composer media count`);if(Q.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 ${Q.count}. Debug screenshot: /tmp/xhs_publish_media_debug.png`)}}async function xz(z,J,K){if(!(await L(z,E))?.ok){await z.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new H(`文字配图: could not click "${E}" entry. Debug: /tmp/xhs_publish_textimage_debug.png`)}if(!await Rz(z)){await z.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new H(`文字配图: 写文字 card editor did not appear after clicking "${E}". Debug: /tmp/xhs_publish_textimage_debug.png`)}for(let V=0;V<J.length;V++){if(V>0){if(!await yz(z,V+1)){await z.screenshot({path:"/tmp/xhs_publish_addcard_debug.png"});throw new H(`文字配图: new card editor #${V+1} did not render after "${g}". Debug: /tmp/xhs_publish_addcard_debug.png`)}}await Tz(z,J[V],V)}if(!await Cz(z)){await z.screenshot({path:"/tmp/xhs_publish_generate_debug.png"});throw new H(`文字配图: "${r}" did not advance to the 预览图片 step. `+"Debug: /tmp/xhs_publish_generate_debug.png")}const q=await Sz(z,K);if(!(await L(z,p))?.ok){await z.screenshot({path:"/tmp/xhs_publish_next_debug.png"});throw new H(`文字配图: could not click "${p}". Debug: /tmp/xhs_publish_next_debug.png`)}await z.wait({time:2});return q}async function jz(z){return z.evaluate(`
474
484
  () => {
475
485
  const text = (document.body?.innerText || '').replace(/s+/g, ' ').trim();
476
486
  const hasTitleInput = !!Array.from(document.querySelectorAll('input, textarea')).find((el) => {
@@ -500,22 +510,22 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
500
510
  const state = hasTitleInput ? 'editor_ready' : hasImageInput || !hasVideoSurface ? 'image_surface' : 'video_surface';
501
511
  return { state, hasTitleInput, hasImageInput, hasVideoSurface };
502
512
  }
503
- `)}async function Tz(z,J=5000){const K=500,Z=Math.max(1,Math.ceil(J/K));let Q=await Zz(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 Zz(z)}}return Q}async function Sz(z,J=1e4){const K=1000,Z=Math.ceil(J/K);for(let Q=0;Q<Z;Q++){if(await z.evaluate(`
513
+ `)}async function Ez(z,J=5000){const K=500,Q=Math.max(1,Math.ceil(J/K));let Z=await jz(z);for(let q=0;q<Q;q++){if(Z.state!=="video_surface")return Z;if(q<Q-1){await z.wait({time:K/1000});Z=await jz(z)}}return Z}async function uz(z,J=1e4){const K=1000,Q=Math.ceil(J/K);for(let Z=0;Z<Q;Z++){if(await z.evaluate(`
504
514
  (() => {
505
- const sels = ${JSON.stringify(C)};
515
+ const sels = ${JSON.stringify(m)};
506
516
  for (const sel of sels) {
507
517
  const el = document.querySelector(sel);
508
518
  if (el && el.offsetParent !== null) return true;
509
519
  }
510
520
  return false;
511
- })()`))return!0;if(Q<Z-1)await z.wait({time:K/1000})}return!1}async function Cz(z){const J=W(await z.evaluate(`
521
+ })()`))return!0;if(Z<Q-1)await z.wait({time:K/1000})}return!1}async function mz(z){const J=W(await z.evaluate(`
512
522
  (() => {
513
523
  const __opencli_xhs_restore_srcs = true;
514
524
  return Array.from(document.querySelectorAll('.img-container img'))
515
525
  .map((img) => img.currentSrc || img.src || '')
516
526
  .filter(Boolean);
517
527
  })()
518
- `));return Array.isArray(J)?J:[]}async function $z(z,J){if(!Array.isArray(J)||J.length===0)return 0;const K=W(await z.evaluate(`
528
+ `));return Array.isArray(J)?J:[]}async function Hz(z,J){if(!Array.isArray(J)||J.length===0)return 0;const K=W(await z.evaluate(`
519
529
  (async (wanted) => {
520
530
  const __opencli_xhs_delete_by_src = true;
521
531
  const want = new Set(wanted);
@@ -546,7 +556,16 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
546
556
  }
547
557
  return removed;
548
558
  })(${JSON.stringify(J)})
549
- `));return Number(K)||0}function xz(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 jz(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 v of Array.isArray(f)?f:[]){const G=xz(v);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}}Yz({site:"xiaohongshu",name:"publish",access:"write",description:"小红书发布图文笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:Xz.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:`文字配图卡片文字,多张卡片用 ${m} 分隔,卡内换行用 \\n`},{name:"card-style",required:!1,help:`文字配图卡片样式,运行时按页面实际选项匹配;找不到会失败。省略时使用${k}。可选: ${i.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 H=D.resolve(Z);if(!_.existsSync(H))throw new F(`--file 指向的文件不存在: ${H}`);Q=_.readFileSync(H,"utf-8").trim();if(!Q)throw new F(`--file 指向的文件内容为空: ${H}`)}const q=J.images?String(J.images).split(",").map((H)=>H.trim()).filter(Boolean):[],$=J.topics?String(J.topics).split(",").map((H)=>H.trim()).filter(Boolean):[],j=Boolean(J.draft),Y=J["card-text"]?String(J["card-text"]):"",U=Y?Y.split(m).map((H)=>H.trim()).filter(Boolean):[],f=J["card-style"]?String(J["card-style"]).trim():"",v=U.length>0;if(!K)throw new F("--title is required");if(K.length>d)throw new F(`Title is ${K.length} chars — must be ≤ ${d}`);if(!Q)throw new F("缺少笔记正文:给位置参数 <content>(字面文本),或用 --file <本机文件路径>");Wz(Q,{fileFlag:"--file"});if(!v&&q.length===0)throw new F("Provide --card-text (text-image mode) or --images (upload mode); neither was given.");if(q.length>u)throw new F(`Too many images: ${q.length} (max ${u})`);if(v&&q.some((H)=>D.extname(H).toLowerCase()===".gif"))throw new F("文字配图模式追加的图片不支持 .gif(编辑器图片入口只接受 jpg/jpeg/png/webp)");const G=Dz(q);await z.goto(Uz);await z.wait({time:1});await z.evaluate("() => { try { window.onbeforeunload = null; } catch (e) {} location.reload(); }").catch(()=>{return});let P="";for(let H=0;H<30;H++){await z.wait({time:0.5});P=await z.evaluate("() => location.href");if(P.includes("creator.xiaohongshu.com"))break}if(!P.includes("creator.xiaohongshu.com")){await z.screenshot({path:"/tmp/xhs_publish_redirect_debug.png"});throw Error(`Redirected away from creator center (landed on ${P}) — session may have expired. `+"Re-capture browser login via: ppcli xiaohongshu creator-profile. Debug screenshot: /tmp/xhs_publish_redirect_debug.png")}const I=await Lz(z);if((await Tz(z,I?.ok?5000:2000)).state==="video_surface"){await z.screenshot({path:"/tmp/xhs_publish_tab_debug.png"});const H=I?.ok?`clicked "${I.text}"`:`visible candidates: ${(I?.visibleTexts||[]).join(" | ")||"none"}`;throw Error("Still on the video publish page after trying to select 图文. "+`Details: ${H}. Debug screenshot: /tmp/xhs_publish_tab_debug.png`)}const h=await Cz(z);let M=f;if(v)M=await Az(z,U,f);else{const H=typeof z.startNetworkCapture==="function"?await z.startNetworkCapture("").catch(()=>!1):!1,O=await t(z,G);if(!O.ok){await z.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new V(`图片注入失败:${O.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_upload_debug.png")}await z.wait({time:n/1000});const B=await jz(z,G.length,H);if(!B.ok){await z.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new V(`图片上传未全部完成:只确认 ${B.completed}/${G.length} 张传到小红书 CDN,`+"继续发布会丢图(通常只剩封面)。直接重新运行本 publish 命令即可(会自动重置发布页重传);"+"请勿手动修改当前页面。Debug: /tmp/xhs_publish_upload_debug.png")}await a(z);if(h.length){await $z(z,h);await z.wait({time:0.5})}const R=await Kz(z);if(R&&typeof R.count==="number"&&R.count>G.length){await z.screenshot({path:"/tmp/xhs_publish_dup_debug.png"});throw new V(`清掉恢复态旧图后编辑器内仍有 ${R.count} 张图,超过本次注入的 ${G.length} 张。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。Debug: /tmp/xhs_publish_dup_debug.png")}}if(!await Sz(z)){await z.screenshot({path:"/tmp/xhs_publish_form_debug.png"});throw new V("Editing form did not appear after image acquisition. The page layout may have changed. Debug screenshot: /tmp/xhs_publish_form_debug.png")}if(v)await Qz(z,U.length,"文字配图 generated images");if(v&&G.length>0){const H=typeof z.startNetworkCapture==="function"?await z.startNetworkCapture("").catch(()=>!1):!1,O=await t(z,G);if(!O.ok){await z.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new V(`追加图片失败:${O.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await z.wait({time:n/1000});const B=await jz(z,G.length,H);if(!B.ok){await z.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new V(`追加图片上传未全部完成:只确认 ${B.completed}/${G.length} 张传到小红书 CDN。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await a(z);await Qz(z,U.length+G.length,"文字配图 appended images")}if(v&&h.length){await $z(z,h);await z.wait({time:0.5})}await e(z,C,K,"title");await N(z,0.6);await e(z,r,Q,"content");await N(z,0.6);let b=[];if($.length)b=await kz(z,r,$);await N(z,1,0.6);const y=await Oz(z,["xhs-publish-btn"]);if(y){await Fz(z,y.x,y.y);await N(z,0.4)}const x=j?["暂存离开","存草稿"]:["发布","发布笔记"],X=await z.evaluate(`
559
+ `));return Number(K)||0}function cz(z){const J=String(z&&(z.url||z.key)||""),K=String(z&&z.method||"").toUpperCase(),Q=Number(z&&z.status||0);if(K!=="PUT")return null;if(!/ros-upload[^/]*\/spectrum\//.test(J))return null;if(Q<200||Q>=300)return null;const Z=J.match(/\/spectrum\/([^?#]+)/);return Z?Z[1]:J}function Gz(z){return Math.min(45,6+z*4)}async function Yz(z,J,K,Q=90000){if(J<=0)return{ok:!0,completed:0};if(!K||typeof z.readNetworkCapture!=="function"){await z.wait({time:Gz(J)});return{ok:!0,completed:null,viaFallback:!0}}const Z=1500,$=Math.ceil(9000/Z),V=Math.ceil(Q/Z),Y=new Set;for(let G=0;G<V;G++){const O=await z.readNetworkCapture().catch(()=>[]);for(const v of Array.isArray(O)?O:[]){const X=cz(v);if(X)Y.add(X)}if(Y.size>=J)return{ok:!0,completed:Y.size};if(G>=$&&Y.size===0){await z.wait({time:Gz(J)});return{ok:!0,completed:null,viaFallback:!0}}await z.wait({time:Z/1000})}return{ok:!1,completed:Y.size}}async function dz(z,J,K){try{await z.goto("https://creator.xiaohongshu.com/new/note-manager")}catch{return null}const Q=J.trim();for(let Z=0;Z<K;Z++){await z.wait({time:1.5});let q;try{q=W(await z.evaluate(`(() => {
560
+ const noteIdRe = /"noteId":"([0-9a-f]{24})"/;
561
+ return Array.from(document.querySelectorAll('div.note[data-impression], div.note')).slice(0, 3).map((card) => {
562
+ const impression = card.getAttribute('data-impression') || '';
563
+ const id = impression.match(noteIdRe)?.[1] || '';
564
+ const title = (card.querySelector('.title, .raw')?.innerText || '').trim();
565
+ const dateText = (card.querySelector('.time_status, .time')?.innerText || '').trim();
566
+ return { id, title, dateText };
567
+ }).filter((entry) => entry.title);
568
+ })()`))}catch{return null}if(Array.isArray(q)){const $=q.find((V)=>{const Y=String(V.title||"").trim();if(!(Y===Q||Y.startsWith(Q)||Q.startsWith(Y)))return!1;const G=String(V.dateText||"").match(/(\d{4})年(\d{2})月(\d{2})日 (\d{2}):(\d{2})/);if(!G)return!0;const O=Date.UTC(Number(G[1]),Number(G[2])-1,Number(G[3]),Number(G[4]),Number(G[5]))-28800000;return Math.abs(Date.now()-O)<=93600000});if($)return $}}return null}Fz({site:"xiaohongshu",name:"publish",access:"write",description:"小红书发布图文笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:Oz.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:`文字配图卡片文字,多张卡片用 ${o} 分隔,卡内换行用 \\n`},{name:"card-style",required:!1,help:`文字配图卡片样式,运行时按页面实际选项匹配;找不到会失败。省略时使用${M}。可选: ${s.map(([z,J])=>`${z}(${J})`).join(" ")}`},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号。平台话题库匹配不上的会自动跳过并在结果中提示,不影响发布"},{name:"draft",type:"bool",default:!1,help:"保存为草稿,不直接发布"}],columns:["status","detail","url"],func:async(z,J)=>{if(!z)throw Error("Browser page required");const K=String(J.title??"").trim(),Q=J.file?String(J.file).trim():"";let Z=String(J.content??"").trim();if(Q){if(Z)throw new U("正文位置参数和 --file 只能二选一,不能同时给");const j=I.resolve(Q);if(!w.existsSync(j))throw new U(`--file 指向的文件不存在: ${j}`);Z=w.readFileSync(j,"utf-8").trim();if(!Z)throw new U(`--file 指向的文件内容为空: ${j}`)}const q=J.images?String(J.images).split(",").map((j)=>j.trim()).filter(Boolean):[],$=J.topics?String(J.topics).split(",").map((j)=>j.trim()).filter(Boolean):[],V=Boolean(J.draft),Y=J["card-text"]?String(J["card-text"]):"",G=Y?Y.split(o).map((j)=>j.trim()).filter(Boolean):[],O=J["card-style"]?String(J["card-style"]).trim():"",v=G.length>0;if(!K)throw new U("--title is required");if(K.length>l)throw new U(`Title is ${K.length} chars — must be ≤ ${l}`);if(!Z)throw new U("缺少笔记正文:给位置参数 <content>(字面文本),或用 --file <本机文件路径>");Uz(Z,{fileFlag:"--file"});if(!v&&q.length===0)throw new U("Provide --card-text (text-image mode) or --images (upload mode); neither was given.");if(q.length>n)throw new U(`Too many images: ${q.length} (max ${n})`);if(v&&q.some((j)=>I.extname(j).toLowerCase()===".gif"))throw new U("文字配图模式追加的图片不支持 .gif(编辑器图片入口只接受 jpg/jpeg/png/webp)");const X=Iz(q);await z.goto(Dz);await z.wait({time:1});await z.evaluate("() => { try { window.onbeforeunload = null; } catch (e) {} location.reload(); }").catch(()=>{return});let _="";for(let j=0;j<30;j++){await z.wait({time:0.5});_=await z.evaluate("() => location.href");if(_.includes("creator.xiaohongshu.com"))break}if(!_.includes("creator.xiaohongshu.com")){await z.screenshot({path:"/tmp/xhs_publish_redirect_debug.png"});throw Error(`Redirected away from creator center (landed on ${_}) — session may have expired. `+"Re-capture browser login via: ppcli xiaohongshu creator-profile. Debug screenshot: /tmp/xhs_publish_redirect_debug.png")}const h=await hz(z);if((await Ez(z,h?.ok?5000:2000)).state==="video_surface"){await z.screenshot({path:"/tmp/xhs_publish_tab_debug.png"});const j=h?.ok?`clicked "${h.text}"`:`visible candidates: ${(h?.visibleTexts||[]).join(" | ")||"none"}`;throw Error("Still on the video publish page after trying to select 图文. "+`Details: ${j}. Debug screenshot: /tmp/xhs_publish_tab_debug.png`)}const f=await mz(z);let A=O;if(v)A=await xz(z,G,O);else{const j=typeof z.startNetworkCapture==="function"?await z.startNetworkCapture("").catch(()=>!1):!1,B=await zz(z,X);if(!B.ok){await z.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new H(`图片注入失败:${B.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_upload_debug.png")}await z.wait({time:i/1000});const D=await Yz(z,X.length,j);if(!D.ok){await z.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new H(`图片上传未全部完成:只确认 ${D.completed}/${X.length} 张传到小红书 CDN,`+"继续发布会丢图(通常只剩封面)。直接重新运行本 publish 命令即可(会自动重置发布页重传);"+"请勿手动修改当前页面。Debug: /tmp/xhs_publish_upload_debug.png")}await Jz(z);if(f.length){await Hz(z,f);await z.wait({time:0.5})}const R=await qz(z);if(R&&typeof R.count==="number"&&R.count>X.length){await z.screenshot({path:"/tmp/xhs_publish_dup_debug.png"});throw new H(`清掉恢复态旧图后编辑器内仍有 ${R.count} 张图,超过本次注入的 ${X.length} 张。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。Debug: /tmp/xhs_publish_dup_debug.png")}}if(!await uz(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(v)await Vz(z,G.length,"文字配图 generated images");if(v&&X.length>0){const j=typeof z.startNetworkCapture==="function"?await z.startNetworkCapture("").catch(()=>!1):!1,B=await zz(z,X);if(!B.ok){await z.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new H(`追加图片失败:${B.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await z.wait({time:i/1000});const D=await Yz(z,X.length,j);if(!D.ok){await z.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new H(`追加图片上传未全部完成:只确认 ${D.completed}/${X.length} 张传到小红书 CDN。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await Jz(z);await Vz(z,G.length+X.length,"文字配图 appended images")}if(v&&f.length){await Hz(z,f);await z.wait({time:0.5})}await Kz(z,m,K,"title");await P(z,0.6);await Kz(z,t,Z,"content");await P(z,0.6);let y=[],b=[];if($.length){const j=await _z(z,t,$);y=j.added;b=j.failed}await P(z,1,0.6);const C=await vz(z,["xhs-publish-btn"]);if(C){await Bz(z,C.x,C.y);await P(z,0.4)}const c=V?["暂存离开","存草稿"]:["发布","发布笔记"],F=await z.evaluate(`
550
569
  (cfg => {
551
570
  const { isDraftMode, publishNames, draftNames, labels } = cfg;
552
571
  const isVisible = (el) => {
@@ -585,8 +604,8 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
585
604
  }
586
605
  }
587
606
  return { ok: false, via: 'none', hosts: hosts.length, lastMethodError };
588
- })(${JSON.stringify({isDraftMode:j,publishNames:vz,draftNames:Bz,labels:x})})
589
- `);if(!X?.ok){if(j){if(await z.evaluate(`
607
+ })(${JSON.stringify({isDraftMode:V,publishNames:kz,draftNames:Nz,labels:c})})
608
+ `);if(!F?.ok){if(V){if(await z.evaluate(`
590
609
  (() => {
591
610
  const labels = ['返回', '关闭', '取消', '离开'];
592
611
  const buttons = document.querySelectorAll('button, [role="button"], div, span');
@@ -599,7 +618,7 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
599
618
  }
600
619
  return false;
601
620
  })()
602
- `)){await z.wait({time:1});const O=["暂存离开","存草稿","保存草稿"];if(await z.evaluate(`
621
+ `)){await z.wait({time:1});const B=["暂存离开","存草稿","保存草稿"];if(await z.evaluate(`
603
622
  (labels => {
604
623
  const buttons = document.querySelectorAll('button, [role="button"]');
605
624
  for (const btn of buttons) {
@@ -614,8 +633,8 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
614
633
  }
615
634
  }
616
635
  return false;
617
- })(${JSON.stringify(O)})
618
- `)){X.ok=!0;X.via="leave-save-fallback"}}if(!X?.ok){await z.wait({time:2});if(await z.evaluate(`
636
+ })(${JSON.stringify(B)})
637
+ `)){F.ok=!0;F.via="leave-save-fallback"}}if(!F?.ok){await z.wait({time:2});if(await z.evaluate(`
619
638
  () => {
620
639
  const markers = ['草稿箱(', '保存于', '编辑于'];
621
640
  for (const el of document.querySelectorAll('*')) {
@@ -624,7 +643,7 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
624
643
  }
625
644
  return false;
626
645
  }
627
- `)){X.ok=!0;X.via="auto-save"}}}}if(!X?.ok){await z.screenshot({path:"/tmp/xhs_publish_submit_debug.png"});const H=X?.via?` (via=${X.via})`:"",O=X?.error?`, error=${X.error}`:"",B=X?.lastMethodError?`, lastMethodError=${X.lastMethodError}`:"";throw Error(`未能触发「${x[0]}」按钮${H}${O}${B}。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_submit_debug.png")}await z.wait({time:4});const w=await z.evaluate("() => location.href"),Hz=j?["草稿已保存","暂存成功","保存成功","保存于","图文笔记("]:["发布成功","上传成功"],E=await z.evaluate(`
646
+ `)){F.ok=!0;F.via="auto-save"}}}}if(!F?.ok){await z.screenshot({path:"/tmp/xhs_publish_submit_debug.png"});const j=F?.via?` (via=${F.via})`:"",B=F?.error?`, error=${F.error}`:"",D=F?.lastMethodError?`, lastMethodError=${F.lastMethodError}`:"";throw Error(`未能触发「${c[0]}」按钮${j}${B}${D}。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_submit_debug.png")}await z.wait({time:4});const N=await z.evaluate("() => location.href"),Xz=V?["草稿已保存","暂存成功","保存成功","保存于","图文笔记("]:["发布成功","上传成功"],d=await z.evaluate(`
628
647
  (markers => {
629
648
  for (const el of document.querySelectorAll('*')) {
630
649
  if (el.tagName === 'STYLE' || el.tagName === 'SCRIPT') continue;
@@ -633,5 +652,5 @@ import*as _ from"node:fs";import*as D from"node:path";import{CommandExecutionErr
633
652
  if (el.children.length === 0 && markers.some(marker => text.includes(marker))) return text;
634
653
  }
635
654
  return '';
636
- })(${JSON.stringify(Hz)})
637
- `),Vz=!w.includes("/publish/publish"),Gz=E.length>0||Vz,c=j?"暂存成功":"发布成功";if(!Gz)throw new V(`${c}无法确认:未观察到成功提示或提交后跳转。`+(w?`当前 URL: ${w}。`:"当前 URL 为空。")+"若确属失败,直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。");return[{status:`✅ ${c}`,detail:[`"${K}"`,v?`${U.length}张文字配图${G.length?` + ${G.length}张图片`:""}${M&&M!==k?` (${M})`:""}`:`${G.length}张图片`,b.length?`话题: ${b.join(" ")}`:"",E||w||""].filter(Boolean).join(" · ")}]}});
655
+ })(${JSON.stringify(Xz)})
656
+ `),Wz=!N.includes("/publish/publish");let T=d.length>0||Wz;const S=V?"暂存成功":"发布成功";let k=null;if(!V){k=await dz(z,K,T?4:8);if(k)T=!0}if(!T){if(V)throw new H(`${S}无法确认:未观察到成功提示或提交后跳转。`+(N?`当前 URL: ${N}。`:"当前 URL 为空。")+"先用 `xiaohongshu drafts` 查草稿箱确认是否已存上;确认没有再重新运行本命令,避免堆出重复草稿。");throw new H(`${S}无法确认:未观察到成功提示或提交后跳转,创作者后台也暂未查到同标题笔记。`+(N?`当前 URL: ${N}。`:"")+"注意:发布按钮已被触发,笔记仍可能延迟入库。禁止立即重发——请等 1-2 分钟后用 `xiaohongshu creator-notes` 回查本篇标题是否已出现;确认不存在后才重新运行本 publish 命令,否则会重复发布。")}return[{status:`✅ ${S}`,detail:[`"${K}"`,v?`${G.length}张文字配图${X.length?` + ${X.length}张图片`:""}${A&&A!==M?` (${A})`:""}`:`${X.length}张图片`,y.length?`话题: ${y.join(" ")}`:"",b.length?`⚠️ 话题未附上(已自动跳过,不影响发布): ${b.map((j)=>j.topic).join(" ")}`:"",k?.id?`笔记ID: ${k.id}`:"",d||(k?"已在创作者后台确认":"")||N||""].filter(Boolean).join(" · "),url:k?.id?`https://www.xiaohongshu.com/explore/${k.id}`:""}]}});
@@ -18,6 +18,25 @@ export interface ResolveSuccess {
18
18
  * clean `exact` match — the page changed, the action still succeeded.
19
19
  */
20
20
  match_level: TargetMatchLevel;
21
+ /**
22
+ * Which physical click path executed: `cdp` (trusted Input.dispatchMouseEvent),
23
+ * `js` (el.click() fallback), or `ax` (accessibility-node click). Surfaced so
24
+ * agents can tell a trusted synthetic click from the untrusted JS fallback.
25
+ */
26
+ click_method?: 'cdp' | 'js' | 'ax';
27
+ /**
28
+ * Result of hit-testing the click point against the resolved element:
29
+ * `target` (the point lands on the element or a descendant — trustworthy),
30
+ * `other` (an overlay/sibling covers it — the CDP click would miss), or
31
+ * `none` (nothing at the point). Only set on click().
32
+ */
33
+ hit?: 'target' | 'ancestor' | 'other' | 'none';
34
+ /**
35
+ * True when the click was retargeted from the resolved element to a nearby
36
+ * clickable ancestor (e.g. an <svg> icon whose click handler lives on the
37
+ * wrapping <div>), so the handler actually fires. See issue #2071.
38
+ */
39
+ retargeted?: boolean;
21
40
  }
22
41
  export interface FillTextResult extends ResolveSuccess {
23
42
  filled: boolean;
@@ -44,7 +44,7 @@ ${G}
44
44
  clearTimeout(timer);
45
45
  }
46
46
  })()
47
- `,{request:W}),Z=Q.url||G;if(Q.error)throw new M("FETCH_ERROR",`Browser fetch failed for ${Z}: ${Q.error}`,"Check that the page is reachable and the current browser profile has access.");if(!Q.ok)throw new M("FETCH_ERROR",`HTTP ${Q.status??0}${Q.statusText?` ${Q.statusText}`:""} from ${Z}`,A(Q.text));const _=Q.text??"";if(!_.trim())return null;try{return JSON.parse(_)}catch{const $=Q.contentType?` (${Q.contentType})`:"";throw new M("FETCH_ERROR",`Expected JSON from ${Z}${$}`,A(_))}}async annotatedScreenshot(G={}){await this.snapshot({source:"dom",viewportExpand:0});try{await this.evaluate(m());return await this.screenshot({...G,annotate:!1})}finally{await this.evaluate(u()).catch(()=>{})}}async click(G,H={}){const W=await this.tryClickAxRef(G);if(W)return W;const Q=await Y(this,G,H),Z=await this.tryCdpOnResolvedElement("DOM.scrollIntoViewIfNeeded"),_=await this.evaluate(P({skipScroll:Z}));if(_?.visible===!0){if(await this.tryNativeClick(_.x,_.y))return Q}const $=await this.evaluate(x({skipScroll:Z}));if(typeof $==="string"||$==null)return Q;if($.status==="clicked")return Q;if($.x!=null&&$.y!=null){if(await this.tryNativeClick($.x,$.y))return Q}throw Error(`Click failed: ${$.error??"JS click and CDP fallback both failed"}`)}async tryNativeClick(G,H){const W=this.nativeClick;if(typeof W!=="function")return!1;try{await W.call(this,G,H);return!0}catch{return!1}}async tryNativeMouseMove(G,H){const W=this.cdp;if(typeof W!=="function")return!1;try{await W.call(this,"Input.dispatchMouseEvent",{type:"mouseMoved",x:G,y:H});return!0}catch{return!1}}async tryNativeDoubleClick(G,H){const W=this.cdp;if(typeof W!=="function")return!1;try{await W.call(this,"Input.dispatchMouseEvent",{type:"mouseMoved",x:G,y:H});await W.call(this,"Input.dispatchMouseEvent",{type:"mousePressed",x:G,y:H,button:"left",clickCount:1});await W.call(this,"Input.dispatchMouseEvent",{type:"mouseReleased",x:G,y:H,button:"left",clickCount:1});await W.call(this,"Input.dispatchMouseEvent",{type:"mousePressed",x:G,y:H,button:"left",clickCount:2});await W.call(this,"Input.dispatchMouseEvent",{type:"mouseReleased",x:G,y:H,button:"left",clickCount:2});return!0}catch{return!1}}async tryNativeDrag(G,H){const W=this.cdp;if(typeof W!=="function")return!1;const Q=Math.round((G.x+H.x)/2),Z=Math.round((G.y+H.y)/2);try{await W.call(this,"Input.dispatchMouseEvent",{type:"mouseMoved",x:G.x,y:G.y});await W.call(this,"Input.dispatchMouseEvent",{type:"mousePressed",x:G.x,y:G.y,button:"left",clickCount:1});await W.call(this,"Input.dispatchMouseEvent",{type:"mouseMoved",x:Q,y:Z,button:"left",buttons:1});await W.call(this,"Input.dispatchMouseEvent",{type:"mouseMoved",x:H.x,y:H.y,button:"left",buttons:1});await W.call(this,"Input.dispatchMouseEvent",{type:"mouseReleased",x:H.x,y:H.y,button:"left",clickCount:1});return!0}catch{return!1}}async tryClickAxRef(G){if(!/^\d+$/.test(G))return null;const H=this._axRefs.get(G);if(!H)return null;const W=this.nativeClick;if(typeof W!=="function")return null;const Q=await this.resolveAxRefPoint(H);if(!Q)return null;try{await W.call(this,Q.x,Q.y);return{matches_n:1,match_level:Q.matchLevel}}catch{return null}}async resolveAxRefPoint(G){const H=this.cdp;if(typeof H!=="function")return null;if(G.backendNodeId!=null){const _=await this.axBoxCenter(G.backendNodeId,G.frame).catch(()=>null);if(_)return{..._,matchLevel:"exact"}}await H.call(this,"Accessibility.enable",C(G.frame));const W=await H.call(this,"Accessibility.getFullAXTree",j(G.frame)).catch(()=>null),Q=w(W,G);if(!Q?.backendNodeId)return null;this._axRefs.set(G.ref,Q);const Z=await this.axBoxCenter(Q.backendNodeId,Q.frame).catch(()=>null);return Z?{...Z,matchLevel:"reidentified"}:null}async axBoxCenter(G,H){const W=this.cdp;if(typeof W!=="function")return null;const Q=await W.call(this,"DOM.getBoxModel",{backendNodeId:G,...H?.sessionId?{frameId:H.frameId,sessionId:H.sessionId,...H.targetUrl?{targetUrl:H.targetUrl}:{}}:{}}),Z=Array.isArray(Q?.model?.content)&&Q.model.content.length>=8?Q.model.content:Array.isArray(Q?.model?.border)&&Q.model.border.length>=8?Q.model.border:null;if(!Z)return null;const _=Z.slice(0,8).map(($)=>typeof $==="number"?$:Number($));if(_.some(($)=>!Number.isFinite($)))return null;return{x:Math.round((_[0]+_[2]+_[4]+_[6])/4),y:Math.round((_[1]+_[3]+_[5]+_[7])/4)}}async tryNativeType(G){const H=this.nativeType;if(typeof H==="function")try{await H.call(this,G);return!0}catch{}const W=this.insertText;if(typeof W!=="function")return!1;try{await W.call(this,G);return!0}catch{return!1}}async tryNativeKeyPress(G,H){const W=this.nativeKeyPress;if(typeof W!=="function")return!1;try{await W.call(this,G,H);return!0}catch{return!1}}async isResolvedFocused(){try{return await this.evaluate(`
47
+ `,{request:W}),Z=Q.url||G;if(Q.error)throw new M("FETCH_ERROR",`Browser fetch failed for ${Z}: ${Q.error}`,"Check that the page is reachable and the current browser profile has access.");if(!Q.ok)throw new M("FETCH_ERROR",`HTTP ${Q.status??0}${Q.statusText?` ${Q.statusText}`:""} from ${Z}`,A(Q.text));const _=Q.text??"";if(!_.trim())return null;try{return JSON.parse(_)}catch{const $=Q.contentType?` (${Q.contentType})`:"";throw new M("FETCH_ERROR",`Expected JSON from ${Z}${$}`,A(_))}}async annotatedScreenshot(G={}){await this.snapshot({source:"dom",viewportExpand:0});try{await this.evaluate(m());return await this.screenshot({...G,annotate:!1})}finally{await this.evaluate(u()).catch(()=>{})}}async click(G,H={}){const W=await this.tryClickAxRef(G);if(W)return W;const Q=await Y(this,G,H),Z=await this.tryCdpOnResolvedElement("DOM.scrollIntoViewIfNeeded"),_=await this.evaluate(P({skipScroll:Z,forClick:!0})),$={hit:_?.hit,retargeted:_?.retargeted};if(_?.visible===!0&&(_.hit==="target"||_.hit==="ancestor")){if(await this.tryNativeClick(_.x,_.y))return{...Q,click_method:"cdp",...$}}const D=await this.evaluate(x({skipScroll:Z}));if(typeof D==="string"||D==null)return{...Q,click_method:"js",...$};if(D.status==="clicked")return{...Q,click_method:"js",...$};if(D.x!=null&&D.y!=null){if(await this.tryNativeClick(D.x,D.y))return{...Q,click_method:"cdp",...$}}throw Error(`Click failed: ${D.error??"JS click and CDP fallback both failed"}`)}async tryNativeClick(G,H){const W=this.nativeClick;if(typeof W!=="function")return!1;try{await W.call(this,G,H);return!0}catch{return!1}}async tryNativeMouseMove(G,H){const W=this.cdp;if(typeof W!=="function")return!1;try{await W.call(this,"Input.dispatchMouseEvent",{type:"mouseMoved",x:G,y:H});return!0}catch{return!1}}async tryNativeDoubleClick(G,H){const W=this.cdp;if(typeof W!=="function")return!1;try{await W.call(this,"Input.dispatchMouseEvent",{type:"mouseMoved",x:G,y:H});await W.call(this,"Input.dispatchMouseEvent",{type:"mousePressed",x:G,y:H,button:"left",clickCount:1});await W.call(this,"Input.dispatchMouseEvent",{type:"mouseReleased",x:G,y:H,button:"left",clickCount:1});await W.call(this,"Input.dispatchMouseEvent",{type:"mousePressed",x:G,y:H,button:"left",clickCount:2});await W.call(this,"Input.dispatchMouseEvent",{type:"mouseReleased",x:G,y:H,button:"left",clickCount:2});return!0}catch{return!1}}async tryNativeDrag(G,H){const W=this.cdp;if(typeof W!=="function")return!1;const Q=Math.round((G.x+H.x)/2),Z=Math.round((G.y+H.y)/2);try{await W.call(this,"Input.dispatchMouseEvent",{type:"mouseMoved",x:G.x,y:G.y});await W.call(this,"Input.dispatchMouseEvent",{type:"mousePressed",x:G.x,y:G.y,button:"left",clickCount:1});await W.call(this,"Input.dispatchMouseEvent",{type:"mouseMoved",x:Q,y:Z,button:"left",buttons:1});await W.call(this,"Input.dispatchMouseEvent",{type:"mouseMoved",x:H.x,y:H.y,button:"left",buttons:1});await W.call(this,"Input.dispatchMouseEvent",{type:"mouseReleased",x:H.x,y:H.y,button:"left",clickCount:1});return!0}catch{return!1}}async tryClickAxRef(G){if(!/^\d+$/.test(G))return null;const H=this._axRefs.get(G);if(!H)return null;const W=this.nativeClick;if(typeof W!=="function")return null;const Q=await this.resolveAxRefPoint(H);if(!Q)return null;try{await W.call(this,Q.x,Q.y);return{matches_n:1,match_level:Q.matchLevel,click_method:"ax"}}catch{return null}}async resolveAxRefPoint(G){const H=this.cdp;if(typeof H!=="function")return null;if(G.backendNodeId!=null){const _=await this.axBoxCenter(G.backendNodeId,G.frame).catch(()=>null);if(_)return{..._,matchLevel:"exact"}}await H.call(this,"Accessibility.enable",C(G.frame));const W=await H.call(this,"Accessibility.getFullAXTree",j(G.frame)).catch(()=>null),Q=w(W,G);if(!Q?.backendNodeId)return null;this._axRefs.set(G.ref,Q);const Z=await this.axBoxCenter(Q.backendNodeId,Q.frame).catch(()=>null);return Z?{...Z,matchLevel:"reidentified"}:null}async axBoxCenter(G,H){const W=this.cdp;if(typeof W!=="function")return null;const Q=await W.call(this,"DOM.getBoxModel",{backendNodeId:G,...H?.sessionId?{frameId:H.frameId,sessionId:H.sessionId,...H.targetUrl?{targetUrl:H.targetUrl}:{}}:{}}),Z=Array.isArray(Q?.model?.content)&&Q.model.content.length>=8?Q.model.content:Array.isArray(Q?.model?.border)&&Q.model.border.length>=8?Q.model.border:null;if(!Z)return null;const _=Z.slice(0,8).map(($)=>typeof $==="number"?$:Number($));if(_.some(($)=>!Number.isFinite($)))return null;return{x:Math.round((_[0]+_[2]+_[4]+_[6])/4),y:Math.round((_[1]+_[3]+_[5]+_[7])/4)}}async tryNativeType(G){const H=this.nativeType;if(typeof H==="function")try{await H.call(this,G);return!0}catch{}const W=this.insertText;if(typeof W!=="function")return!1;try{await W.call(this,G);return!0}catch{return!1}}async tryNativeKeyPress(G,H){const W=this.nativeKeyPress;if(typeof W!=="function")return!1;try{await W.call(this,G,H);return!0}catch{return!1}}async isResolvedFocused(){try{return await this.evaluate(`
48
48
  (() => {
49
49
  const el = window.__resolved;
50
50
  return !!el && (document.activeElement === el || (typeof el.matches === 'function' && el.matches(':focus')));
@@ -77,6 +77,7 @@ export declare function resolveTargetJs(ref: string, opts?: ResolveOptions): str
77
77
  */
78
78
  export declare function boundingRectResolvedJs(opts?: {
79
79
  skipScroll?: boolean;
80
+ forClick?: boolean;
80
81
  }): string;
81
82
  /**
82
83
  * Generate JS for click that uses the unified resolver.
@@ -215,18 +215,112 @@ export function resolveTargetJs(j,g={}){const q=JSON.stringify(j),w=g.nth!==void
215
215
  return { ok: true, matches_n: matches.length, match_level: 'exact' };
216
216
  }
217
217
  })()
218
- `}export function boundingRectResolvedJs(j={}){return`
218
+ `}export function boundingRectResolvedJs(j={}){const g=j.skipScroll?"false":"true",q=j.forClick?"true":"false";return`
219
219
  (() => {
220
220
  const el = window.__resolved;
221
221
  if (!el) throw new Error('No resolved element');
222
- if (${j.skipScroll?"false":"true"}) el.scrollIntoView({ behavior: 'instant', block: 'center' });
223
- const rect = el.getBoundingClientRect();
222
+ if (${g}) el.scrollIntoView({ behavior: 'instant', block: 'center' });
223
+
224
+ const FOR_CLICK = ${q};
225
+ // hover()/dblClick() want the plain element centre — the retarget + hit-test
226
+ // below are click-only so those actions keep their original behaviour.
227
+ if (!FOR_CLICK) {
228
+ const r0 = el.getBoundingClientRect();
229
+ return {
230
+ x: Math.round(r0.left + r0.width / 2),
231
+ y: Math.round(r0.top + r0.height / 2),
232
+ w: Math.round(r0.width),
233
+ h: Math.round(r0.height),
234
+ visible: Math.round(r0.width) > 0 && Math.round(r0.height) > 0,
235
+ };
236
+ }
237
+
238
+ // Does this node OWN a click handler? Deliberately excludes cursor:pointer,
239
+ // which is an *inherited* CSS property — an <svg> icon inside a clickable
240
+ // <div> inherits the pointer cursor but owns no handler, so cursor can't
241
+ // decide whether a node is the real click target. See issue #2071.
242
+ const ownsClickHandler = (node) => {
243
+ if (!node || node.nodeType !== 1) return false;
244
+ const tag = node.tagName.toLowerCase();
245
+ if (['a','button','input','select','textarea','label','summary'].includes(tag)) return true;
246
+ const role = node.getAttribute && node.getAttribute('role');
247
+ if (role && ['button','link','menuitem','menuitemcheckbox','menuitemradio','tab','option','checkbox','radio','switch'].includes(role)) return true;
248
+ if (typeof node.onclick === 'function') return true;
249
+ if (node.hasAttribute && (node.hasAttribute('onclick') || node.hasAttribute('jsaction'))) return true;
250
+ try {
251
+ for (const k in node) {
252
+ if (k.charCodeAt(0) === 95 && (k.indexOf('__reactProps$') === 0 || k.indexOf('__reactEventHandlers$') === 0)) {
253
+ const p = node[k];
254
+ if (p && (p.onClick || p.onMouseDown || p.onMouseUp)) return true;
255
+ }
256
+ }
257
+ } catch (e) {}
258
+ return false;
259
+ };
260
+ // A retarget destination just needs to look clickable; here cursor:pointer
261
+ // IS a useful signal (the ancestor is where it was set).
262
+ const isClickableAncestor = (node) => {
263
+ if (ownsClickHandler(node)) return true;
264
+ try { return window.getComputedStyle(node).cursor === 'pointer'; } catch (e) { return false; }
265
+ };
266
+
267
+ // #2071: if the resolved node owns no click handler but a nearby ancestor
268
+ // is clickable, aim at that ancestor so the handler fires.
269
+ let target = el;
270
+ let retargeted = false;
271
+ if (!ownsClickHandler(el)) {
272
+ let a = el.parentElement, hops = 0;
273
+ while (a && hops < 4) {
274
+ if (isClickableAncestor(a)) { target = a; retargeted = true; break; }
275
+ a = a.parentElement; hops++;
276
+ }
277
+ }
278
+
279
+ const rect = target.getBoundingClientRect();
224
280
  const w = Math.round(rect.width);
225
281
  const h = Math.round(rect.height);
226
- const x = Math.round(rect.left + rect.width / 2);
227
- const y = Math.round(rect.top + rect.height / 2);
282
+ let x = Math.round(rect.left + rect.width / 2);
283
+ let y = Math.round(rect.top + rect.height / 2);
228
284
  const visible = w > 0 && h > 0;
229
- return { x, y, w, h, visible };
285
+
286
+ // #2076: verify the click point actually lands on the target. A trusted
287
+ // CDP click hit-tests at (x,y) and delivers the event to whatever is
288
+ // topmost there — an overlay/sibling can silently swallow it. Classify:
289
+ // 'target' — the point is the target or a light-DOM descendant.
290
+ // 'ancestor' — the point is an ANCESTOR of the target. This is the
291
+ // open-shadow-DOM case (elementFromPoint returns the shadow
292
+ // *host*, not the shadow content) and the "point sits over
293
+ // the target's own wrapper background" case. In both a CDP
294
+ // click at (x,y) still reaches the target — CDP hit-testing
295
+ // pierces shadow roots, and light-DOM clicks bubble up — so
296
+ // it is trustworthy, unlike an unrelated overlay.
297
+ // 'other' — an unrelated element covers the point (the real overlay
298
+ // bug); the caller then dispatches a direct DOM click.
299
+ const hitClass = (px, py) => {
300
+ let at = null;
301
+ try { at = document.elementFromPoint(px, py); } catch (e) { return 'none'; }
302
+ if (!at) return 'none';
303
+ if (at === target || target.contains(at)) return 'target';
304
+ if (at.contains && at.contains(target)) return 'ancestor';
305
+ return 'other';
306
+ };
307
+ let hit = visible ? hitClass(x, y) : 'none';
308
+ if (visible && hit !== 'target' && hit !== 'ancestor') {
309
+ const cands = [
310
+ [rect.left + rect.width * 0.5, rect.top + rect.height * 0.25],
311
+ [rect.left + rect.width * 0.25, rect.top + rect.height * 0.5],
312
+ [rect.left + rect.width * 0.75, rect.top + rect.height * 0.5],
313
+ [rect.left + rect.width * 0.5, rect.top + rect.height * 0.75],
314
+ [rect.left + 3, rect.top + 3],
315
+ [rect.right - 3, rect.bottom - 3],
316
+ ];
317
+ for (const c of cands) {
318
+ const px = Math.round(c[0]), py = Math.round(c[1]);
319
+ const hc = hitClass(px, py);
320
+ if (hc === 'target' || hc === 'ancestor') { x = px; y = py; hit = hc; break; }
321
+ }
322
+ }
323
+ return { x, y, w, h, visible, hit, retargeted };
230
324
  })()
231
325
  `}export function clickResolvedJs(j={}){return`
232
326
  (() => {