publishport-opencli 1.0.30 → 1.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/cli-manifest.json +345 -71
  2. package/clis/_shared/article/login.js +1 -1
  3. package/clis/_shared/login-wait.js +1 -0
  4. package/clis/_shared/site-auth.js +1 -1
  5. package/clis/baijiahao/article.js +2 -2
  6. package/clis/baijiahao/auth.js +18 -0
  7. package/clis/baijiahao/lists.js +1 -1
  8. package/clis/baijiahao/stats.js +1 -1
  9. package/clis/bilibili/coin.js +1 -1
  10. package/clis/bilibili/comment.js +1 -1
  11. package/clis/bilibili/comments.js +1 -1
  12. package/clis/bilibili/dm-list.js +1 -1
  13. package/clis/bilibili/dm-read.js +1 -1
  14. package/clis/bilibili/favorite-add.js +1 -1
  15. package/clis/bilibili/me.js +1 -1
  16. package/clis/bilibili/search.js +1 -1
  17. package/clis/bilibili/stats.js +1 -1
  18. package/clis/bilibili/user-videos.js +1 -1
  19. package/clis/bilibili/utils.js +7 -7
  20. package/clis/cnblogs/article.js +1 -1
  21. package/clis/csdn/stats.js +7 -13
  22. package/clis/devto/publish.js +20 -4
  23. package/clis/douyin/_shared/work-list.js +1 -0
  24. package/clis/douyin/publish.js +9 -8
  25. package/clis/douyin/stats.js +1 -1
  26. package/clis/douyin/videos.js +1 -1
  27. package/clis/jike/comments.js +2 -1
  28. package/clis/jike/like.js +1 -1
  29. package/clis/jike/notifications.js +2 -115
  30. package/clis/jike/post.js +2 -2
  31. package/clis/jike/topic.js +1 -1
  32. package/clis/jike/topics.js +3 -0
  33. package/clis/nowcoder/auth.js +1 -24
  34. package/clis/nowcoder/comment.js +1 -1
  35. package/clis/nowcoder/comments.js +1 -1
  36. package/clis/nowcoder/delete.js +1 -1
  37. package/clis/nowcoder/detail.js +1 -48
  38. package/clis/nowcoder/dm-list.js +1 -1
  39. package/clis/nowcoder/dm-read.js +1 -1
  40. package/clis/nowcoder/dm-send.js +1 -1
  41. package/clis/nowcoder/experience.js +1 -22
  42. package/clis/nowcoder/inbox.js +1 -1
  43. package/clis/nowcoder/like-status.js +1 -0
  44. package/clis/nowcoder/like.js +1 -1
  45. package/clis/nowcoder/my.js +1 -1
  46. package/clis/nowcoder/notifications.js +1 -16
  47. package/clis/nowcoder/post.js +1 -1
  48. package/clis/nowcoder/recommend.js +1 -1
  49. package/clis/nowcoder/search.js +1 -32
  50. package/clis/nowcoder/utils.js +38 -44
  51. package/clis/qiita/publish.js +2 -2
  52. package/clis/quora/__fixtures__/dom.html +24 -0
  53. package/clis/quora/answer-detail.js +1 -1
  54. package/clis/quora/question.js +25 -1
  55. package/clis/quora/search.js +1 -1
  56. package/clis/quora/shared.js +18 -17
  57. package/clis/quora/upvote.js +5 -13
  58. package/clis/reddit/comment.js +25 -25
  59. package/clis/reddit/reply.js +7 -5
  60. package/clis/reddit/search.js +1 -51
  61. package/clis/reddit/shared.js +13 -0
  62. package/clis/reddit/subreddit-info.js +10 -4
  63. package/clis/reddit/subreddit-rules.js +1 -0
  64. package/clis/reddit/subreddit.js +1 -52
  65. package/clis/reddit/upvote.js +33 -10
  66. package/clis/reddit/user-comments.js +1 -20
  67. package/clis/reddit/user-posts.js +1 -17
  68. package/clis/twitter/post.js +63 -39
  69. package/clis/wechat-channels/auth.js +34 -31
  70. package/clis/wechat-channels/publish.js +295 -130
  71. package/clis/xianyu/watch.js +43 -1
  72. package/clis/xiaohongshu/_shared/note-manager.js +37 -0
  73. package/clis/xiaohongshu/creator-notes.js +9 -43
  74. package/clis/xiaohongshu/delete-note.js +7 -19
  75. package/clis/xiaohongshu/draft-delete.js +2 -2
  76. package/clis/xiaohongshu/publish.js +34 -43
  77. package/clis/xiaohongshu/search.js +18 -3
  78. package/clis/youtube/publish.js +11 -11
  79. package/clis/zhihu/answer-comments.js +2 -11
  80. package/clis/zhihu/answer-detail.js +1 -11
  81. package/clis/zhihu/comment.js +4 -4
  82. package/clis/zhihu/like.js +17 -3
  83. package/clis/zhihu/question.js +20 -6
  84. package/clis/zhihu/read-shared.js +26 -0
  85. package/clis/zhihu/recommend.js +1 -7
  86. package/clis/zhihu/search.js +1 -11
  87. package/dist/src/browser/base-page.d.ts +41 -1
  88. package/dist/src/browser/base-page.js +29 -16
  89. package/dist/src/browser/find.d.ts +31 -4
  90. package/dist/src/browser/find.js +76 -23
  91. package/dist/src/browser/utils.d.ts +30 -0
  92. package/dist/src/browser/utils.js +7 -1
  93. package/dist/src/cli.js +22 -22
  94. package/dist/src/commanderAdapter.d.ts +14 -0
  95. package/dist/src/commanderAdapter.js +4 -3
  96. package/dist/src/doctor.d.ts +7 -0
  97. package/dist/src/doctor.js +9 -9
  98. package/dist/src/errors.d.ts +6 -0
  99. package/dist/src/errors.js +1 -1
  100. package/dist/src/execution.d.ts +1 -0
  101. package/dist/src/execution.js +2 -2
  102. package/dist/src/main.js +6 -6
  103. package/dist/src/types.d.ts +28 -0
  104. package/package.json +1 -1
@@ -0,0 +1,37 @@
1
+ import{AuthRequiredError as S,CommandExecutionError as _}from"@jackwener/opencli/errors";export const NOTE_MANAGER_URL="https://creator.xiaohongshu.com/new/note-manager";const b="/creator/note/user/posted";export const NOTE_MANAGER_TABS={all:0,published:1,reviewing:2,rejected:3};const x=/^[0-9a-f]{24}$/i,T=3,C=2,N=0.5,P=16;function p(t){if(t&&typeof t==="object"&&!Array.isArray(t)&&"session"in t&&"data"in t)return t.data;return t}export function buildSignedNoteUrl(t,e,r){if(!t||!e)return"";return`https://www.xiaohongshu.com/explore/${t}?xsec_token=${e}&xsec_source=${r||""}`}function d(t){const e=Number(t);return Number.isFinite(e)?e:0}export function mapPostedNote(t){const e=typeof t?.id==="string"?t.id:"";return{id:e,title:typeof t?.display_title==="string"?t.display_title:"",time:typeof t?.time==="string"?t.time:"",views:d(t?.view_count),likes:d(t?.likes),collects:d(t?.collected_count),comments:d(t?.comments_count),shares:d(t?.shared_count),type:typeof t?.type==="string"?t.type:"",sticky:t?.sticky===!0,tabStatus:Number.isFinite(Number(t?.tab_status))?Number(t.tab_status):null,permission:typeof t?.permission_msg==="string"?t.permission_msg:"",noteUrl:buildSignedNoteUrl(e,t?.xsec_token,t?.xsec_source)}}export function harvestPostedCaptures(t,e,r){for(const i of Array.isArray(t)?t:[]){const u=String(i?.url||"");if(!u.includes(b))continue;let o;try{o=new URL(u,"https://creator.xiaohongshu.com")}catch{continue}if(Number(o.searchParams.get("tab"))!==e)continue;const c=Number(o.searchParams.get("page"));if(!Number.isInteger(c)||c<0)continue;if(Number(i?.responseStatus||0)!==200||typeof i?.responsePreview!=="string")continue;let n;try{n=JSON.parse(i.responsePreview)}catch{continue}if(!Array.isArray(n?.data?.notes))continue;const l=Number(n.data.page);r.set(c,{notes:n.data.notes,nextPage:Number.isInteger(l)?l:-1,total:Array.isArray(n.data.tags)?d(n.data.tags.find((a)=>a?.id==="special.note_time_desc")?.notes_count):0})}return r}const k=`(() => {
2
+ const noteIdOf = (card) => {
3
+ try {
4
+ const parsed = JSON.parse(card.getAttribute('data-impression') || '');
5
+ const id = parsed && parsed.noteTarget && parsed.noteTarget.value && parsed.noteTarget.value.noteId;
6
+ return typeof id === 'string' ? id : '';
7
+ } catch { return ''; }
8
+ };
9
+ const text = (el) => ((el && (el.innerText || el.textContent)) || '').trim();
10
+ const cards = Array.from(document.querySelectorAll('.note-card[data-impression], div.note[data-impression]'));
11
+ return cards.map((card) => ({
12
+ id: noteIdOf(card),
13
+ title: text(card.querySelector('.note-card__title, .title, .raw')),
14
+ time: text(card.querySelector('.note-card__time, .time_status, .time')),
15
+ metrics: Array.from(card.querySelectorAll('.note-card__stat, .icon_list .icon'))
16
+ .map((el) => parseInt(text(el), 10))
17
+ .filter((value) => Number.isFinite(value)),
18
+ })).filter((card) => card.id);
19
+ })()`;export function normalizeCardTime(t){const e=String(t||"").match(/(\d{4})\D(\d{1,2})\D(\d{1,2})\D+(\d{1,2}):(\d{2})/);if(!e)return"";const r=(i)=>String(i).padStart(2,"0");return`${e[1]}-${r(e[2])}-${r(e[3])} ${r(e[4])}:${e[5]}`}function v(t){const e=Array.isArray(t?.metrics)?t.metrics:[];return{id:String(t?.id||""),title:String(t?.title||""),time:normalizeCardTime(t?.time),views:e[0]??0,comments:e[1]??0,likes:e[2]??0,collects:e[3]??0,shares:e[4]??0,type:"",sticky:!1,tabStatus:null,permission:"",noteUrl:""}}const E="document.querySelectorAll('.note-card[data-impression], div.note[data-impression]').length";async function h(t){let e=-1;for(let r=0;r<P;r++){await t.wait({time:N});const i=Number(p(await t.evaluate(E)));if(Number.isFinite(i)&&i>0&&i===e)return i;e=Number.isFinite(i)?i:-1;if(e===0&&r>=5)return 0}return Math.max(e,0)}async function O(t){const e=p(await t.evaluate(k));return Array.isArray(e)?e.map(v):[]}async function R(t){const e=p(await t.evaluate("location.href"));if(typeof e!=="string")return;let r;try{r=new URL(e)}catch{return}if(/\/login(?:[/?#]|$)/i.test(r.pathname+r.search))throw new S("creator.xiaohongshu.com")}async function g(t,e){for(let r=0;r<20;r++){if(p(await t.evaluate(`(() => {
20
+ const tabs = Array.from(document.querySelectorAll('.tab-list .tab-item'));
21
+ if (tabs.length < ${Object.keys(NOTE_MANAGER_TABS).length}) return false;
22
+ tabs[${e}].click();
23
+ return true;
24
+ })()`))===!0)return;await t.wait({time:N})}throw new _("xiaohongshu note-manager: 笔记管理页没有出现预期的四个状态标签,页面结构可能已改版。")}async function D(t){await t.evaluate(`(() => {
25
+ const first = document.querySelector('.note-card[data-impression], div.note[data-impression]');
26
+ let el = first && first.parentElement;
27
+ while (el) {
28
+ const style = window.getComputedStyle(el);
29
+ if ((style.overflowY === 'auto' || style.overflowY === 'scroll') && el.scrollHeight > el.clientHeight + 10) {
30
+ el.scrollTop = el.scrollHeight;
31
+ return true;
32
+ }
33
+ el = el.parentElement;
34
+ }
35
+ window.scrollTo(0, document.body.scrollHeight);
36
+ return false;
37
+ })()`)}export async function loadNoteManagerNotes(t,e={}){const r=e.tab||"all",i=NOTE_MANAGER_TABS[r];if(i===void 0)throw new _(`xiaohongshu note-manager: unknown tab ${JSON.stringify(r)}`);const u=Number.isInteger(e.limit)&&e.limit>0?e.limit:20,o=typeof e.untilId==="string"?e.untilId:"";await t.goto(NOTE_MANAGER_URL);await t.wait({time:1.5});await R(t);await h(t);const c=typeof t.startNetworkCapture==="function"&&typeof t.readNetworkCapture==="function"&&await t.startNetworkCapture(b)!==!1;let n=null;for(let a=0;c&&a<C;a++){const s=await I(t,i,u,o);if(s&&(!n||s.notes.length>n.notes.length))n=s;if(n&&(n.complete||n.notes.length>=u||n.notes.some((m)=>m.id===o)))break}if(n)return n;if(i!==NOTE_MANAGER_TABS.all)await g(t,i);await h(t);const l=await O(t);return{notes:l,complete:l.length===0,total:0,source:"dom"}}async function I(t,e,r,i){await t.readNetworkCapture();const u=e===NOTE_MANAGER_TABS.all?NOTE_MANAGER_TABS.published:NOTE_MANAGER_TABS.all;await g(t,u);await t.wait({time:1});await h(t);await t.readNetworkCapture();await g(t,e);await t.wait({time:1});await h(t);const o=new Map;harvestPostedCaptures(await t.readNetworkCapture(),e,o);if(!o.has(0))return null;const c=()=>{const a=[],s=new Set;let m=0,w=0;while(o.has(m)){const f=o.get(m);w=Math.max(w,f.total);for(const A of f.notes){const y=mapPostedNote(A);if(!x.test(y.id)||s.has(y.id))continue;s.add(y.id);a.push(y)}if(f.nextPage<0||f.nextPage===m)return{notes:a,total:w,nextPage:-1};m=f.nextPage}return{notes:a,total:w,nextPage:m}};let n=c();const l=()=>n.nextPage<0||e===NOTE_MANAGER_TABS.all&&n.total>0&&n.notes.length>=n.total;while(!l()&&n.notes.length<r&&!n.notes.some((a)=>a.id===i)){const a=n.nextPage;for(let s=0;s<T&&!o.has(a);s++){await D(t);await t.wait({time:1});await h(t);harvestPostedCaptures(await t.readNetworkCapture(),e,o)}if(!o.has(a))break;n=c()}return{notes:n.notes,complete:l(),total:n.total,source:"api"}}
@@ -1,5 +1,4 @@
1
- import{cli as k,Strategy as v}from"@jackwener/opencli/registry";import{CommandExecutionError as x,EmptyResultError as S}from"@jackwener/opencli/errors";const R=/^发布于 (\d{4}年\d{2}月\d{2}日 \d{2}:\d{2})$/,M=/^\d+$/,H=/可见$/,E="/api/galaxy/creator/datacenter/note/analyze/list",b=10,A=20,N=0.5,O="https://creator.xiaohongshu.com/statistics/note-detail",L=/&quot;noteId&quot;:&quot;([0-9a-f]{24})&quot;/g;function w(e){return e?`${O}?noteId=${encodeURIComponent(e)}`:""}function P(e){if(!e)return"";const t=new Date(e+28800000),r=(a)=>String(a).padStart(2,"0");return`${t.getUTCFullYear()}年${r(t.getUTCMonth()+1)}月${r(t.getUTCDate())}日 ${r(t.getUTCHours())}:${r(t.getUTCMinutes())}`}export function parseCreatorNotesText(e){const t=e.split(`
2
- `).map((n)=>n.trim()).filter(Boolean),r=[],a=new Set;for(let n=0;n<t.length;n++){const o=t[n].match(R);if(!o)continue;let s=n-1;while(s>=0&&H.test(t[s]))s--;if(s<0)continue;const l=t[s],c=[];let i=n+1;while(i<t.length&&M.test(t[i])&&c.length<5){c.push(parseInt(t[i],10));i++}if(c.length<4)continue;const u=`${l}@@${o[1]}`;if(a.has(u))continue;a.add(u);r.push({id:"",title:l,date:o[1],views:c[0]??0,likes:c[2]??0,collects:c[3]??0,comments:c[1]??0,url:""});n=i-1}return r}export function parseCreatorNoteIdsFromHtml(e){const t=[],r=new Set;for(const a of e.matchAll(L)){const n=a[1];if(!n||r.has(n))continue;r.add(n);t.push(n)}return t}function U(e){return e.map((t)=>({id:t.id,title:t.title,date:t.date,views:t.metrics[0]??0,likes:t.metrics[2]??0,collects:t.metrics[3]??0,comments:t.metrics[1]??0,url:w(t.id)}))}function T(e){return(e??[]).map((t)=>({id:t.id??"",title:t.title??"",date:P(t.post_time),views:t.read_count??0,likes:t.like_count??0,collects:t.fav_count??0,comments:t.comment_count??0,url:w(t.id)}))}function f(e){if(e&&typeof e==="object"&&!Array.isArray(e)&&"session"in e&&"data"in e)return e.data;return e}async function q(e){await e.evaluate(`(() => {
1
+ import{cli as M,Strategy as R}from"@jackwener/opencli/registry";import{ArgumentError as H,CliError as P,CommandExecutionError as f,EmptyResultError as I}from"@jackwener/opencli/errors";import{NOTE_MANAGER_TABS as p,loadNoteManagerNotes as b}from"./_shared/note-manager.js";const g=Object.keys(p),x="/api/galaxy/creator/datacenter/note/analyze/list",N=10,E=20,A=0.5,U="https://creator.xiaohongshu.com/statistics/note-detail";function k(t){return t?`${U}?noteId=${encodeURIComponent(t)}`:""}function L(t){if(!t)return"";const e=new Date(t+28800000),r=(a)=>String(a).padStart(2,"0");return`${e.getUTCFullYear()}年${r(e.getUTCMonth()+1)}月${r(e.getUTCDate())}日 ${r(e.getUTCHours())}:${r(e.getUTCMinutes())}`}function S(t){return(t??[]).map((e)=>({id:e.id??"",title:e.title??"",date:L(e.post_time),views:e.read_count??0,likes:e.like_count??0,collects:e.fav_count??0,comments:e.comment_count??0,shares:null,status:"published",url:k(e.id),note_url:""}))}function z(t){const e=String(t||"").match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})$/);return e?`${e[1]}年${e[2]}月${e[3]}日 ${e[4]}:${e[5]}`:""}function m(t,e){return{id:t.id,title:t.title,date:z(t.time),views:t.views,likes:t.likes,collects:t.collects,comments:t.comments,shares:t.shares,status:e,url:k(t.id),note_url:t.noteUrl}}function C(t){const e=String(t??"all").trim().toLowerCase();if(!g.includes(e))throw new H(`--status must be one of ${g.join(" / ")}, got ${JSON.stringify(t)}`);return e}async function X(t,e){const r=new Map,a=e.filter((n)=>n.tabStatus!==p.published);for(const n of e)if(n.tabStatus===p.published)r.set(n.id,"published");if(a.length===0)return r;for(const n of["reviewing","rejected"]){const s=await b(t,{tab:n,limit:a.length});for(const o of s.notes)if(!r.has(o.id))r.set(o.id,n)}return r}async function j(t,e,r){const a=await b(t,{tab:r,limit:e});if(a.notes.length===0)return{notes:[],listed:a};if(!a.complete&&a.notes.length<e){const o=a.total>0&&r==="all"?`(账号共 ${a.total} 条)`:"";throw new f(`xiaohongshu creator-notes: 只读到最新 ${a.notes.length} 条笔记${o},后面的页没有加载出来;`+`不返回残缺列表。用 --limit ${a.notes.length} 取已读到的这些,更早的笔记暂时翻不到。`)}const n=a.notes.slice(0,e);if(r!=="all")return{notes:n.map((o)=>m(o,r)),listed:a};if(a.source!=="api")return{notes:n.map((o)=>m(o,"unknown")),listed:a};const s=await X(t,n);return{notes:n.map((o)=>m(o,s.get(o.id)??"unknown")),listed:a}}function w(t){if(t&&typeof t==="object"&&!Array.isArray(t)&&"session"in t&&"data"in t)return t.data;return t}async function D(t){await t.evaluate(`(() => {
3
2
  window.__xhsCapture = {};
4
3
  if (window.__xhsCaptureInstalled) return;
5
4
  window.__xhsCaptureInstalled = true;
@@ -39,31 +38,11 @@ import{cli as k,Strategy as v}from"@jackwener/opencli/registry";import{CommandEx
39
38
  if (key in OrigXHR) HookedXHR[key] = OrigXHR[key];
40
39
  }
41
40
  window.XMLHttpRequest = HookedXHR;
42
- })()`)}function y(e){const t=f(e);if(typeof t==="string")try{return JSON.parse(t)}catch{return{}}if(t&&typeof t==="object"&&!Array.isArray(t))return t;return{}}function C(e){try{const a=new URL(e,"https://creator.xiaohongshu.com"),n=Number.parseInt(a.searchParams.get("page_num")||"",10);if(Number.isFinite(n)&&n>0)return n}catch{}const t=String(e||"").match(/[?&]page_num=(\d+)/),r=Number.parseInt(t?.[1]||"",10);return Number.isFinite(r)&&r>0?r:Number.MAX_SAFE_INTEGER}function _(e){const t=[],r=new Set;let a=0;const n=Object.entries(e).filter(([o])=>o.includes("/note/analyze/list")).sort(([o],[s])=>C(o)-C(s));for(const[o,s]of n){if(!s?.ok)continue;try{const c=JSON.parse(s.body)?.data??{};if(typeof c.total==="number"&&c.total>a)a=c.total;for(const i of c.note_infos??[]){if(!i?.id||r.has(i.id))continue;r.add(i.id);t.push(i)}}catch{}}return{items:t,total:a}}function I(e,t,r){if(t<=0)return!0;return e.length>=Math.min(t,r)}async function D(e){let t={};for(let r=0;r<A;r++){await e.wait(N);const a=await e.evaluate("JSON.stringify(window.__xhsCapture || {})");t=y(a);if(Object.keys(t).some((n)=>n.includes("/note/analyze/list")))break}return t}async function X(e,t){const r=new Map,a=async()=>{const o=f(await e.evaluate(`() => {
43
- const noteIdRe = /"noteId":"([0-9a-f]{24})"/;
44
- return Array.from(document.querySelectorAll('div.note[data-impression], div.note')).map((card) => {
45
- const impression = card.getAttribute('data-impression') || '';
46
- const id = impression.match(noteIdRe)?.[1] || '';
47
- const title = (card.querySelector('.title, .raw')?.innerText || '').trim();
48
- return { id, title };
49
- }).filter((entry) => entry.id && entry.title);
50
- }`));for(const s of Array.isArray(o)?o:[])if(!r.has(s.id))r.set(s.id,s.title)},n=async()=>{return f(await e.evaluate(`(() => {
51
- const firstCard = document.querySelector('div.note[data-impression]');
52
- let el = firstCard && firstCard.parentElement;
53
- while (el) {
54
- const s = window.getComputedStyle(el);
55
- if ((s.overflowY === 'auto' || s.overflowY === 'scroll') && el.scrollHeight > el.clientHeight + 10) {
56
- el.scrollTop = el.scrollHeight;
57
- return true;
58
- }
59
- el = el.parentElement;
60
- }
61
- return false;
62
- })()`))};try{await e.goto("https://creator.xiaohongshu.com/new/note-manager");for(let o=0;o<12;o++){await e.wait(1);await a();if(r.size>=t)return r;await n()}return r}catch{return r}}async function z(e,t){await e.goto("https://creator.xiaohongshu.com/statistics");await q(e);await e.evaluate(`(() => {
41
+ })()`)}function y(t){const e=w(t);if(typeof e==="string")try{return JSON.parse(e)}catch{return{}}if(e&&typeof e==="object"&&!Array.isArray(e))return e;return{}}function O(t){try{const a=new URL(t,"https://creator.xiaohongshu.com"),n=Number.parseInt(a.searchParams.get("page_num")||"",10);if(Number.isFinite(n)&&n>0)return n}catch{}const e=String(t||"").match(/[?&]page_num=(\d+)/),r=Number.parseInt(e?.[1]||"",10);return Number.isFinite(r)&&r>0?r:Number.MAX_SAFE_INTEGER}function _(t){const e=[],r=new Set;let a=0;const n=Object.entries(t).filter(([s])=>s.includes("/note/analyze/list")).sort(([s],[o])=>O(s)-O(o));for(const[s,o]of n){if(!o?.ok)continue;try{const i=JSON.parse(o.body)?.data??{};if(typeof i.total==="number"&&i.total>a)a=i.total;for(const c of i.note_infos??[]){if(!c?.id||r.has(c.id))continue;r.add(c.id);e.push(c)}}catch{}}return{items:e,total:a}}function v(t,e,r){if(e<=0)return!0;return t.length>=Math.min(e,r)}async function F(t){let e={};for(let r=0;r<E;r++){await t.wait(A);const a=await t.evaluate("JSON.stringify(window.__xhsCapture || {})");e=y(a);if(Object.keys(e).some((n)=>n.includes("/note/analyze/list")))break}return e}async function J(t,e){await t.goto("https://creator.xiaohongshu.com/statistics");await D(t);await t.evaluate(`(() => {
63
42
  history.pushState({}, '', '/statistics/data-analysis?source=official');
64
43
  window.dispatchEvent(new PopStateEvent('popstate'));
65
- })()`);let r=await D(e),{items:a,total:n}=_(r);if(a.length===0)return[];const o=n>0?Math.ceil(n/b):1,s=Math.min(o,Math.ceil(t/b));for(let i=2;i<=s&&a.length<t;i++){if(!f(await e.evaluate(`(() => {
66
- const target = String(${i});
44
+ })()`);let r=await F(t),{items:a,total:n}=_(r);if(a.length===0)return[];const s=n>0?Math.ceil(n/N):1,o=Math.min(s,Math.ceil(e/N));for(let u=2;u<=o&&a.length<e;u++){if(!w(await t.evaluate(`(() => {
45
+ const target = String(${u});
67
46
  // .d-pagination-page renders the page number doubled (a visible span +
68
47
  // an accessibility span), so textContent for page 2 reads "22". Match
69
48
  // both the raw digit and the doubled form to tolerate either render.
@@ -74,34 +53,21 @@ import{cli as k,Strategy as v}from"@jackwener/opencli/registry";import{CommandEx
74
53
  });
75
54
  if (match) { match.click(); return true; }
76
55
  return false;
77
- })()`)))break;const h=a.length;let d=!1;for(let p=0;p<A;p++){await e.wait(N);const m=await e.evaluate("JSON.stringify(window.__xhsCapture || {})");r=y(m);const g=_(r);if(g.items.length>h){a=g.items;n=Math.max(n,g.total);d=!0;break}}if(!d)break}if(!I(a,n,t))throw new x(`xiaohongshu creator-notes: captured ${a.length} of ${Math.min(n,t)} expected analyze rows; refusing partial results`);const l=T(a).slice(0,t);if(l.filter((i)=>!i.title).length>0){const i=await X(e,l.length);for(const u of l)if(!u.title&&u.id&&i.has(u.id))u.title=i.get(u.id)}return l}async function F(e,t){const r=Math.min(Math.max(t,10),20),a=Math.max(1,Math.ceil(t/r)),n=[];await e.goto(`https://creator.xiaohongshu.com/statistics/data-analysis?type=0&page_size=${r}&page_num=1`);for(let o=1;o<=a&&n.length<t;o++){const s=`${E}?type=0&page_size=${r}&page_num=${o}`;let c=(await e.evaluate(`
56
+ })()`)))break;const c=a.length;let h=!1;for(let l=0;l<E;l++){await t.wait(A);const T=await t.evaluate("JSON.stringify(window.__xhsCapture || {})");r=y(T);const d=_(r);if(d.items.length>c){a=d.items;n=Math.max(n,d.total);h=!0;break}}if(!h)break}if(!v(a,n,e))throw new f(`xiaohongshu creator-notes: captured ${a.length} of ${Math.min(n,e)} expected analyze rows; refusing partial results`);return S(a).slice(0,e)}async function B(t,e){const r=Math.min(Math.max(e,10),20),a=Math.max(1,Math.ceil(e/r)),n=[];await t.goto(`https://creator.xiaohongshu.com/statistics/data-analysis?type=0&page_size=${r}&page_num=1`);for(let s=1;s<=a&&n.length<e;s++){const o=`${x}?type=0&page_size=${r}&page_num=${s}`;let i=(await t.evaluate(`
78
57
  async () => {
79
58
  try {
80
- const resp = await fetch(${JSON.stringify(s)}, { credentials: 'include' });
59
+ const resp = await fetch(${JSON.stringify(o)}, { credentials: 'include' });
81
60
  if (!resp.ok) return { error: 'HTTP ' + resp.status };
82
61
  return await resp.json();
83
62
  } catch (e) {
84
63
  return { error: e?.message ?? String(e) };
85
64
  }
86
65
  }
87
- `))?.data?.note_infos??[];if(!c.length){await e.installInterceptor(E);await e.evaluate(`
66
+ `))?.data?.note_infos??[];if(!i.length){await t.installInterceptor(x);await t.evaluate(`
88
67
  async () => {
89
68
  try {
90
- await fetch(${JSON.stringify(s)}, { credentials: 'include' });
69
+ await fetch(${JSON.stringify(o)}, { credentials: 'include' });
91
70
  } catch {}
92
71
  return true;
93
72
  }
94
- `);await e.wait(1);c=(await e.getInterceptedRequests()).find((h)=>Array.isArray(h?.data?.note_infos))?.data?.note_infos??[]}if(!c.length)break;n.push(...T(c));if(c.length<r)break}return n.slice(0,t)}export async function fetchCreatorNotes(e,t){let r=[];try{r=await z(e,t)}catch(a){if(a instanceof x)throw a}if(r.length===0)r=await F(e,t);if(r.length===0){await e.goto("https://creator.xiaohongshu.com/new/note-manager");const a=Math.max(0,Math.ceil(t/10)+1);for(let n=0;n<=a;n++){const o=await e.evaluate(`() => {
95
- const noteIdRe = /"noteId":"([0-9a-f]{24})"/;
96
- return Array.from(document.querySelectorAll('div.note[data-impression], div.note')).map((card) => {
97
- const impression = card.getAttribute('data-impression') || '';
98
- const id = impression.match(noteIdRe)?.[1] || '';
99
- const title = (card.querySelector('.title, .raw')?.innerText || '').trim();
100
- const dateText = (card.querySelector('.time_status, .time')?.innerText || '').trim();
101
- const date = dateText.replace(/^发布于\\s*/, '');
102
- const metrics = Array.from(card.querySelectorAll('.icon_list .icon'))
103
- .map((el) => parseInt((el.innerText || '').trim(), 10))
104
- .filter((value) => Number.isFinite(value));
105
- return { id, title, date, metrics };
106
- });
107
- }`),s=U(Array.isArray(o)?o:[]).filter((d)=>d.title&&d.date);if(s.length>0)r=s;if(r.length>=t||r.length>0&&n===0)break;const l=await e.evaluate("() => ({ text: document.body.innerText, html: document.body.innerHTML })"),c=typeof l?.text==="string"?l.text:"",i=typeof l?.html==="string"?l.html:"",u=parseCreatorNotesText(c),h=parseCreatorNoteIdsFromHtml(i);r=u.map((d,p)=>{const m=h[p]??"";return{...d,id:m,url:w(m)}});if(r.length>=t||n===a)break;await e.pressKey("PageDown");await e.wait(1)}}return r.slice(0,t)}k({site:"xiaohongshu",name:"creator-notes",access:"read",description:"小红书创作者笔记列表 + 每篇数据 (标题/日期/观看/点赞/收藏/评论)",domain:"creator.xiaohongshu.com",strategy:v.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"limit",type:"int",default:20,help:"Number of notes to return"}],columns:["rank","id","title","date","views","likes","collects","comments","url"],func:async(e,t)=>{const r=t.limit||20,a=await fetchCreatorNotes(e,r);if(!Array.isArray(a)||a.length===0)throw new S("xiaohongshu creator-notes","No notes found. Ensure you are logged into creator.xiaohongshu.com and the account has published notes.");return a.slice(0,r).map((n,o)=>({rank:o+1,id:n.id,title:n.title,date:n.date,views:n.views,likes:n.likes,collects:n.collects,comments:n.comments,url:n.url}))}});export const __test__={harvestAnalyzeListCaptures:_,isAnalyzeCaptureComplete:I,parseCaptureMapPayload:y,unwrapEvaluateResult:f};
73
+ `);await t.wait(1);i=(await t.getInterceptedRequests()).find((l)=>Array.isArray(l?.data?.note_infos))?.data?.note_infos??[]}if(!i.length)break;n.push(...S(i));if(i.length<r)break}return n.slice(0,e)}export async function fetchCreatorNotes(t,e,r={}){const a=C(r.status),{notes:n}=await j(t,e,a);if(n.length>0||a==="reviewing"||a==="rejected")return n;let s=[];try{s=await J(t,e)}catch(o){if(o instanceof P)throw o;throw new f(`xiaohongshu creator-notes: 数据分析页读取失败: ${o?.message??String(o)}`)}if(s.length===0)s=await B(t,e);return s.slice(0,e)}M({site:"xiaohongshu",name:"creator-notes",access:"read",description:"小红书创作者笔记列表 + 每篇数据 (标题/日期/观看/点赞/收藏/评论)",domain:"creator.xiaohongshu.com",strategy:R.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"limit",type:"int",default:20,help:"Number of notes to return"},{name:"status",default:"all",choices:g,help:"按状态筛选:all 全部(默认,含审核中/未通过) / published 已发布 / reviewing 审核中 / rejected 未通过"}],columns:["rank","id","title","status","date","views","likes","collects","comments","shares","url","note_url"],func:async(t,e)=>{const r=e.limit||20,a=C(e.status),n=await fetchCreatorNotes(t,r,{status:a});if(!Array.isArray(n)||n.length===0)throw new I("xiaohongshu creator-notes",a==="all"?"No notes found. Ensure you are logged into creator.xiaohongshu.com and the account has notes.":`账号当前没有状态为 ${a} 的笔记。`);return n.slice(0,r).map((s,o)=>({rank:o+1,id:s.id,title:s.title,status:s.status,date:s.date,views:s.views,likes:s.likes,collects:s.collects,comments:s.comments,shares:s.shares,url:s.url,note_url:s.note_url}))}});export const __test__={harvestAnalyzeListCaptures:_,isAnalyzeCaptureComplete:v,parseCaptureMapPayload:y,unwrapEvaluateResult:w};
@@ -1,4 +1,4 @@
1
- import{cli as I,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentError as s,AuthRequiredError as k,CliError as S,CommandExecutionError as n,EmptyResultError as E}from"@jackwener/opencli/errors";const A="https://creator.xiaohongshu.com/new/note-manager",h=3000,L=2000,T=1e4,m=1000,w=/^[0-9a-f]{24}$/i;function l(e){if(e&&typeof e==="object"&&"session"in e&&"data"in e)return e.data;return e}function U(e,o){if(typeof e!=="string")throw new n(`xiaohongshu/delete-note: malformed ${o} payload`);return e}function g(e,o){if(typeof e!=="boolean")throw new n(`xiaohongshu/delete-note: malformed ${o} payload`);return e}function N(e,o){if(!e||typeof e!=="object"||Array.isArray(e))throw new n(`xiaohongshu/delete-note: malformed ${o} payload`);return e}function b(e,o){const t=N(e,o);if(typeof t.ok!=="boolean")throw new n(`xiaohongshu/delete-note: malformed ${o} payload`);return t}function C(e){const o=String(e||"").toLowerCase();return o==="xiaohongshu.com"||o.endsWith(".xiaohongshu.com")}function O(e){return e.hostname.toLowerCase()==="creator.xiaohongshu.com"&&e.pathname.replace(/\/+$/,"")==="/statistics/note-detail"}function p(e){const o=String(e??"").trim();if(!o)throw new s("xiaohongshu/delete-note: note-id cannot be empty");if(w.test(o))return o.toLowerCase();if(!/^https:\/\//i.test(o))throw new s("xiaohongshu/delete-note: note-id must be a 24-character Xiaohongshu note ID or an exact Xiaohongshu note URL");let t;try{t=new URL(o)}catch{throw new s("xiaohongshu/delete-note: invalid note URL")}if(t.protocol!=="https:"||t.username||t.password||t.port||!C(t.hostname))throw new s("xiaohongshu/delete-note: note URL must be an exact https://*.xiaohongshu.com URL");const r=t.searchParams.get("noteId")||t.searchParams.get("note_id");if(r&&w.test(r)&&O(t))return r.toLowerCase();const i=t.pathname.match(/^\/(?:explore|note|search_result|discovery\/item)\/([0-9a-f]{24})\/?$/i)||t.pathname.match(/^\/user\/profile\/[^/?#]+\/([0-9a-f]{24})\/?$/i);if(i)return i[1].toLowerCase();throw new s("xiaohongshu/delete-note: note URL must contain a 24-character note ID")}function x(e,o){return`
1
+ import{cli as I,Strategy as R}from"@jackwener/opencli/registry";import{ArgumentError as l,CliError as A,CommandExecutionError as r,EmptyResultError as k}from"@jackwener/opencli/errors";import{NOTE_MANAGER_TABS as N,loadNoteManagerNotes as h}from"./_shared/note-manager.js";const m=".note-card[data-impression], div.note[data-impression]",v=".note-card__action-btn--del, span.control.data-del",w={published:"已发布",reviewing:"审核中",rejected:"未通过"},T=2000,L=1e4,g=1000,p=/^[0-9a-f]{24}$/i;function d(e){if(e&&typeof e==="object"&&"session"in e&&"data"in e)return e.data;return e}function O(e,o){if(typeof e!=="boolean")throw new r(`xiaohongshu/delete-note: malformed ${o} payload`);return e}function C(e,o){if(!e||typeof e!=="object"||Array.isArray(e))throw new r(`xiaohongshu/delete-note: malformed ${o} payload`);return e}function b(e,o){const t=C(e,o);if(typeof t.ok!=="boolean")throw new r(`xiaohongshu/delete-note: malformed ${o} payload`);return t}function M(e){const o=String(e||"").toLowerCase();return o==="xiaohongshu.com"||o.endsWith(".xiaohongshu.com")}function U(e){return e.hostname.toLowerCase()==="creator.xiaohongshu.com"&&e.pathname.replace(/\/+$/,"")==="/statistics/note-detail"}function y(e){const o=String(e??"").trim();if(!o)throw new l("xiaohongshu/delete-note: note-id cannot be empty");if(p.test(o))return o.toLowerCase();if(!/^https:\/\//i.test(o))throw new l("xiaohongshu/delete-note: note-id must be a 24-character Xiaohongshu note ID or an exact Xiaohongshu note URL");let t;try{t=new URL(o)}catch{throw new l("xiaohongshu/delete-note: invalid note URL")}if(t.protocol!=="https:"||t.username||t.password||t.port||!M(t.hostname))throw new l("xiaohongshu/delete-note: note URL must be an exact https://*.xiaohongshu.com URL");const i=t.searchParams.get("noteId")||t.searchParams.get("note_id");if(i&&p.test(i)&&U(t))return i.toLowerCase();const a=t.pathname.match(/^\/(?:explore|note|search_result|discovery\/item)\/([0-9a-f]{24})\/?$/i)||t.pathname.match(/^\/user\/profile\/[^/?#]+\/([0-9a-f]{24})\/?$/i);if(a)return a[1].toLowerCase();throw new l("xiaohongshu/delete-note: note URL must contain a 24-character note ID")}function _(e,o){return`
2
2
  (cfg => {
3
3
  const { targetId, shouldClick } = cfg;
4
4
  const isVisible = (el) => !!el && el.offsetParent !== null;
@@ -12,10 +12,10 @@ import{cli as I,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
12
12
  return false;
13
13
  }
14
14
  };
15
- const notes = Array.from(document.querySelectorAll('.note')).filter(isVisible);
15
+ const notes = Array.from(document.querySelectorAll(${JSON.stringify(m)})).filter(isVisible);
16
16
  for (const note of notes) {
17
17
  if (matchesNoteId(note.getAttribute('data-impression'))) {
18
- const del = note.querySelector('span.control.data-del');
18
+ const del = note.querySelector(${JSON.stringify(v)});
19
19
  if (!del || !isVisible(del)) {
20
20
  return { ok: false, kind: 'no_delete_action', visibleRows: notes.length };
21
21
  }
@@ -28,7 +28,7 @@ import{cli as I,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
28
28
  }
29
29
  return { ok: false, kind: 'not_found', visibleRows: notes.length };
30
30
  })(${JSON.stringify({targetId:e,shouldClick:o})})
31
- `}function y(e){return`
31
+ `}function x(e){return`
32
32
  (targetId => {
33
33
  const matchesNoteId = (impressionRaw) => {
34
34
  if (!impressionRaw) return false;
@@ -40,22 +40,10 @@ import{cli as I,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
40
40
  return false;
41
41
  }
42
42
  };
43
- const notes = Array.from(document.querySelectorAll('.note'));
43
+ const notes = Array.from(document.querySelectorAll(${JSON.stringify(m)}));
44
44
  return notes.some((n) => matchesNoteId(n.getAttribute('data-impression')));
45
45
  })(${JSON.stringify(e)})
46
- `}I({site:"xiaohongshu",name:"delete-note",access:"write",description:"删除小红书已发布笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:v.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"note-id",required:!0,positional:!0,help:"Note ID (e.g. 6a08ba0b000000000702a893 from xiaohongshu creator-notes / URL)"},{name:"execute",type:"boolean",default:!1,help:"Actually click delete + confirm. Default is dry-run target verification only."}],columns:["status","note_id","message"],func:async(e,o)=>{try{const t=p(o["note-id"]),r=o.execute===!0;await e.goto(A);await e.wait({time:h/1000});const i=U(l(await e.evaluate("() => location.href")),"current-url");if(typeof i==="string"&&/\/login(?:[/?#]|$)/i.test(new URL(i).pathname+new URL(i).search))throw new k("creator.xiaohongshu.com");const R=`
47
- () => {
48
- const isVisible = (el) => !!el && el.offsetParent !== null;
49
- for (const el of document.querySelectorAll('a, button, [role="tab"], div')) {
50
- const text = (el.innerText || el.textContent || '').trim();
51
- if (text === '已发布' && isVisible(el)) {
52
- el.click();
53
- return true;
54
- }
55
- }
56
- return false;
57
- }
58
- `;let c=!1;for(let a=0;a<30;a++){c=g(l(await e.evaluate(R)),"published-tab");if(c)break;await e.wait({time:0.5})}if(!c)throw new n("xiaohongshu/delete-note: 已发布 tab not found on note-manager; xhs creator UI may have changed.");await e.wait({time:h/1000});const u=b(l(await e.evaluate(x(t,r))),"locate-note");if(!u?.ok){if(u?.kind==="not_found")throw new E("xiaohongshu/delete-note",`Note ${t} not visible in the 已发布 tab. Verify the note belongs to the logged-in account and has cleared review (审核中 / 未通过 rows have no web delete entry).`);if(u?.kind==="no_delete_action")throw new n(`xiaohongshu/delete-note: note ${t} row found but no delete action visible; xhs creator UI may have changed.`);throw new n("xiaohongshu/delete-note: failed to locate note row")}if(!r)return[{status:"dry-run",note_id:t,message:"Target note row and delete action verified. Re-run with --execute to delete."}];await e.wait({time:L/1000});const d=b(l(await e.evaluate(`
46
+ `}async function D(e,o,t){if(t?.tabStatus===N.published)return"published";for(const i of["reviewing","rejected"])if((await h(e,{tab:i,limit:Number.MAX_SAFE_INTEGER,untilId:o})).notes.some((u)=>u.id===o))return i;return"unknown"}async function E(e,o){return O(d(await e.evaluate(x(o))),"verify-gone")}I({site:"xiaohongshu",name:"delete-note",access:"write",description:"删除小红书笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:R.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"note-id",required:!0,positional:!0,help:"Note ID (e.g. 6a08ba0b000000000702a893 from xiaohongshu creator-notes / URL)"},{name:"execute",type:"boolean",default:!1,help:"Actually click delete + confirm. Default is dry-run target verification only."}],columns:["status","note_id","message"],func:async(e,o)=>{try{const t=y(o["note-id"]),i=o.execute===!0,a=await h(e,{tab:"all",limit:Number.MAX_SAFE_INTEGER,untilId:t}),u=a.notes.find((n)=>n.id===t)??null,c=b(d(await e.evaluate(_(t,i))),"locate-note");if(!c?.ok){if(c?.kind==="not_found"){if(u)throw new r(`xiaohongshu/delete-note: 笔记 ${t} 在账号笔记列表里,但笔记管理页上没找到对应的行;页面结构可能已改版。`);if(a.complete)throw new k("xiaohongshu/delete-note",`笔记 ${t} 不存在:账号笔记列表已读全(${a.notes.length} 条,含审核中、未通过),里面没有这个 ID。ID 有误、笔记已被删除,或属于其他账号。`);throw new r(`xiaohongshu/delete-note: 只读到账号最新 ${a.notes.length} 条笔记,里面没有 ${t},更早的没有加载出来,无法判断这条笔记是否存在。未做任何删除。`)}if(c?.kind==="no_delete_action"){const n=await D(e,t,u);if(n==="reviewing"||n==="rejected")throw new r(`xiaohongshu/delete-note: 笔记 ${t} 存在,当前状态「${w[n]}」,网页端这一行暂无删除入口。未做任何删除;用 creator-notes --status ${n} 可查看该状态下的笔记。`);throw new r(`xiaohongshu/delete-note: note ${t} row found but no delete action visible(状态:${w[n]??"未知"}); xhs creator UI may have changed.`)}throw new r("xiaohongshu/delete-note: failed to locate note row")}if(!i)return[{status:"dry-run",note_id:t,message:"Target note row and delete action verified. Nothing was deleted. Re-run with --execute to delete."}];await e.wait({time:T/1000});const s=b(d(await e.evaluate(`
59
47
  () => {
60
48
  const isVisible = (el) => !!el && el.offsetParent !== null;
61
49
  const footer = Array.from(document.querySelectorAll('.d-modal-footer')).find(isVisible);
@@ -66,4 +54,4 @@ import{cli as I,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
66
54
  confirmBtn.click();
67
55
  return { ok: true };
68
56
  }
69
- `)),"confirm-modal");if(!d?.ok)throw new n(`xiaohongshu/delete-note: confirmation modal step failed (${d?.kind??"unknown"})`);const _=Math.ceil(T/m);let f=!0;for(let a=0;a<_;a++){await e.wait({time:m/1000});if(g(l(await e.evaluate(y(t))),"verify-gone")===!1){f=!1;break}}if(f)throw new n(`xiaohongshu/delete-note: note ${t} still visible after confirm click; deletion may not have committed.`);return[{status:"deleted",note_id:t,message:"Delete confirmed and note row disappeared."}]}catch(t){if(t instanceof S)throw t;throw new n(`xiaohongshu/delete-note failed: ${t?.message??String(t)}`)}}});export const __test__={normalizeNoteId:p,buildLocateAndMaybeDeleteScript:x,buildVerifyGoneScript:y};
57
+ `)),"confirm-modal");if(!s?.ok){if(s?.kind==="no_modal"&&await E(e,t)===!1)return[{status:"deleted",note_id:t,message:"Note row disappeared right after the delete action (no confirmation dialog was shown)."}];const n=Array.isArray(s?.labels)&&s.labels.length?`, buttons=${JSON.stringify(s.labels)}`:"";throw new r(`xiaohongshu/delete-note: confirmation modal step failed (${s?.kind??"unknown"}${n})。笔记仍在列表里,未删除。`)}const S=Math.ceil(L/g);let f=!0;for(let n=0;n<S;n++){await e.wait({time:g/1000});if(await E(e,t)===!1){f=!1;break}}if(f)throw new r(`xiaohongshu/delete-note: note ${t} still visible after confirm click; deletion may not have committed.`);return[{status:"deleted",note_id:t,message:"Delete confirmed and note row disappeared."}]}catch(t){if(t instanceof A)throw t;throw new r(`xiaohongshu/delete-note failed: ${t?.message??String(t)}`)}}});export const __test__={normalizeNoteId:y,buildLocateAndMaybeDeleteScript:_,buildVerifyGoneScript:x};
@@ -1,4 +1,4 @@
1
- import{CommandExecutionError as i}from"@jackwener/opencli/errors";import{cli as f,Strategy as u}from"@jackwener/opencli/registry";import{DRAFT_DB_NAME as y,STORE_NAME_MAP as m,draftNotFound as w,ensureDraftDbPage as p,findDraftEntry as x,normalizeDraftId as g,normalizeDraftRecord as D,normalizeDraftType as h,readDraftEntries as b,unwrapBrowserResult as q}from"./draft-utils.js";function S(e,t){return`
1
+ import{CommandExecutionError as i}from"@jackwener/opencli/errors";import{cli as f,Strategy as u}from"@jackwener/opencli/registry";import{DRAFT_DB_NAME as y,STORE_NAME_MAP as m,draftNotFound as w,ensureDraftDbPage as p,findDraftEntry as g,normalizeDraftId as x,normalizeDraftRecord as D,normalizeDraftType as h,readDraftEntries as b,unwrapBrowserResult as q}from"./draft-utils.js";function S(e,t){return`
2
2
  (async () => {
3
3
  const openDb = () => new Promise((resolve, reject) => {
4
4
  const req = indexedDB.open(${JSON.stringify(y)});
@@ -34,4 +34,4 @@ import{CommandExecutionError as i}from"@jackwener/opencli/errors";import{cli as
34
34
  return { ok: false, error: String(error && error.message || error) };
35
35
  }
36
36
  })()
37
- `}f({site:"xiaohongshu",name:"draft-delete",access:"write",description:"删除一条小红书本地草稿",domain:"creator.xiaohongshu.com",strategy:u.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"id",positional:!0,required:!0,help:"Draft id returned by `ppcli xiaohongshu drafts`"},{name:"type",default:"image",help:"Draft type: image, video, article, audio"},{name:"execute",type:"bool",default:!1,help:"Actually delete the local draft. Default is dry-run verification only."}],columns:["status","id","type","message"],func:async(e,t)=>{const a=g(t.id),r=h(t.type),d=t.execute===!0;await p(e);const c=await b(e,r),o=x(c,a);if(!o)throw w(a,r,"xiaohongshu/draft-delete");const n=D(o.row,o.key,r,1);if(!d)return[{status:"dry-run",id:n.id,type:r,message:"Draft exists. Re-run with --execute to delete."}];const l=m[r],s=q(await e.evaluate(S(l,o.key)));if(!s?.ok)throw new i(s?.error||`Failed to delete draft ${n.id}`);if(!s.deleted)throw new i(`Draft ${n.id} still exists after delete`);return[{status:"deleted",id:n.id,type:r,message:"Draft deleted."}]}});
37
+ `}f({site:"xiaohongshu",name:"draft-delete",access:"write",description:"删除一条小红书本地草稿",domain:"creator.xiaohongshu.com",strategy:u.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"id",positional:!0,required:!0,help:"Draft id returned by `ppcli xiaohongshu drafts`"},{name:"type",default:"image",help:"Draft type: image, video, article, audio"},{name:"execute",type:"bool",default:!1,help:"Actually delete the local draft. Default is dry-run verification only."}],columns:["status","dry_run","id","type","message"],func:async(e,t)=>{const a=x(t.id),r=h(t.type),d=t.execute===!0;await p(e);const c=await b(e,r),o=g(c,a);if(!o)throw w(a,r,"xiaohongshu/draft-delete");const n=D(o.row,o.key,r,1);if(!d)return[{status:"dry-run",dry_run:!0,id:n.id,type:r,message:"演练成功:草稿存在,本次没有删除任何东西(命令默认只演练)。确认要删,加 --execute 重跑。"}];const l=m[r],s=q(await e.evaluate(S(l,o.key)));if(!s?.ok)throw new i(s?.error||`Failed to delete draft ${n.id}`);if(!s.deleted)throw new i(`Draft ${n.id} still exists after delete`);return[{status:"deleted",dry_run:!1,id:n.id,type:r,message:"Draft deleted."}]}});
@@ -1,13 +1,13 @@
1
- import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/topics.js";import{XHS_PUBLISH_ARGS as pt,validateXhsSettings as bt,setXhsPublishSettings as wt}from"./_shared/publish-settings.js";import*as L from"node:fs";import*as I from"node:path";import{CommandExecutionError as u,ArgumentError as p}from"@jackwener/opencli/errors";import{cli as gt,Strategy as xt}from"@jackwener/opencli/registry";import{assertLiteralContent as _t}from"../_shared/content-guard.js";import{setInputFiles as yt}from"../_shared/file-inject.js";import{humanType as j,humanWait as N,humanMoveTo as vt,elementViewportCenter as St}from"./human.js";const Tt="https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image",V=18,z=20,X=3000,H="|||",S="基础",G=[["基础","默认兜底,万能"],["边框","金句/要点卡"],["备忘","提醒/随手记"],["清新","日常/清单贴士"],["涂写","随笔/碎碎念"],["便签","笔记/提醒"],["光影","情绪/文艺"],["涂鸦","趣味/童话"],["简约","干货/观点"],["手写","日记/情感"],["插图","生活方式/轻松话题"],["美漫","活力/趣味/故事感"],["弥散","弥散光氛围"],["柔和","柔和/温柔金句"],["印刷","印刷海报/排版"],["科技","科技/产品"],["贺卡","节日祝福"],["札记","艺术/水彩氛围"],["书摘","书摘/引用"],["手帐","手帐拼贴"],["几何","醒目/有力主张"]],ne=G.map(([t])=>t),O="文字配图",Y="再写一张",K="生成图片",W="下一步",B=".tiptap.ProseMirror",kt=["_onPublish","onPublish","_onSubmit","_handlePublish"],At=["_onSave","_onSaveDraft","_onDraft"],U=['[contenteditable="true"][placeholder*="标题"]','[contenteditable="true"][placeholder*="赞"]','input[placeholder*="标题"]','input[placeholder*="title" i]','[contenteditable="true"][class*="title"]','input[maxlength="20"]','input[class*="title"]',".title-input input",".note-title input","input[maxlength]"],Q=['[contenteditable="true"][class*="content"]','[contenteditable="true"][class*="editor"]','[contenteditable="true"][placeholder*="描述"]','[contenteditable="true"][placeholder*="正文"]','[contenteditable="true"][placeholder*="内容"]','.note-content [contenteditable="true"]','.editor-content [contenteditable="true"]','[contenteditable="true"]:not([placeholder*="标题"]):not([placeholder*="赞"]):not([placeholder*="title" i])'],Et={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp"};function g(t){if(t&&typeof t==="object"&&typeof t.session==="string"&&Object.prototype.hasOwnProperty.call(t,"data"))return t.data;return t}function $t(t){return t.map((e)=>{const r=I.resolve(e);if(!L.existsSync(r))throw new p(`Image file not found: ${r}`);const n=I.extname(r).toLowerCase();if(!Et[n])throw new p(`Unsupported image format "${n}". Supported: jpg, png, gif, webp`);return r})}const Z='input[type="file"][accept*="image"],input[type="file"][accept*=".jpg"],input[type="file"][accept*=".jpeg"],input[type="file"][accept*=".png"],input[type="file"][accept*=".gif"],input[type="file"][accept*=".webp"]';async function Ct(t,e=15000){const r=500,n=Math.max(1,Math.ceil(e/r));for(let o=0;o<n;o++){if(await t.evaluate(`
1
+ import{TOPIC_ID_ARG as mt,parseTopics as pt,attachTopics as bt}from"./_shared/topics.js";import{XHS_PUBLISH_ARGS as wt,validateXhsSettings as gt,setXhsPublishSettings as _t}from"./_shared/publish-settings.js";import*as L from"node:fs";import*as N from"node:path";import{CommandExecutionError as u,ArgumentError as b}from"@jackwener/opencli/errors";import{cli as xt,Strategy as yt}from"@jackwener/opencli/registry";import{assertLiteralContent as vt}from"../_shared/content-guard.js";import{setInputFiles as St}from"../_shared/file-inject.js";import{humanType as X,humanWait as I,humanMoveTo as Tt,elementViewportCenter as kt}from"./human.js";import{loadNoteManagerNotes as $t}from"./_shared/note-manager.js";const Et="https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image",B=18,H=20,G=3000,Y="|||",S="基础",K=[["基础","默认兜底,万能"],["边框","金句/要点卡"],["备忘","提醒/随手记"],["清新","日常/清单贴士"],["涂写","随笔/碎碎念"],["便签","笔记/提醒"],["光影","情绪/文艺"],["涂鸦","趣味/童话"],["简约","干货/观点"],["手写","日记/情感"],["插图","生活方式/轻松话题"],["美漫","活力/趣味/故事感"],["弥散","弥散光氛围"],["柔和","柔和/温柔金句"],["印刷","印刷海报/排版"],["科技","科技/产品"],["贺卡","节日祝福"],["札记","艺术/水彩氛围"],["书摘","书摘/引用"],["手帐","手帐拼贴"],["几何","醒目/有力主张"]],ae=K.map(([t])=>t),U="文字配图",W="再写一张",Q="生成图片",Z="下一步",F=".tiptap.ProseMirror",At=["_onPublish","onPublish","_onSubmit","_handlePublish"],Ct=["_onSave","_onSaveDraft","_onDraft"],J=['[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]"],tt=['[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])'],Mt={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp"};function _(t){if(t&&typeof t==="object"&&typeof t.session==="string"&&Object.prototype.hasOwnProperty.call(t,"data"))return t.data;return t}function Nt(t){return t.map((e)=>{const r=N.resolve(e);if(!L.existsSync(r))throw new b(`Image file not found: ${r}`);const n=N.extname(r).toLowerCase();if(!Mt[n])throw new b(`Unsupported image format "${n}". Supported: jpg, png, gif, webp`);return r})}const et='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 It(t,e=15000){const r=500,n=Math.max(1,Math.ceil(e/r));for(let o=0;o<n;o++){if(await t.evaluate(`
2
2
  (() => {
3
- const sels = ${JSON.stringify(Z)};
3
+ const sels = ${JSON.stringify(et)};
4
4
  return !!document.querySelector(sels);
5
5
  })()
6
- `))return!0;if(o<n-1)await t.wait({time:r/1000})}return!1}async function tt(t,e){if(!await Ct(t))return{ok:!1,count:0,error:"No file input found on page (waited 15s; publish surface did not finish rendering)"};try{return{ok:!0,count:(await yt(t,Z,e)).count??e.length}}catch(n){return{ok:!1,count:0,error:n instanceof Error?n.message:String(n)}}}async function et(t,e=30000){const r=2000,n=Math.ceil(e/r);for(let o=0;o<n;o++){if(!await t.evaluate(`
6
+ `))return!0;if(o<n-1)await t.wait({time:r/1000})}return!1}async function nt(t,e){if(!await It(t))return{ok:!1,count:0,error:"No file input found on page (waited 15s; publish surface did not finish rendering)"};try{return{ok:!0,count:(await St(t,et,e)).count??e.length}}catch(n){return{ok:!1,count:0,error:n instanceof Error?n.message:String(n)}}}async function rt(t,e=30000){const r=2000,n=Math.ceil(e/r);for(let o=0;o<n;o++){if(!await t.evaluate(`
7
7
  () => !!document.querySelector(
8
8
  '[class*="upload"][class*="progress"], [class*="uploading"], [class*="loading"][class*="image"]'
9
9
  )
10
- `))return;await t.wait({time:r/1000})}}export function describeTextMismatch(t,e){const r=String(t??""),n=String(e??""),o=(f)=>f.split(/\n\s*\n/).filter((y)=>y.trim()).length;let i=0;while(i<r.length&&i<n.length&&r[i]===n[i])i++;const a=(f)=>JSON.stringify(f.slice(Math.max(0,i-15),i+25)),d=`内容校验不一致:期望 ${r.length} 字符 / ${o(r)} 段,实际 ${n.length} 字符 / ${o(n)} 段。`,s=i>=r.length&&i>=n.length?"":` 首个差异在第 ${i+1} 个字符:期望 ${a(r)},实际 ${a(n)}。`,c=o(r)>1&&o(n)<=1?" 段落数塌成 1 段,通常是编辑器把换行吃掉了。":"";return d+s+c}async function Mt(t,e,r){const n=()=>t.evaluate(`
10
+ `))return;await t.wait({time:r/1000})}}export function describeTextMismatch(t,e){const r=String(t??""),n=String(e??""),o=(m)=>m.split(/\n\s*\n/).filter((y)=>y.trim()).length;let i=0;while(i<r.length&&i<n.length&&r[i]===n[i])i++;const a=(m)=>JSON.stringify(m.slice(Math.max(0,i-15),i+25)),c=`内容校验不一致:期望 ${r.length} 字符 / ${o(r)} 段,实际 ${n.length} 字符 / ${o(n)} 段。`,s=i>=r.length&&i>=n.length?"":` 首个差异在第 ${i+1} 个字符:期望 ${a(r)},实际 ${a(n)}。`,d=o(r)>1&&o(n)<=1?" 段落数塌成 1 段,通常是编辑器把换行吃掉了。":"";return c+s+d}async function Pt(t,e,r){const n=()=>t.evaluate(`
11
11
  ((sel) => {
12
12
  const el = Array.from(document.querySelectorAll(sel)).find(n => n && n.offsetParent !== null);
13
13
  if (!el) return -1;
@@ -23,7 +23,7 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
23
23
  return document.execCommand('insertParagraph', false);
24
24
  })(${JSON.stringify(e)})
25
25
  `);await t.wait({time:0.2});a=await n();if(a>o)return;throw new u(`正文第 ${r} 处换行没能插入段落分隔(编辑器块数仍为 ${o},`+`按键通道=${i},execCommand 兜底也没生效)。继续打字会把整篇正文塞进一段,因此中止——`+`没换行的笔记发出去只能删了重发。
26
- `+"请勿直接重试同一条命令,先看编辑器当前状态:最可能是有话题/表情联想浮层开着,"+"此时 Enter 被浮层当成「选中候选」吃掉。正文里如果带 # 号尤其容易触发。")}async function nt(t,e,r,n){const o=await t.evaluate(`
26
+ `+"请勿直接重试同一条命令,先看编辑器当前状态:最可能是有话题/表情联想浮层开着,"+"此时 Enter 被浮层当成「选中候选」吃掉。正文里如果带 # 号尤其容易触发。")}async function ot(t,e,r,n){const o=await t.evaluate(`
27
27
  (function(selectors) {
28
28
  const __opencli_xhs_fill_phase = "locate";
29
29
  for (const sel of selectors) {
@@ -117,7 +117,7 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
117
117
  return { ok: true };
118
118
  })(${JSON.stringify(o.sel)})
119
119
  `))?.ok){await t.screenshot({path:`/tmp/xhs_publish_${n}_debug.png`});throw Error(`Could not prepare ${n} input. Debug screenshot: /tmp/xhs_publish_${n}_debug.png`)}await t.wait({time:0.35});try{const s=String(r).split(`
120
- `);for(let c=0;c<s.length;c++){if(c>0)await Mt(t,o.sel,c);if(s[c])await j(t,s[c])}a=await t.evaluate(`
120
+ `);for(let d=0;d<s.length;d++){if(d>0)await Pt(t,o.sel,d);if(s[d])await X(t,s[d])}a=await t.evaluate(`
121
121
  ((selector, expectedText) => {
122
122
  const __opencli_xhs_fill_phase = "verify";
123
123
  const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
@@ -141,7 +141,7 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
141
141
  return { ok: actual === normalize(expectedText), actual };
142
142
  })(${JSON.stringify(o.sel)}, ${JSON.stringify(r)})
143
143
  `)}catch(s){if(s instanceof u)throw s;if(String(r).includes(`
144
- `))throw new u(`写入${n}失败:${String(s?.message||s)}。`+"正文含换行,不能回退到单段写入方式(那会把多段正文塞进一段),因此中止。"+"⚠️ 编辑器可能已自动保存草稿:请勿直接重试,先用 `ppcli xiaohongshu drafts` 查看。");a=await i()}}else a=await i();if(!a?.ok){await t.screenshot({path:`/tmp/xhs_publish_${n}_debug.png`});const d=typeof a?.actual==="string"?a.actual:"";throw Error(`Failed to set ${n}. ${describeTextMismatch(r,d)}`+" ⚠️ 编辑器可能已自动保存草稿:请勿直接重试,先用 `ppcli xiaohongshu drafts` 查看,"+"已存在则改为在该草稿上继续操作或先删除,避免建出重复草稿。"+` Debug screenshot: /tmp/xhs_publish_${n}_debug.png`)}}async function It(t){const e=await t.evaluate(`
144
+ `))throw new u(`写入${n}失败:${String(s?.message||s)}。`+"正文含换行,不能回退到单段写入方式(那会把多段正文塞进一段),因此中止。"+"⚠️ 编辑器可能已自动保存草稿:请勿直接重试,先用 `ppcli xiaohongshu drafts` 查看。");a=await i()}}else a=await i();if(!a?.ok){await t.screenshot({path:`/tmp/xhs_publish_${n}_debug.png`});const c=typeof a?.actual==="string"?a.actual:"";throw Error(`Failed to set ${n}. ${describeTextMismatch(r,c)}`+" ⚠️ 编辑器可能已自动保存草稿:请勿直接重试,先用 `ppcli xiaohongshu drafts` 查看,"+"已存在则改为在该草稿上继续操作或先删除,避免建出重复草稿。"+` Debug screenshot: /tmp/xhs_publish_${n}_debug.png`)}}async function Rt(t){const e=await t.evaluate(`
145
145
  () => {
146
146
  const isVisible = (el) => {
147
147
  if (!el || el.offsetParent === null) return false;
@@ -190,7 +190,7 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
190
190
  }
191
191
  return { ok: false, visibleTexts };
192
192
  }
193
- `);if(e?.ok)await t.wait({time:1});return e}async function T(t,e,r=7000){const n=500,o=Math.max(1,Math.ceil(r/n));let i={ok:!1};for(let a=0;a<o;a++){i=g(await t.evaluate(`
193
+ `);if(e?.ok)await t.wait({time:1});return e}async function T(t,e,r=7000){const n=500,o=Math.max(1,Math.ceil(r/n));let i={ok:!1};for(let a=0;a<o;a++){i=_(await t.evaluate(`
194
194
  ((cfg) => {
195
195
  const __opencli_xhs_click_label = { wantLabel: ${JSON.stringify(e)} };
196
196
  const wantLabel = ${JSON.stringify(e)};
@@ -225,10 +225,10 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
225
225
  }
226
226
  return { ok: false };
227
227
  })()
228
- `));if(i?.ok)return i;await t.wait({time:n/1000})}return i}async function Nt(t){const e=await t.evaluate(`
228
+ `));if(i?.ok)return i;await t.wait({time:n/1000})}return i}async function Dt(t){const e=await t.evaluate(`
229
229
  (() => {
230
230
  const __opencli_xhs_focus_card = true;
231
- const sel = ${JSON.stringify(B)};
231
+ const sel = ${JSON.stringify(F)};
232
232
  const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
233
233
  // Prefer the editor inside the active swiper slide if present.
234
234
  const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
@@ -242,34 +242,34 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
242
242
  selection?.addRange(range);
243
243
  return { ok: true };
244
244
  })()
245
- `);return g(e)}async function Rt(t){const e=await t.evaluate(`
245
+ `);return _(e)}async function qt(t){const e=await t.evaluate(`
246
246
  (() => {
247
247
  const __opencli_xhs_card_text = true;
248
- const sel = ${JSON.stringify(B)};
248
+ const sel = ${JSON.stringify(F)};
249
249
  const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
250
250
  const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
251
251
  const text = active ? (active.innerText || active.textContent || '').trim() : '';
252
252
  return { ok: !!text, text };
253
253
  })()
254
- `);return g(e)}async function rt(t){const e=await t.evaluate(`
254
+ `);return _(e)}async function it(t){const e=await t.evaluate(`
255
255
  (() => {
256
256
  const __opencli_xhs_card_count = true;
257
- const sel = ${JSON.stringify(B)};
257
+ const sel = ${JSON.stringify(F)};
258
258
  const editors = Array.from(document.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
259
259
  const active = editors.find((el) => el.closest('.swiper-slide-active')) || editors[editors.length - 1];
260
260
  const activeText = active ? (active.innerText || active.textContent || '').trim() : '';
261
261
  return { ok: true, count: editors.length, activeEmpty: !activeText };
262
262
  })()
263
- `);return g(e)}async function Pt(t,e=8000){const r=300,n=Math.ceil(e/r);for(let o=0;o<n;o++){if((await rt(t))?.count>=1)return!0;await t.wait({time:r/1000})}return!1}async function Dt(t,e,r=6000){const n=300,o=Math.ceil(r/n);for(let i=0;i<o;i++){const a=await rt(t);if(a?.count>=e&&a?.activeEmpty)return!0;await t.wait({time:n/1000})}return!1}async function qt(t,e,r=4){for(let n=0;n<r;n++){if(!(await T(t,Y))?.ok)return!1;if(await Dt(t,e,2500))return!0}return!1}async function Lt(t){const e=await t.evaluate(`
263
+ `);return _(e)}async function Ot(t,e=8000){const r=300,n=Math.ceil(e/r);for(let o=0;o<n;o++){if((await it(t))?.count>=1)return!0;await t.wait({time:r/1000})}return!1}async function Lt(t,e,r=6000){const n=300,o=Math.ceil(r/n);for(let i=0;i<o;i++){const a=await it(t);if(a?.count>=e&&a?.activeEmpty)return!0;await t.wait({time:n/1000})}return!1}async function Bt(t,e,r=4){for(let n=0;n<r;n++){if(!(await T(t,W))?.ok)return!1;if(await Lt(t,e,2500))return!0}return!1}async function Ut(t){const e=await t.evaluate(`
264
264
  (() => {
265
265
  const __opencli_xhs_preview_ready = true;
266
266
  const ready = Array.from(document.querySelectorAll('button'))
267
267
  .some((b) => b.offsetParent !== null && (b.innerText || '').replace(/\\s+/g, '') === '下一步');
268
268
  return { ok: ready };
269
269
  })()
270
- `);return g(e)}async function Ot(t,e=6){const r=400,n=Math.ceil(3000/r);for(let o=0;o<e;o++){if(!(await T(t,K))?.ok&&o===0)return!1;for(let a=0;a<n;a++){if((await Lt(t))?.ok)return!0;await t.wait({time:r/1000})}}return!1}async function Bt(t,e,r){e=String(e).replace(/\\n/g,`
271
- `);if(!(await Nt(t))?.ok)throw new u(`文字配图: could not focus card editor #${r+1}`);if(typeof t.insertText==="function"){const i=e.split(`
272
- `);for(let a=0;a<i.length;a++){if(a>0&&typeof t.pressKey==="function")await t.pressKey("Enter");if(i[a])await j(t,i[a])}}else await t.evaluate(`(t => document.execCommand('insertText', false, t))(${JSON.stringify(e)})`);await t.wait({time:0.4});if(!(await Rt(t))?.ok)throw new u(`文字配图: card editor #${r+1} is empty after typing`)}async function Ut(t,e){if(!e||e===S)return S;const r=g(await t.evaluate(`
270
+ `);return _(e)}async function Ft(t,e=6){const r=400,n=Math.ceil(3000/r);for(let o=0;o<e;o++){if(!(await T(t,Q))?.ok&&o===0)return!1;for(let a=0;a<n;a++){if((await Ut(t))?.ok)return!0;await t.wait({time:r/1000})}}return!1}async function Jt(t,e,r){e=String(e).replace(/\\n/g,`
271
+ `);if(!(await Dt(t))?.ok)throw new u(`文字配图: could not focus card editor #${r+1}`);if(typeof t.insertText==="function"){const i=e.split(`
272
+ `);for(let a=0;a<i.length;a++){if(a>0&&typeof t.pressKey==="function")await t.pressKey("Enter");if(i[a])await X(t,i[a])}}else await t.evaluate(`(t => document.execCommand('insertText', false, t))(${JSON.stringify(e)})`);await t.wait({time:0.4});if(!(await qt(t))?.ok)throw new u(`文字配图: card editor #${r+1} is empty after typing`)}async function jt(t,e){if(!e||e===S)return S;const r=_(await t.evaluate(`
273
273
  (async () => {
274
274
  const __opencli_xhs_card_styles = true;
275
275
  const want = ${JSON.stringify(e)};
@@ -298,7 +298,7 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
298
298
  }
299
299
  return { ok: seen.length > 0, styles: seen, found: false };
300
300
  })()
301
- `));if(!r?.found)throw new u(`文字配图: requested style "${e}" is not available for this content (options: ${(r?.styles||[]).join(" / ")||"none"}). Choose an available style or omit --card-style to use ${S}.`);if(!(await T(t,e))?.ok)throw new u(`文字配图: could not click requested style "${e}".`);await t.wait({time:0.6});return e}async function ot(t){const e=await t.evaluate(`
301
+ `));if(!r?.found)throw new u(`文字配图: requested style "${e}" is not available for this content (options: ${(r?.styles||[]).join(" / ")||"none"}). Choose an available style or omit --card-style to use ${S}.`);if(!(await T(t,e))?.ok)throw new u(`文字配图: could not click requested style "${e}".`);await t.wait({time:0.6});return e}async function at(t){const e=await t.evaluate(`
302
302
  (() => {
303
303
  const __opencli_xhs_composer_media_count = true;
304
304
  const visibleBox = (el) => {
@@ -311,7 +311,7 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
311
311
  const r = el.getBoundingClientRect();
312
312
  return r.width >= 48 && r.height >= 48;
313
313
  };
314
- const titleSelectors = ${JSON.stringify(U)};
314
+ const titleSelectors = ${JSON.stringify(J)};
315
315
  const titleEl = titleSelectors
316
316
  .map((sel) => Array.from(document.querySelectorAll(sel)))
317
317
  .flat()
@@ -330,7 +330,7 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
330
330
  }
331
331
  return { ok: true, count };
332
332
  })()
333
- `);return g(e)}async function it(t,e,r){const n=await ot(t);if(!n||typeof n.count!=="number")throw new u(`${r}: could not verify current composer media count`);if(n.count<e){await t.screenshot({path:"/tmp/xhs_publish_media_debug.png"});throw new u(`${r}: expected at least ${e} visible media item(s), got ${n.count}. Debug screenshot: /tmp/xhs_publish_media_debug.png`)}}async function Ft(t,e,r){if(!(await T(t,O))?.ok){await t.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new u(`文字配图: could not click "${O}" entry. Debug: /tmp/xhs_publish_textimage_debug.png`)}if(!await Pt(t)){await t.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new u(`文字配图: 写文字 card editor did not appear after clicking "${O}". Debug: /tmp/xhs_publish_textimage_debug.png`)}for(let d=0;d<e.length;d++){if(d>0){if(!await qt(t,d+1)){await t.screenshot({path:"/tmp/xhs_publish_addcard_debug.png"});throw new u(`文字配图: new card editor #${d+1} did not render after "${Y}". Debug: /tmp/xhs_publish_addcard_debug.png`)}}await Bt(t,e[d],d)}if(!await Ot(t)){await t.screenshot({path:"/tmp/xhs_publish_generate_debug.png"});throw new u(`文字配图: "${K}" did not advance to the 预览图片 step. `+"Debug: /tmp/xhs_publish_generate_debug.png")}const i=await Ut(t,r);if(!(await T(t,W))?.ok){await t.screenshot({path:"/tmp/xhs_publish_next_debug.png"});throw new u(`文字配图: could not click "${W}". Debug: /tmp/xhs_publish_next_debug.png`)}await t.wait({time:2});return i}async function at(t){return t.evaluate(`
333
+ `);return _(e)}async function st(t,e,r){const n=await at(t);if(!n||typeof n.count!=="number")throw new u(`${r}: could not verify current composer media count`);if(n.count<e){await t.screenshot({path:"/tmp/xhs_publish_media_debug.png"});throw new u(`${r}: expected at least ${e} visible media item(s), got ${n.count}. Debug screenshot: /tmp/xhs_publish_media_debug.png`)}}async function Vt(t,e,r){if(!(await T(t,U))?.ok){await t.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new u(`文字配图: could not click "${U}" entry. Debug: /tmp/xhs_publish_textimage_debug.png`)}if(!await Ot(t)){await t.screenshot({path:"/tmp/xhs_publish_textimage_debug.png"});throw new u(`文字配图: 写文字 card editor did not appear after clicking "${U}". Debug: /tmp/xhs_publish_textimage_debug.png`)}for(let c=0;c<e.length;c++){if(c>0){if(!await Bt(t,c+1)){await t.screenshot({path:"/tmp/xhs_publish_addcard_debug.png"});throw new u(`文字配图: new card editor #${c+1} did not render after "${W}". Debug: /tmp/xhs_publish_addcard_debug.png`)}}await Jt(t,e[c],c)}if(!await Ft(t)){await t.screenshot({path:"/tmp/xhs_publish_generate_debug.png"});throw new u(`文字配图: "${Q}" did not advance to the 预览图片 step. `+"Debug: /tmp/xhs_publish_generate_debug.png")}const i=await jt(t,r);if(!(await T(t,Z))?.ok){await t.screenshot({path:"/tmp/xhs_publish_next_debug.png"});throw new u(`文字配图: could not click "${Z}". Debug: /tmp/xhs_publish_next_debug.png`)}await t.wait({time:2});return i}async function lt(t){return t.evaluate(`
334
334
  () => {
335
335
  const text = (document.body?.innerText || '').replace(/s+/g, ' ').trim();
336
336
  const hasTitleInput = !!Array.from(document.querySelectorAll('input, textarea')).find((el) => {
@@ -360,22 +360,22 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
360
360
  const state = hasTitleInput ? 'editor_ready' : hasImageInput || !hasVideoSurface ? 'image_surface' : 'video_surface';
361
361
  return { state, hasTitleInput, hasImageInput, hasVideoSurface };
362
362
  }
363
- `)}async function Jt(t,e=5000){const r=500,n=Math.max(1,Math.ceil(e/r));let o=await at(t);for(let i=0;i<n;i++){if(o.state!=="video_surface")return o;if(i<n-1){await t.wait({time:r/1000});o=await at(t)}}return o}async function jt(t,e=1e4){const r=1000,n=Math.ceil(e/r);for(let o=0;o<n;o++){if(await t.evaluate(`
363
+ `)}async function zt(t,e=5000){const r=500,n=Math.max(1,Math.ceil(e/r));let o=await lt(t);for(let i=0;i<n;i++){if(o.state!=="video_surface")return o;if(i<n-1){await t.wait({time:r/1000});o=await lt(t)}}return o}async function Xt(t,e=1e4){const r=1000,n=Math.ceil(e/r);for(let o=0;o<n;o++){if(await t.evaluate(`
364
364
  (() => {
365
- const sels = ${JSON.stringify(U)};
365
+ const sels = ${JSON.stringify(J)};
366
366
  for (const sel of sels) {
367
367
  const el = document.querySelector(sel);
368
368
  if (el && el.offsetParent !== null) return true;
369
369
  }
370
370
  return false;
371
- })()`))return!0;if(o<n-1)await t.wait({time:r/1000})}return!1}async function Vt(t){const e=g(await t.evaluate(`
371
+ })()`))return!0;if(o<n-1)await t.wait({time:r/1000})}return!1}async function Ht(t){const e=_(await t.evaluate(`
372
372
  (() => {
373
373
  const __opencli_xhs_restore_srcs = true;
374
374
  return Array.from(document.querySelectorAll('.img-container img'))
375
375
  .map((img) => img.currentSrc || img.src || '')
376
376
  .filter(Boolean);
377
377
  })()
378
- `));return Array.isArray(e)?e:[]}async function st(t,e){if(!Array.isArray(e)||e.length===0)return 0;const r=g(await t.evaluate(`
378
+ `));return Array.isArray(e)?e:[]}async function ct(t,e){if(!Array.isArray(e)||e.length===0)return 0;const r=_(await t.evaluate(`
379
379
  (async (wanted) => {
380
380
  const __opencli_xhs_delete_by_src = true;
381
381
  const want = new Set(wanted);
@@ -406,16 +406,7 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
406
406
  }
407
407
  return removed;
408
408
  })(${JSON.stringify(e)})
409
- `));return Number(r)||0}function zt(t){const e=String(t&&(t.url||t.key)||""),r=String(t&&t.method||"").toUpperCase(),n=Number(t&&t.status||0);if(r!=="PUT")return null;if(!/ros-upload[^/]*\/spectrum\//.test(e))return null;if(n<200||n>=300)return null;const o=e.match(/\/spectrum\/([^?#]+)/);return o?o[1]:e}function lt(t){return Math.min(45,6+t*4)}async function ct(t,e,r,n=90000){if(e<=0)return{ok:!0,completed:0};if(!r||typeof t.readNetworkCapture!=="function"){await t.wait({time:lt(e)});return{ok:!0,completed:null,viaFallback:!0}}const o=1500,a=Math.ceil(9000/o),d=Math.ceil(n/o),s=new Set;for(let c=0;c<d;c++){const f=await t.readNetworkCapture().catch(()=>[]);for(const y of Array.isArray(f)?f:[]){const b=zt(y);if(b)s.add(b)}if(s.size>=e)return{ok:!0,completed:s.size};if(c>=a&&s.size===0){await t.wait({time:lt(e)});return{ok:!0,completed:null,viaFallback:!0}}await t.wait({time:o/1000})}return{ok:!1,completed:s.size}}async function Xt(t,e,r){try{await t.goto("https://creator.xiaohongshu.com/new/note-manager")}catch{return null}const n=e.trim();for(let o=0;o<r;o++){await t.wait({time:1.5});let i;try{i=g(await t.evaluate(`(() => {
410
- const noteIdRe = /"noteId":"([0-9a-f]{24})"/;
411
- return Array.from(document.querySelectorAll('div.note[data-impression], div.note')).slice(0, 3).map((card) => {
412
- const impression = card.getAttribute('data-impression') || '';
413
- const id = impression.match(noteIdRe)?.[1] || '';
414
- const title = (card.querySelector('.title, .raw')?.innerText || '').trim();
415
- const dateText = (card.querySelector('.time_status, .time')?.innerText || '').trim();
416
- return { id, title, dateText };
417
- }).filter((entry) => entry.title);
418
- })()`))}catch{return null}if(Array.isArray(i)){const a=i.find((d)=>{const s=String(d.title||"").trim();if(!(s===n||s.startsWith(n)||n.startsWith(s)))return!1;const c=String(d.dateText||"").match(/(\d{4})年(\d{2})月(\d{2})日 (\d{2}):(\d{2})/);if(!c)return!0;const f=Date.UTC(Number(c[1]),Number(c[2])-1,Number(c[3]),Number(c[4]),Number(c[5]))-28800000;return Math.abs(Date.now()-f)<=93600000});if(a)return a}}return null}gt({site:"xiaohongshu",name:"publish",access:"write",description:"小红书发布图文笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:xt.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"content",required:!1,positional:!0,help:"笔记正文(字面文本,不会展开 @文件 引用;长正文建议用 --file)"},{name:"file",required:!1,help:"从本机文件读取笔记正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"笔记标题 (最多20字)"},{name:"images",required:!1,help:"图片路径,逗号分隔,最多9张 (jpg/png/gif/webp)"},{name:"card-text",required:!1,help:`文字配图卡片文字,多张卡片用 ${H} 分隔,卡内换行用 \\n`},{name:"card-style",required:!1,help:`文字配图卡片样式,运行时按页面实际选项匹配;找不到会失败。省略时使用${S}。可选: ${G.map(([t,e])=>`${t}(${e})`).join(" ")}`},{name:"topics",required:!1,help:"话题名称,逗号分隔,最多10个;先用 topics 查询。无法准确选中时停止提交"},ft,{name:"draft",type:"bool",default:!1,help:"保存为草稿,不直接发布"},{name:"timeout",type:"int",default:300,help:"整段发布流程的等待预算(秒);超时后先用 creator-notes 回查,勿直接重复发布"},{name:"ai-generated",type:"boolean",help:"声明内容由 AI 生成(发布和草稿均支持;设置失败时停止提交)"},...pt],columns:["status","detail","url"],func:async(t,e)=>{bt(e);if(!t)throw Error("Browser page required");const r=String(e.title??"").trim(),n=e.file?String(e.file).trim():"";let o=String(e.content??"").trim();if(n){if(o)throw new p("正文位置参数和 --file 只能二选一,不能同时给");const l=I.resolve(n);if(!L.existsSync(l))throw new p(`--file 指向的文件不存在: ${l}`);o=L.readFileSync(l,"utf-8").trim();if(!o)throw new p(`--file 指向的文件内容为空: ${l}`)}const i=e.images?String(e.images).split(",").map((l)=>l.trim()).filter(Boolean):[],a=ht(e),d=a.map((l)=>l.name),s=Boolean(e.draft),c=e["card-text"]?String(e["card-text"]):"",f=c?c.split(H).map((l)=>l.trim()).filter(Boolean):[],y=e["card-style"]?String(e["card-style"]).trim():"",b=f.length>0;if(!r)throw new p("--title is required");if(r.length>z)throw new p(`Title is ${r.length} chars — must be ≤ ${z}`);if(!o)throw new p("缺少笔记正文:给位置参数 <content>(字面文本),或用 --file <本机文件路径>");_t(o,{fileFlag:"--file"});if(!b&&i.length===0)throw new p("Provide --card-text (text-image mode) or --images (upload mode); neither was given.");if(i.length>V)throw new p(`Too many images: ${i.length} (max ${V})`);if(b&&i.some((l)=>I.extname(l).toLowerCase()===".gif"))throw new p("文字配图模式追加的图片不支持 .gif(编辑器图片入口只接受 jpg/jpeg/png/webp)");const h=$t(i);await t.goto(Tt);await t.wait({time:1});await t.evaluate("() => { try { window.onbeforeunload = null; } catch (e) {} location.reload(); }").catch(()=>{return});let k="";for(let l=0;l<30;l++){await t.wait({time:0.5});k=await t.evaluate("() => location.href");if(k.includes("creator.xiaohongshu.com"))break}if(!k.includes("creator.xiaohongshu.com")){await t.screenshot({path:"/tmp/xhs_publish_redirect_debug.png"});throw Error(`Redirected away from creator center (landed on ${k}) — session may have expired. `+"Re-capture browser login via: ppcli xiaohongshu creator-profile. Debug screenshot: /tmp/xhs_publish_redirect_debug.png")}const A=await It(t);if((await Jt(t,A?.ok?5000:2000)).state==="video_surface"){await t.screenshot({path:"/tmp/xhs_publish_tab_debug.png"});const l=A?.ok?`clicked "${A.text}"`:`visible candidates: ${(A?.visibleTexts||[]).join(" | ")||"none"}`;throw Error("Still on the video publish page after trying to select 图文. "+`Details: ${l}. Debug screenshot: /tmp/xhs_publish_tab_debug.png`)}const E=await Vt(t);let C=y;if(b)C=await Ft(t,f,y);else{const l=typeof t.startNetworkCapture==="function"?await t.startNetworkCapture("").catch(()=>!1):!1,w=await tt(t,h);if(!w.ok){await t.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new u(`图片注入失败:${w.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_upload_debug.png")}await t.wait({time:X/1000});const x=await ct(t,h.length,l);if(!x.ok){await t.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new u(`图片上传未全部完成:只确认 ${x.completed}/${h.length} 张传到小红书 CDN,`+"继续发布会丢图(通常只剩封面)。直接重新运行本 publish 命令即可(会自动重置发布页重传);"+"请勿手动修改当前页面。Debug: /tmp/xhs_publish_upload_debug.png")}await et(t);if(E.length){await st(t,E);await t.wait({time:0.5})}const M=await ot(t);if(M&&typeof M.count==="number"&&M.count>h.length){await t.screenshot({path:"/tmp/xhs_publish_dup_debug.png"});throw new u(`清掉恢复态旧图后编辑器内仍有 ${M.count} 张图,超过本次注入的 ${h.length} 张。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。Debug: /tmp/xhs_publish_dup_debug.png")}}if(!await jt(t)){await t.screenshot({path:"/tmp/xhs_publish_form_debug.png"});throw new u("Editing form did not appear after image acquisition. The page layout may have changed. Debug screenshot: /tmp/xhs_publish_form_debug.png")}if(b)await it(t,f.length,"文字配图 generated images");if(b&&h.length>0){const l=typeof t.startNetworkCapture==="function"?await t.startNetworkCapture("").catch(()=>!1):!1,w=await tt(t,h);if(!w.ok){await t.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new u(`追加图片失败:${w.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await t.wait({time:X/1000});const x=await ct(t,h.length,l);if(!x.ok){await t.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new u(`追加图片上传未全部完成:只确认 ${x.completed}/${h.length} 张传到小红书 CDN。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await et(t);await it(t,f.length+h.length,"文字配图 appended images")}if(b&&E.length){await st(t,E);await t.wait({time:0.5})}await nt(t,U,r,"title");await N(t,0.6);await nt(t,Q,o,"content");await N(t,0.6);let R=[];if(d.length)R=await mt(t,Q,a);await wt(t,e);await N(t,1,0.6);const P=await St(t,["xhs-publish-btn"]);if(P){await vt(t,P.x,P.y);await N(t,0.4)}const F=s?["暂存离开","存草稿"]:["发布","发布笔记"],m=await t.evaluate(`
409
+ `));return Number(r)||0}function Gt(t){const e=String(t&&(t.url||t.key)||""),r=String(t&&t.method||"").toUpperCase(),n=Number(t&&t.status||0);if(r!=="PUT")return null;if(!/ros-upload[^/]*\/spectrum\//.test(e))return null;if(n<200||n>=300)return null;const o=e.match(/\/spectrum\/([^?#]+)/);return o?o[1]:e}function ut(t){return Math.min(45,6+t*4)}async function dt(t,e,r,n=90000){if(e<=0)return{ok:!0,completed:0};if(!r||typeof t.readNetworkCapture!=="function"){await t.wait({time:ut(e)});return{ok:!0,completed:null,viaFallback:!0}}const o=1500,a=Math.ceil(9000/o),c=Math.ceil(n/o),s=new Set;for(let d=0;d<c;d++){const m=await t.readNetworkCapture().catch(()=>[]);for(const y of Array.isArray(m)?m:[]){const w=Gt(y);if(w)s.add(w)}if(s.size>=e)return{ok:!0,completed:s.size};if(d>=a&&s.size===0){await t.wait({time:ut(e)});return{ok:!0,completed:null,viaFallback:!0}}await t.wait({time:o/1000})}return{ok:!1,completed:s.size}}const j=3;export function pickPublishedNote(t,e,r=Date.now()){const n=String(e||"").trim();return(Array.isArray(t)?t:[]).filter((i)=>!i.sticky).slice(0,j).find((i)=>{const a=String(i.title||"").trim();if(!a||!(a===n||a.startsWith(n)||n.startsWith(a)))return!1;const c=String(i.time||"").match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})$/);if(!c)return!0;const s=Date.UTC(Number(c[1]),Number(c[2])-1,Number(c[3]),Number(c[4]),Number(c[5]))-28800000;return Math.abs(r-s)<=93600000})??null}async function Yt(t,e,r){let n="";for(let o=0;o<r;o++){if(o>0)await t.wait({time:3});let i;try{i=await $t(t,{tab:"all",limit:j+5})}catch(c){return{note:null,reason:`创作者后台笔记列表读取出错:${c?.message??String(c)}`}}const a=pickPublishedNote(i.notes,e);if(a)return{note:a,reason:""};n=i.notes.length===0?"创作者后台笔记列表为空":`创作者后台最新 ${Math.min(i.notes.length,j)} 条笔记里没有同标题笔记`}return{note:null,reason:n}}xt({site:"xiaohongshu",name:"publish",access:"write",description:"小红书发布图文笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:yt.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:`图片路径,逗号分隔,最多${B}张 (jpg/png/gif/webp)`},{name:"card-text",required:!1,help:`文字配图卡片文字,多张卡片用 ${Y} 分隔,卡内换行用 \\n`},{name:"card-style",required:!1,help:`文字配图卡片样式,运行时按页面实际选项匹配;找不到会失败。省略时使用${S}。可选: ${K.map(([t,e])=>`${t}(${e})`).join(" ")}`},{name:"topics",required:!1,help:"话题名称,逗号分隔,最多10个;先用 topics 查询。无法准确选中时停止提交"},mt,{name:"draft",type:"bool",default:!1,help:"保存为草稿,不直接发布"},{name:"timeout",type:"int",default:300,help:"整段发布流程的等待预算(秒);超时后先用 creator-notes 回查,勿直接重复发布"},{name:"ai-generated",type:"boolean",help:"声明内容由 AI 生成(发布和草稿均支持;设置失败时停止提交)"},...wt],columns:["status","detail","url"],func:async(t,e)=>{gt(e);if(!t)throw Error("Browser page required");const r=String(e.title??"").trim(),n=e.file?String(e.file).trim():"";let o=String(e.content??"").trim();if(n){if(o)throw new b("正文位置参数和 --file 只能二选一,不能同时给");const l=N.resolve(n);if(!L.existsSync(l))throw new b(`--file 指向的文件不存在: ${l}`);o=L.readFileSync(l,"utf-8").trim();if(!o)throw new b(`--file 指向的文件内容为空: ${l}`)}const i=e.images?String(e.images).split(",").map((l)=>l.trim()).filter(Boolean):[],a=pt(e),c=a.map((l)=>l.name),s=Boolean(e.draft),d=e["card-text"]?String(e["card-text"]):"",m=d?d.split(Y).map((l)=>l.trim()).filter(Boolean):[],y=e["card-style"]?String(e["card-style"]).trim():"",w=m.length>0;if(!r)throw new b("--title is required");if(r.length>H)throw new b(`Title is ${r.length} chars — must be ≤ ${H}`);if(!o)throw new b("缺少笔记正文:给位置参数 <content>(字面文本),或用 --file <本机文件路径>");vt(o,{fileFlag:"--file"});if(!w&&i.length===0)throw new b("Provide --card-text (text-image mode) or --images (upload mode); neither was given.");if(i.length>B)throw new b(`Too many images: ${i.length} (max ${B})`);if(w&&i.some((l)=>N.extname(l).toLowerCase()===".gif"))throw new b("文字配图模式追加的图片不支持 .gif(编辑器图片入口只接受 jpg/jpeg/png/webp)");const f=Nt(i);await t.goto(Et);await t.wait({time:1});await t.evaluate("() => { try { window.onbeforeunload = null; } catch (e) {} location.reload(); }").catch(()=>{return});let k="";for(let l=0;l<30;l++){await t.wait({time:0.5});k=await t.evaluate("() => location.href");if(k.includes("creator.xiaohongshu.com"))break}if(!k.includes("creator.xiaohongshu.com")){await t.screenshot({path:"/tmp/xhs_publish_redirect_debug.png"});throw Error(`Redirected away from creator center (landed on ${k}) — session may have expired. `+"Re-capture browser login via: ppcli xiaohongshu creator-profile. Debug screenshot: /tmp/xhs_publish_redirect_debug.png")}const E=await Rt(t);if((await zt(t,E?.ok?5000:2000)).state==="video_surface"){await t.screenshot({path:"/tmp/xhs_publish_tab_debug.png"});const l=E?.ok?`clicked "${E.text}"`:`visible candidates: ${(E?.visibleTexts||[]).join(" | ")||"none"}`;throw Error("Still on the video publish page after trying to select 图文. "+`Details: ${l}. Debug screenshot: /tmp/xhs_publish_tab_debug.png`)}const A=await Ht(t);let C=y;if(w)C=await Vt(t,m,y);else{const l=typeof t.startNetworkCapture==="function"?await t.startNetworkCapture("").catch(()=>!1):!1,g=await nt(t,f);if(!g.ok){await t.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new u(`图片注入失败:${g.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_upload_debug.png")}await t.wait({time:G/1000});const x=await dt(t,f.length,l);if(!x.ok){await t.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new u(`图片上传未全部完成:只确认 ${x.completed}/${f.length} 张传到小红书 CDN,`+"继续发布会丢图(通常只剩封面)。直接重新运行本 publish 命令即可(会自动重置发布页重传);"+"请勿手动修改当前页面。Debug: /tmp/xhs_publish_upload_debug.png")}await rt(t);if(A.length){await ct(t,A);await t.wait({time:0.5})}const M=await at(t);if(M&&typeof M.count==="number"&&M.count>f.length){await t.screenshot({path:"/tmp/xhs_publish_dup_debug.png"});throw new u(`清掉恢复态旧图后编辑器内仍有 ${M.count} 张图,超过本次注入的 ${f.length} 张。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。Debug: /tmp/xhs_publish_dup_debug.png")}}if(!await Xt(t)){await t.screenshot({path:"/tmp/xhs_publish_form_debug.png"});throw new u("Editing form did not appear after image acquisition. The page layout may have changed. Debug screenshot: /tmp/xhs_publish_form_debug.png")}if(w)await st(t,m.length,"文字配图 generated images");if(w&&f.length>0){const l=typeof t.startNetworkCapture==="function"?await t.startNetworkCapture("").catch(()=>!1):!1,g=await nt(t,f);if(!g.ok){await t.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new u(`追加图片失败:${g.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await t.wait({time:G/1000});const x=await dt(t,f.length,l);if(!x.ok){await t.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new u(`追加图片上传未全部完成:只确认 ${x.completed}/${f.length} 张传到小红书 CDN。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await rt(t);await st(t,m.length+f.length,"文字配图 appended images")}if(w&&A.length){await ct(t,A);await t.wait({time:0.5})}await ot(t,J,r,"title");await I(t,0.6);await ot(t,tt,o,"content");await I(t,0.6);let P=[];if(c.length)P=await bt(t,tt,a);await _t(t,e);await I(t,1,0.6);const R=await kt(t,["xhs-publish-btn"]);if(R){await Tt(t,R.x,R.y);await I(t,0.4)}const V=s?["暂存离开","存草稿"]:["发布","发布笔记"],h=await t.evaluate(`
419
410
  (cfg => {
420
411
  const { isDraftMode, publishNames, draftNames, labels } = cfg;
421
412
  const isVisible = (el) => {
@@ -454,8 +445,8 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
454
445
  }
455
446
  }
456
447
  return { ok: false, via: 'none', hosts: hosts.length, lastMethodError };
457
- })(${JSON.stringify({isDraftMode:s,publishNames:kt,draftNames:At,labels:F})})
458
- `);if(!m?.ok){if(s){if(await t.evaluate(`
448
+ })(${JSON.stringify({isDraftMode:s,publishNames:At,draftNames:Ct,labels:V})})
449
+ `);if(!h?.ok){if(s){if(await t.evaluate(`
459
450
  (() => {
460
451
  const labels = ['返回', '关闭', '取消', '离开'];
461
452
  const buttons = document.querySelectorAll('button, [role="button"], div, span');
@@ -468,7 +459,7 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
468
459
  }
469
460
  return false;
470
461
  })()
471
- `)){await t.wait({time:1});const w=["暂存离开","存草稿","保存草稿"];if(await t.evaluate(`
462
+ `)){await t.wait({time:1});const g=["暂存离开","存草稿","保存草稿"];if(await t.evaluate(`
472
463
  (labels => {
473
464
  const buttons = document.querySelectorAll('button, [role="button"]');
474
465
  for (const btn of buttons) {
@@ -483,8 +474,8 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
483
474
  }
484
475
  }
485
476
  return false;
486
- })(${JSON.stringify(w)})
487
- `)){m.ok=!0;m.via="leave-save-fallback"}}if(!m?.ok){await t.wait({time:2});if(await t.evaluate(`
477
+ })(${JSON.stringify(g)})
478
+ `)){h.ok=!0;h.via="leave-save-fallback"}}if(!h?.ok){await t.wait({time:2});if(await t.evaluate(`
488
479
  () => {
489
480
  const markers = ['草稿箱(', '保存于', '编辑于'];
490
481
  for (const el of document.querySelectorAll('*')) {
@@ -493,7 +484,7 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
493
484
  }
494
485
  return false;
495
486
  }
496
- `)){m.ok=!0;m.via="auto-save"}}}}if(!m?.ok){await t.screenshot({path:"/tmp/xhs_publish_submit_debug.png"});const l=m?.via?` (via=${m.via})`:"",w=m?.error?`, error=${m.error}`:"",x=m?.lastMethodError?`, lastMethodError=${m.lastMethodError}`:"";throw Error(`未能触发「${F[0]}」按钮${l}${w}${x}。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_submit_debug.png")}await t.wait({time:4});const v=await t.evaluate("() => location.href"),ut=s?["草稿已保存","暂存成功","保存成功","保存于","图文笔记("]:["发布成功","上传成功"],J=await t.evaluate(`
487
+ `)){h.ok=!0;h.via="auto-save"}}}}if(!h?.ok){await t.screenshot({path:"/tmp/xhs_publish_submit_debug.png"});const l=h?.via?` (via=${h.via})`:"",g=h?.error?`, error=${h.error}`:"",x=h?.lastMethodError?`, lastMethodError=${h.lastMethodError}`:"";throw Error(`未能触发「${V[0]}」按钮${l}${g}${x}。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_submit_debug.png")}await t.wait({time:4});const v=await t.evaluate("() => location.href"),ft=s?["草稿已保存","暂存成功","保存成功","保存于","图文笔记("]:["发布成功","上传成功"],z=await t.evaluate(`
497
488
  (markers => {
498
489
  for (const el of document.querySelectorAll('*')) {
499
490
  if (el.tagName === 'STYLE' || el.tagName === 'SCRIPT') continue;
@@ -502,5 +493,5 @@ import{TOPIC_ID_ARG as ft,parseTopics as ht,attachTopics as mt}from"./_shared/to
502
493
  if (el.children.length === 0 && markers.some(marker => text.includes(marker))) return text;
503
494
  }
504
495
  return '';
505
- })(${JSON.stringify(ut)})
506
- `),dt=!v.includes("/publish/publish");let D=J.length>0||dt;const q=s?"暂存成功":"发布成功";let _=null;if(!s){_=await Xt(t,r,D?4:8);if(_)D=!0}if(!D){if(s)throw new u(`${q}无法确认:未观察到成功提示或提交后跳转。`+(v?`当前 URL: ${v}。`:"当前 URL 为空。")+"先用 `xiaohongshu drafts` 查草稿箱确认是否已存上;确认没有再重新运行本命令,避免堆出重复草稿。");throw new u(`${q}无法确认:未观察到成功提示或提交后跳转,创作者后台也暂未查到同标题笔记。`+(v?`当前 URL: ${v}。`:"")+"注意:发布按钮已被触发,笔记仍可能延迟入库。禁止立即重发——请等 1-2 分钟后用 `xiaohongshu creator-notes` 回查本篇标题是否已出现;确认不存在后才重新运行本 publish 命令,否则会重复发布。")}return[{status:`✅ ${q}`,detail:[`"${r}"`,b?`${f.length}张文字配图${h.length?` + ${h.length}张图片`:""}${C&&C!==S?` (${C})`:""}`:`${h.length}张图片`,R.length?`话题: ${R.join(" ")}`:"",_?.id?`笔记ID: ${_.id}`:"",J||(_?"已在创作者后台确认":"")||v||""].filter(Boolean).join(" · "),url:_?.id?`https://www.xiaohongshu.com/explore/${_.id}`:""}]}});
496
+ })(${JSON.stringify(ft)})
497
+ `),ht=!v.includes("/publish/publish");let D=z.length>0||ht;const q=s?"暂存成功":"发布成功";let p=null,O="";if(!s){const l=await Yt(t,r,D?2:4);p=l.note;O=l.reason;if(p)D=!0}if(!D){if(s)throw new u(`${q}无法确认:未观察到成功提示或提交后跳转。`+(v?`当前 URL: ${v}。`:"当前 URL 为空。")+"先用 `xiaohongshu drafts` 查草稿箱确认是否已存上;确认没有再重新运行本命令,避免堆出重复草稿。");throw new u(`${q}无法确认:未观察到成功提示或提交后跳转,创作者后台也暂未查到同标题笔记(${O||"未回查到"})。`+(v?`当前 URL: ${v}。`:"")+"注意:发布按钮已被触发,笔记仍可能延迟入库。禁止立即重发——请等 1-2 分钟后用 `xiaohongshu creator-notes` 回查本篇标题是否已出现;确认不存在后才重新运行本 publish 命令,否则会重复发布。")}return[{status:`✅ ${q}`,detail:[`"${r}"`,w?`${m.length}张文字配图${f.length?` + ${f.length}张图片`:""}${C&&C!==S?` (${C})`:""}`:`${f.length}张图片`,P.length?`话题: ${P.join(" ")}`:"",p?.id?`笔记ID: ${p.id}`:"",z||(p?"已在创作者后台确认":"")||v||"",p?.id?p.noteUrl?"链接需登录小红书后打开":"链接为创作者后台数据页,需登录":"",!s&&!p?.id?`未回查到笔记ID(${O||"未回查到"}),稍后用 creator-notes 按标题查`:""].filter(Boolean).join(" · "),url:p?.id?p.noteUrl||`https://creator.xiaohongshu.com/statistics/note-detail?noteId=${p.id}`:""}]}});