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
@@ -1,33 +1,45 @@
1
- import*as S from"node:fs";import*as b from"node:path";import{cli as v,Strategy as k}from"@jackwener/opencli/registry";import{AuthRequiredError as w,CommandExecutionError as c}from"@jackwener/opencli/errors";import{describeTwitterApiError as C,resolveTwitterOperationMetadata as A,unwrapBrowserResult as O}from"./shared.js";import{TWITTER_BEARER_TOKEN as E}from"./utils.js";import{assertLiteralContent as I}from"../_shared/content-guard.js";import{setInputFiles as P}from"../_shared/file-inject.js";const p=4,g=500,_=30000,h=250,q=1e4,f=500,y=15000,N="https://x.com/compose/post",M="https://x.com/home",x='input[type="file"][data-testid="fileInput"]',R=new Set([".jpg",".jpeg",".png",".gif",".webp"]),U={queryId:"5CdvsV_zjv4L64XFifAglw",features:{premium_content_api_read_enabled:!1,communities_web_enable_tweet_community_results_fetch:!0,c9s_tweet_anatomy_moderator_badge_enabled:!0,responsive_web_grok_analyze_button_fetch_trends_enabled:!1,responsive_web_grok_analyze_post_followups_enabled:!0,rweb_cashtags_composer_attachment_enabled:!0,responsive_web_jetfuel_frame:!0,responsive_web_grok_share_attachment_enabled:!0,responsive_web_grok_annotations_enabled:!0,responsive_web_edit_tweet_api_enabled:!0,rweb_conversational_replies_downvote_enabled:!1,graphql_is_translatable_rweb_tweet_is_translatable_enabled:!0,view_counts_everywhere_api_enabled:!0,longform_notetweets_consumption_enabled:!0,responsive_web_twitter_article_tweet_consumption_enabled:!0,content_disclosure_indicator_enabled:!0,content_disclosure_ai_generated_indicator_enabled:!0,responsive_web_grok_show_grok_translated_post:!0,responsive_web_grok_analysis_button_from_backend:!0,post_ctas_fetch_enabled:!0,longform_notetweets_rich_text_read_enabled:!0,longform_notetweets_inline_media_enabled:!1,profile_label_improvements_pcf_label_in_post_enabled:!0,responsive_web_profile_redirect_enabled:!1,rweb_tipjar_consumption_enabled:!1,verified_phone_label_enabled:!1,articles_preview_enabled:!0,rweb_cashtags_enabled:!0,responsive_web_grok_community_note_auto_translation_is_enabled:!0,responsive_web_graphql_skip_user_profile_image_extensions_enabled:!1,freedom_of_speech_not_reach_fetch_enabled:!0,standardized_nudges_misinfo:!0,tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled:!0,responsive_web_grok_image_annotation_enabled:!0,responsive_web_grok_imagine_annotation_enabled:!0,responsive_web_graphql_timeline_navigation_enabled:!0},fieldToggles:{}};function J(e){const o=e.split(",").map((t)=>t.trim()).filter(Boolean);if(o.length>p)throw new c(`Too many images: ${o.length} (max ${p})`);return o.map((t)=>{const s=b.resolve(t),n=b.extname(s).toLowerCase();if(!R.has(n))throw new c(`Unsupported image format "${n}". Supported: jpg, png, gif, webp`);const r=S.statSync(s,{throwIfNoEntry:!1});if(!r||!r.isFile())throw new c(`Not a valid file: ${s}`);return s})}function L(e){const t=(e instanceof Error?e.message:String(e)).toLowerCase();return t.includes("unknown action")||t.includes("not supported")||t.includes("inserttext returned no inserted flag")}async function z(e){return e.evaluate(`(() => {
1
+ import*as k from"node:fs";import*as w from"node:path";import{cli as C,Strategy as O}from"@jackwener/opencli/registry";import{AuthRequiredError as g,CommandExecutionError as u}from"@jackwener/opencli/errors";import{describeTwitterApiError as A,resolveTwitterOperationMetadata as E,unwrapBrowserResult as I}from"./shared.js";import{TWITTER_BEARER_TOKEN as P}from"./utils.js";import{assertLiteralContent as M}from"../_shared/content-guard.js";import{setInputFiles as N}from"../_shared/file-inject.js";const h=4,y=500,p=30000,x=250,q=1e4,_=500,T=15000,R="https://x.com/compose/post",U="https://x.com/home",v='input[type="file"][data-testid="fileInput"]',J=new Set([".jpg",".jpeg",".png",".gif",".webp"]),B={queryId:"5CdvsV_zjv4L64XFifAglw",features:{premium_content_api_read_enabled:!1,communities_web_enable_tweet_community_results_fetch:!0,c9s_tweet_anatomy_moderator_badge_enabled:!0,responsive_web_grok_analyze_button_fetch_trends_enabled:!1,responsive_web_grok_analyze_post_followups_enabled:!0,rweb_cashtags_composer_attachment_enabled:!0,responsive_web_jetfuel_frame:!0,responsive_web_grok_share_attachment_enabled:!0,responsive_web_grok_annotations_enabled:!0,responsive_web_edit_tweet_api_enabled:!0,rweb_conversational_replies_downvote_enabled:!1,graphql_is_translatable_rweb_tweet_is_translatable_enabled:!0,view_counts_everywhere_api_enabled:!0,longform_notetweets_consumption_enabled:!0,responsive_web_twitter_article_tweet_consumption_enabled:!0,content_disclosure_indicator_enabled:!0,content_disclosure_ai_generated_indicator_enabled:!0,responsive_web_grok_show_grok_translated_post:!0,responsive_web_grok_analysis_button_from_backend:!0,post_ctas_fetch_enabled:!0,longform_notetweets_rich_text_read_enabled:!0,longform_notetweets_inline_media_enabled:!1,profile_label_improvements_pcf_label_in_post_enabled:!0,responsive_web_profile_redirect_enabled:!1,rweb_tipjar_consumption_enabled:!1,verified_phone_label_enabled:!1,articles_preview_enabled:!0,rweb_cashtags_enabled:!0,responsive_web_grok_community_note_auto_translation_is_enabled:!0,responsive_web_graphql_skip_user_profile_image_extensions_enabled:!1,freedom_of_speech_not_reach_fetch_enabled:!0,standardized_nudges_misinfo:!0,tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled:!0,responsive_web_grok_image_annotation_enabled:!0,responsive_web_grok_imagine_annotation_enabled:!0,responsive_web_graphql_timeline_navigation_enabled:!0},fieldToggles:{}};function L(e){const t=e.split(",").map((o)=>o.trim()).filter(Boolean);if(t.length>h)throw new u(`Too many images: ${t.length} (max ${h})`);return t.map((o)=>{const s=w.resolve(o),r=w.extname(s).toLowerCase();if(!J.has(r))throw new u(`Unsupported image format "${r}". Supported: jpg, png, gif, webp`);const n=k.statSync(s,{throwIfNoEntry:!1});if(!n||!n.isFile())throw new u(`Not a valid file: ${s}`);return s})}function z(e){const o=(e instanceof Error?e.message:String(e)).toLowerCase();return o.includes("unknown action")||o.includes("not supported")||o.includes("inserttext returned no inserted flag")}const m=`
2
2
  const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
3
- const boxes = Array.from(document.querySelectorAll('[data-testid="tweetTextarea_0"]'));
4
- const box = boxes.find(visible) || boxes[0];
3
+ const composerBoxes = () => Array.from(document.querySelectorAll('[data-testid="tweetTextarea_0"]'));
4
+ const composerDialog = (box) => box ? box.closest('[role="dialog"][aria-modal="true"]') : null;
5
+ const pickComposer = () => {
6
+ const boxes = composerBoxes();
7
+ return boxes.find((box) => visible(box) && composerDialog(box)) || boxes.find(visible) || boxes[0] || null;
8
+ };
9
+ `;async function D(e){return e.evaluate(`(() => {
10
+ ${m}
11
+ const box = pickComposer();
5
12
  if (!box) return { ok: false, message: 'Could not find the tweet composer text area. Are you logged in?' };
6
13
  box.focus();
7
14
  return { ok: true };
8
- })()`)}async function B(e,o){const t=Math.ceil(q/h);return e.evaluate(`(async () => {
9
- const expected = ${JSON.stringify(o)};
15
+ })()`)}function j(e,t=80){const o=String(e??"").replace(/\s+/g," ").trim();return o.length>t?`${o.slice(0,t)}…`:o}function b(e,t){const o=(n)=>String(n||"").replace(/ /g," ").replace(/\s+/g," ").trim(),s=o(t?.actualText),r=[`期望文本 ${o(e).length} 字`,s?`编辑框实际 ${s.length} 字:「${j(s)}」`:"编辑框为空(文本没有写进去)"];if(t?.composerCount!==void 0)r.push(`页面上编辑框 ${t.composerCount} 个${t.inDialog?",校验的是发帖弹窗里的":""}`);if(t?.focusInComposer===!1)r.push("焦点不在该编辑框");if(t?.pageHasFocus===!1)r.push("页面未获得焦点");if(t?.visibility&&t.visibility!=="visible")r.push(`页面可见性 ${t.visibility}`);return`Could not verify tweet text in the composer after typing(${r.join(";")})。尚未提交,未发出任何内容。`}async function F(e,t){const o=Math.ceil(q/x);return e.evaluate(`(async () => {
16
+ ${m}
17
+ const expected = ${JSON.stringify(t)};
10
18
  const normalize = s => String(s || '').replace(/ /g, ' ').replace(/s+/g, ' ').trim();
11
19
  const normalizedExpected = normalize(expected);
12
- for (let i = 0; i < ${JSON.stringify(t)}; i++) {
13
- const box = document.querySelector('[data-testid="tweetTextarea_0"]');
14
- const actual = box ? (box.innerText || box.textContent || '') : '';
15
- if (box && normalize(actual).includes(normalizedExpected)) return { ok: true };
16
- await new Promise(r => setTimeout(r, ${JSON.stringify(h)}));
20
+ const read = (box) => box ? (box.innerText || box.textContent || '') : '';
21
+ for (let i = 0; i < ${JSON.stringify(o)}; i++) {
22
+ const box = pickComposer();
23
+ if (box && normalize(read(box)).includes(normalizedExpected)) return { ok: true };
24
+ await new Promise(r => setTimeout(r, ${JSON.stringify(x)}));
17
25
  }
18
- const box = document.querySelector('[data-testid="tweetTextarea_0"]');
26
+ const box = pickComposer();
19
27
  return {
20
28
  ok: false,
21
29
  message: 'Could not verify tweet text in the composer after typing.',
22
- actualText: box ? (box.innerText || box.textContent || '') : ''
30
+ actualText: read(box),
31
+ composerCount: composerBoxes().length,
32
+ inDialog: !!composerDialog(box),
33
+ focusInComposer: !!box && (document.activeElement === box || box.contains(document.activeElement)),
34
+ pageHasFocus: document.hasFocus(),
35
+ visibility: document.visibilityState,
23
36
  };
24
- })()`)}async function j(e,o){const t=await z(e);if(!t?.ok)return t;const s=[e.nativeType?.bind(e),e.insertText?.bind(e)].filter(Boolean);for(const n of s)try{await n(o);const r=await B(e,o);if(r?.ok)return r}catch(r){if(!L(r))throw r}return e.evaluate(`(async () => {
37
+ })()`)}async function X(e,t){const o=await D(e);if(!o?.ok)return o;const s=[e.nativeType?.bind(e),e.insertText?.bind(e)].filter(Boolean);for(const n of s){try{await n(t)}catch(a){if(!z(a))throw a;continue}const l=await F(e,t);if(l?.ok)return l;if(String(l?.actualText??"").trim())return{ok:!1,message:b(t,l)}}const r=await e.evaluate(`(async () => {
25
38
  try {
26
- const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
27
- const boxes = Array.from(document.querySelectorAll('[data-testid="tweetTextarea_0"]'));
28
- const box = boxes.find(visible) || boxes[0];
39
+ ${m}
40
+ const box = pickComposer();
29
41
  if (!box) return { ok: false, message: 'Could not find the tweet composer text area. Are you logged in?' };
30
- const textToInsert = ${JSON.stringify(o)};
42
+ const textToInsert = ${JSON.stringify(t)};
31
43
  const normalize = s => String(s || '').replace(/ /g, ' ').replace(/s+/g, ' ').trim();
32
44
  box.focus();
33
45
  if (!document.execCommand('insertText', false, textToInsert)) {
@@ -38,13 +50,22 @@ import*as S from"node:fs";import*as b from"node:path";import{cli as v,Strategy a
38
50
  await new Promise(r => setTimeout(r, 500));
39
51
  const actual = box.innerText || box.textContent || '';
40
52
  if (normalize(actual).includes(normalize(textToInsert))) return { ok: true };
41
- return { ok: false, message: 'Could not verify tweet text in the composer after typing.', actualText: actual };
53
+ return {
54
+ ok: false,
55
+ message: 'Could not verify tweet text in the composer after typing.',
56
+ actualText: actual,
57
+ composerCount: composerBoxes().length,
58
+ inDialog: !!composerDialog(box),
59
+ focusInComposer: document.activeElement === box || box.contains(document.activeElement),
60
+ pageHasFocus: document.hasFocus(),
61
+ visibility: document.visibilityState,
62
+ };
42
63
  } catch (e) { return { ok: false, message: String(e) }; }
43
- })()`)}async function D(e,o){const t=Math.ceil(_/g);return e.evaluate(`(async () => {
44
- const expected = ${JSON.stringify(o)};
45
- const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
46
- for (let i = 0; i < ${JSON.stringify(t)}; i++) {
47
- await new Promise(r => setTimeout(r, ${JSON.stringify(g)}));
64
+ })()`);if(r&&r.ok===!1&&"actualText"in r)return{ok:!1,message:b(t,r)};return r}async function H(e,t){const o=Math.ceil(p/y);return e.evaluate(`(async () => {
65
+ ${m}
66
+ const expected = ${JSON.stringify(t)};
67
+ for (let i = 0; i < ${JSON.stringify(o)}; i++) {
68
+ await new Promise(r => setTimeout(r, ${JSON.stringify(y)}));
48
69
  const attachments = document.querySelector('[data-testid="attachments"]');
49
70
  const previewCount = Math.max(
50
71
  attachments ? attachments.querySelectorAll('[role="group"], img, video').length : 0,
@@ -53,33 +74,36 @@ import*as S from"node:fs";import*as b from"node:path";import{cli as v,Strategy a
53
74
  /remove media|remove image|remove/i.test((el.getAttribute('aria-label') || '') + ' ' + (el.textContent || ''))
54
75
  ).length
55
76
  );
56
- const button = Array.from(document.querySelectorAll('[data-testid="tweetButtonInline"], [data-testid="tweetButton"]'))
77
+ const scope = composerDialog(pickComposer()) || document;
78
+ const button = Array.from(scope.querySelectorAll('[data-testid="tweetButtonInline"], [data-testid="tweetButton"]'))
57
79
  .find((el) => visible(el));
58
80
  const buttonReady = !!button && !button.disabled && button.getAttribute('aria-disabled') !== 'true';
59
81
  if (previewCount >= expected && buttonReady) return { ok: true, previewCount };
60
82
  }
61
- return { ok: false, message: 'Image upload timed out (${_/1000}s).' };
62
- })()`)}async function F(e,o){const t=Math.ceil(y/f),s=await e.evaluate(`(async () => {
83
+ return { ok: false, message: 'Image upload timed out (${p/1000}s).' };
84
+ })()`)}async function G(e,t){const o=Math.ceil(T/_),s=await e.evaluate(`(async () => {
63
85
  try {
64
- const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
86
+ ${m}
65
87
  // For text-only posts the Tweet button only becomes enabled once
66
88
  // Draft.js has propagated the inserted text into composer state, which
67
89
  // can lag the prior insert/verify step. A single find misfires on
68
90
  // slow renders with "Tweet button is disabled or not found." Poll for
69
91
  // a visible, enabled button before clicking.
70
92
  let btn;
71
- for (let i = 0; i < ${JSON.stringify(t)}; i++) {
72
- const buttons = Array.from(document.querySelectorAll('[data-testid="tweetButtonInline"], [data-testid="tweetButton"]'));
93
+ for (let i = 0; i < ${JSON.stringify(o)}; i++) {
94
+ // 发帖按钮也有两个(弹窗里的和首页内联的);只点文本所在那个编辑框自己的按钮。
95
+ const scope = composerDialog(pickComposer()) || document;
96
+ const buttons = Array.from(scope.querySelectorAll('[data-testid="tweetButtonInline"], [data-testid="tweetButton"]'));
73
97
  btn = buttons.find((el) => visible(el) && !el.disabled && el.getAttribute('aria-disabled') !== 'true');
74
98
  if (btn) break;
75
- await new Promise(r => setTimeout(r, ${JSON.stringify(f)}));
99
+ await new Promise(r => setTimeout(r, ${JSON.stringify(_)}));
76
100
  }
77
101
  if (!btn) return { ok: false, message: 'Tweet button is disabled or not found.' };
78
102
  btn.click();
79
103
  return { ok: true };
80
104
  } catch (e) { return { ok: false, message: String(e) }; }
81
- })()`);if(!s?.ok)return s;const n=Math.ceil(y/f);return e.evaluate(`(async () => {
82
- const expected = ${JSON.stringify(o)};
105
+ })()`);if(!s?.ok)return s;const r=Math.ceil(T/_);return e.evaluate(`(async () => {
106
+ const expected = ${JSON.stringify(t)};
83
107
  const normalize = s => String(s || '').replace(/ /g, ' ').replace(/s+/g, ' ').trim();
84
108
  const expectedText = normalize(expected);
85
109
  const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
@@ -96,8 +120,8 @@ import*as S from"node:fs";import*as b from"node:path";import{cli as v,Strategy a
96
120
  }
97
121
  return {};
98
122
  };
99
- for (let i = 0; i < ${JSON.stringify(n)}; i++) {
100
- await new Promise(r => setTimeout(r, ${JSON.stringify(f)}));
123
+ for (let i = 0; i < ${JSON.stringify(r)}; i++) {
124
+ await new Promise(r => setTimeout(r, ${JSON.stringify(_)}));
101
125
  const toasts = Array.from(document.querySelectorAll('[role="alert"], [data-testid="toast"]'))
102
126
  .filter((el) => visible(el));
103
127
  const successToast = toasts.find((el) => /sent|posted|your post was sent|your tweet was sent/i.test(el.textContent || ''));
@@ -117,15 +141,15 @@ import*as S from"node:fs";import*as b from"node:path";import{cli as v,Strategy a
117
141
  }
118
142
  }
119
143
  return { ok: false, message: 'Tweet submission did not complete before timeout.' };
120
- })()`)}function T(e){const o=Array.isArray(e?.errors)?e.errors:[];if(o.length===0)return"";return o.map((t)=>t?.message||JSON.stringify(t)).filter(Boolean).join("; ").slice(0,300)}function X(e){const o=e?.data?.create_tweet?.tweet_results?.result,t=o?.tweet||o;if(!t||typeof t!=="object")return null;const s=String(t.rest_id||t.legacy?.id_str||"").trim();if(!/^\d+$/.test(s))return null;const n=t.core?.user_results?.result,r=String(n?.core?.screen_name||n?.legacy?.screen_name||"").trim();return{id:s,url:r?`https://x.com/${r}/status/${s}`:`https://x.com/i/status/${s}`}}async function H(e,o){await e.goto(M,{waitUntil:"load",settleMs:1000});const s=(await e.getCookies({url:"https://x.com"})).find((d)=>d.name==="ct0")?.value||null;if(!s)throw new w("x.com","Not logged into x.com (no ct0 cookie)");const n=await A(e,"CreateTweet",U),r={variables:{tweet_text:o,dark_request:!1,media:{media_entities:[],possibly_sensitive:!1},semantic_annotation_ids:[]},features:n.features,queryId:n.queryId};if(n.fieldToggles&&Object.keys(n.fieldToggles).length>0)r.fieldToggles=n.fieldToggles;const u=JSON.stringify({Authorization:`Bearer ${decodeURIComponent(E)}`,"X-Csrf-Token":s,"X-Twitter-Auth-Type":"OAuth2Session","X-Twitter-Active-User":"yes","Content-Type":"application/json"}),i=`/i/api/graphql/${n.queryId}/CreateTweet`,a=JSON.stringify(r),l=O(await e.evaluate(`async () => {
121
- const r = await fetch(${JSON.stringify(i)}, {
144
+ })()`)}function S(e){const t=Array.isArray(e?.errors)?e.errors:[];if(t.length===0)return"";return t.map((o)=>o?.message||JSON.stringify(o)).filter(Boolean).join("; ").slice(0,300)}function K(e){const t=e?.data?.create_tweet?.tweet_results?.result,o=t?.tweet||t;if(!o||typeof o!=="object")return null;const s=String(o.rest_id||o.legacy?.id_str||"").trim();if(!/^\d+$/.test(s))return null;const r=o.core?.user_results?.result,n=String(r?.core?.screen_name||r?.legacy?.screen_name||"").trim();return{id:s,url:n?`https://x.com/${n}/status/${s}`:`https://x.com/i/status/${s}`}}async function V(e,t){await e.goto(U,{waitUntil:"load",settleMs:1000});const s=(await e.getCookies({url:"https://x.com"})).find((d)=>d.name==="ct0")?.value||null;if(!s)throw new g("x.com","Not logged into x.com (no ct0 cookie)");const r=await E(e,"CreateTweet",B),n={variables:{tweet_text:t,dark_request:!1,media:{media_entities:[],possibly_sensitive:!1},semantic_annotation_ids:[]},features:r.features,queryId:r.queryId};if(r.fieldToggles&&Object.keys(r.fieldToggles).length>0)n.fieldToggles=r.fieldToggles;const l=JSON.stringify({Authorization:`Bearer ${decodeURIComponent(P)}`,"X-Csrf-Token":s,"X-Twitter-Auth-Type":"OAuth2Session","X-Twitter-Active-User":"yes","Content-Type":"application/json"}),a=`/i/api/graphql/${r.queryId}/CreateTweet`,i=JSON.stringify(n),c=I(await e.evaluate(`async () => {
145
+ const r = await fetch(${JSON.stringify(a)}, {
122
146
  method: 'POST',
123
- headers: ${u},
147
+ headers: ${l},
124
148
  credentials: 'include',
125
- body: ${JSON.stringify(a)},
149
+ body: ${JSON.stringify(i)},
126
150
  });
127
151
  const bodyText = await r.text();
128
152
  let bodyJson = null;
129
153
  try { bodyJson = JSON.parse(bodyText); } catch {}
130
154
  return { ok: r.ok, httpStatus: r.status, bodyJson, bodyText };
131
- }`));if(l?.httpStatus===401||l?.httpStatus===403)throw new w("x.com",`Twitter CreateTweet returned HTTP ${l.httpStatus}`);if(!l?.ok){const d=T(l?.bodyJson);return{ok:!1,message:C("CreateTweet",l?.httpStatus??0,d||void 0)}}const m=X(l.bodyJson);if(!m){const d=T(l.bodyJson);return{ok:!1,message:d?`CreateTweet failed: ${d}`:"CreateTweet returned no created tweet id."}}return{ok:!0,message:"Tweet posted successfully.",...m}}v({site:"twitter",name:"post",access:"write",description:"Post a new tweet/thread",domain:"x.com",strategy:k.UI,browser:!0,args:[{name:"text",fromFile:!0,type:"string",required:!0,positional:!0,help:"The text content of the tweet"},{name:"images",type:"string",required:!1,help:"Image paths, comma-separated, max 4 (jpg/png/gif/webp)"}],columns:["status","message","text","id","url"],func:async(e,o)=>{if(!e)throw new c("Browser session required for twitter post");const t=o.images?J(String(o.images)):[],s=String(o.text??"");I(s,{label:"推文内容"});try{await e.goto(N,{waitUntil:"load",settleMs:2500});await e.wait({selector:'[data-testid="tweetTextarea_0"]',timeout:15})}catch(i){if(t.length>0)throw i;const a=await H(e,s);if(!a?.ok)throw new c(a?.message??"Tweet failed to post.");return[{status:"success",message:a?.message??"Tweet posted.",text:s,...a?.id?{id:a.id}:{},...a?.url?{url:a.url}:{}}]}if(t.length>0){await e.wait({selector:x,timeout:20});await P(e,x,t);const i=await D(e,t.length);if(!i?.ok)throw new c(i?.message??`Image upload timed out (${_/1000}s).`)}const n=await j(e,s);if(!n?.ok)throw new c(n?.message??"Could not type tweet text.");await e.wait(1);let r=null,u=null;try{r=await F(e,s)}catch(i){u=i}if(!r?.ok){const{confirmTweetByReadback:i}=await import("./tweets.js"),a=await i(e,s);if(a.found)return[{status:"success",message:"提交回执未确认(可能超时),但发后回查已在时间线确认发布成功,请勿重发。",text:s,...a.id?{id:a.id}:{},...a.url?{url:a.url}:{},confirmed_by:"readback"}];const l=u?String(u&&u.message||u):r?.message??"Tweet was not confirmed sent.",m=a.error?`发后回查失败(${a.error})——无法确认,重试前请先人工核对时间线`:`发后回查已检查最近 ${a.checked} 条推文,未发现该内容,可安全重试`;throw new c(`${l};${m}`)}return[{status:"success",message:r?.message??"Tweet posted.",text:s,...r?.id?{id:r.id}:{},...r?.url?{url:r.url}:{}}]}});
155
+ }`));if(c?.httpStatus===401||c?.httpStatus===403)throw new g("x.com",`Twitter CreateTweet returned HTTP ${c.httpStatus}`);if(!c?.ok){const d=S(c?.bodyJson);return{ok:!1,message:A("CreateTweet",c?.httpStatus??0,d||void 0)}}const f=K(c.bodyJson);if(!f){const d=S(c.bodyJson);return{ok:!1,message:d?`CreateTweet failed: ${d}`:"CreateTweet returned no created tweet id."}}return{ok:!0,message:"Tweet posted successfully.",...f}}C({site:"twitter",name:"post",access:"write",description:"Post a new tweet/thread",domain:"x.com",strategy:O.UI,browser:!0,args:[{name:"text",fromFile:!0,type:"string",required:!0,positional:!0,help:"The text content of the tweet"},{name:"images",type:"string",required:!1,help:"Image paths, comma-separated, max 4 (jpg/png/gif/webp)"}],columns:["status","message","text","id","url"],func:async(e,t)=>{if(!e)throw new u("Browser session required for twitter post");const o=t.images?L(String(t.images)):[],s=String(t.text??"");M(s,{label:"推文内容"});try{await e.goto(R,{waitUntil:"load",settleMs:2500});await e.wait({selector:'[data-testid="tweetTextarea_0"]',timeout:15})}catch(a){if(o.length>0)throw a;const i=await V(e,s);if(!i?.ok)throw new u(i?.message??"Tweet failed to post.");return[{status:"success",message:i?.message??"Tweet posted.",text:s,...i?.id?{id:i.id}:{},...i?.url?{url:i.url}:{}}]}if(o.length>0){await e.wait({selector:v,timeout:20});await N(e,v,o);const a=await H(e,o.length);if(!a?.ok)throw new u(a?.message??`Image upload timed out (${p/1000}s).`)}const r=await X(e,s);if(!r?.ok)throw new u(r?.message??"Could not type tweet text.");await e.wait(1);let n=null,l=null;try{n=await G(e,s)}catch(a){l=a}if(!n?.ok){const{confirmTweetByReadback:a}=await import("./tweets.js"),i=await a(e,s);if(i.found)return[{status:"success",message:"提交回执未确认(可能超时),但发后回查已在时间线确认发布成功,请勿重发。",text:s,...i.id?{id:i.id}:{},...i.url?{url:i.url}:{},confirmed_by:"readback"}];const c=l?String(l&&l.message||l):n?.message??"Tweet was not confirmed sent.",f=i.error?`发后回查失败(${i.error})——无法确认,重试前请先人工核对时间线`:`发后回查已检查最近 ${i.checked} 条推文,未发现该内容,可安全重试`;throw new u(`${c};${f}`)}return[{status:"success",message:n?.message??"Tweet posted.",text:s,...n?.id?{id:n.id}:{},...n?.url?{url:n.url}:{}}]}});export const __test__={PICK_COMPOSER_JS:m,describeComposerMismatch:b};
@@ -1,31 +1,34 @@
1
- import{AuthRequiredError as i,CommandExecutionError as r}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as o}from"../_shared/site-auth.js";async function a(t){return(await t.getCookies({url:"https://channels.weixin.qq.com"})).some((n)=>n.name==="sessionid"&&n.value)}async function s(t){if(!await a(t))throw new i("channels.weixin.qq.com","WeChat Channels sessionid cookie missing");await t.goto("https://channels.weixin.qq.com/platform");let e=null;for(let n=0;n<30;n++){e=await t.evaluate(`(async () => {
2
- try {
3
- var href = location.href || '';
4
- if (!/^https?:\\/\\/channels\\.weixin\\.qq\\.com/.test(href)) {
5
- return { kind: 'pending', detail: 'location not settled: ' + href };
6
- }
7
- if (/login\\.html/.test(href)) {
8
- return { kind: 'auth', detail: 'WeChat Channels platform redirected to login.html' };
9
- }
10
- const r = await fetch('https://channels.weixin.qq.com/cgi-bin/mmfinderassistant-bin/auth/auth_data', {
11
- method: 'POST',
12
- credentials: 'include',
13
- headers: { 'Content-Type': 'application/json' },
14
- body: '{}',
15
- });
16
- if (!r.ok) return { kind: 'http', httpStatus: r.status };
17
- const d = await r.json();
18
- if (!d || d.base_resp?.ret !== 0) {
19
- return { kind: 'auth', detail: 'WeChat Channels auth_data base_resp.ret=' + String(d?.base_resp?.ret) };
20
- }
21
- const fu = d.data?.finder_user || d.finder_user || {};
22
- const userId = String(fu.uniq_id || fu.username || '');
23
- const name = String(fu.nickname || fu.name || '');
24
- if (!userId && !name) {
25
- return { kind: 'auth', detail: 'WeChat Channels auth_data 200 but finder_user empty' };
26
- }
27
- return { ok: true, user_id: userId, name };
28
- } catch (e) {
29
- return { kind: 'pending', detail: String(e && e.message || e) };
30
- }
31
- })()`);if(e?.ok||e?.kind==="auth"||e?.kind==="http")break;await t.wait({time:0.5})}if(e?.kind==="auth")throw new i("channels.weixin.qq.com",e.detail);if(e?.kind==="http")throw new r(`HTTP ${e.httpStatus} from auth_data`);if(e?.kind==="pending")throw new i("channels.weixin.qq.com",`WeChat Channels auth probe not settled: ${e.detail}`);if(!e?.ok)throw new r(`Unexpected WeChat Channels probe: ${JSON.stringify(e)}`);return{user_id:e.user_id,name:e.name}}o({site:"wechat-channels",domain:"channels.weixin.qq.com",loginUrl:"https://channels.weixin.qq.com/login.html?from=assistant",columns:["user_id","name"],quickCheck:a,verify:s,poll:async(t)=>{if(!await a(t))throw new i("channels.weixin.qq.com","Waiting for WeChat Channels sessionid cookie");return s(t)}});
1
+ import{AuthRequiredError as r,CommandExecutionError as c}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as h}from"../_shared/site-auth.js";const a="channels.weixin.qq.com",f="https://channels.weixin.qq.com/platform",m="https://channels.weixin.qq.com/cgi-bin/mmfinderassistant-bin/auth/auth_data",g=0,_=300330,w=300350;function k(e){if(e&&typeof e==="object"&&!Array.isArray(e)&&"data"in e&&"session"in e)return e.data;return e}async function p(e,n){return k(await e.evaluate(n))}async function o(e){return(await e.getCookies({url:"https://channels.weixin.qq.com"})).some((t)=>t.name==="sessionid"&&t.value)}export function classifyAuthData(e){if(!e||typeof e!=="object"||Array.isArray(e))return{kind:"unreadable",detail:`auth_data 响应不是对象: ${JSON.stringify(e)?.slice(0,200)}`};const n=e.errCode;if(typeof n!=="number")return{kind:"unreadable",detail:`auth_data 响应缺少 errCode(字段: ${Object.keys(e).join(",")||"无"})`};if(n>=_&&n<w)return{kind:"auth",detail:`WeChat Channels auth_data errCode=${n}${e.errMsg?` ${e.errMsg}`:""}`};if(n!==g)return{kind:"unreadable",detail:`auth_data errCode=${n}${e.errMsg||e.message?` ${e.errMsg||e.message}`:""}`};const t=e.data?.finderUser,i=String(t?.finderUsername??""),s=String(t?.nickname??"");if(!i&&!s)return{kind:"unreadable",detail:`auth_data errCode=0 但取不到 data.finderUser(data 字段: ${Object.keys(e.data??{}).join(",")||"无"})`};return{kind:"ok",user_id:i,name:s}}const C=`(async () => {
2
+ try {
3
+ var href = location.href || '';
4
+ if (!/^https?:\\/\\/channels\\.weixin\\.qq\\.com/.test(href)) {
5
+ return { kind: 'pending', detail: 'location not settled: ' + href };
6
+ }
7
+ var onLogin = /\\/login\\.html|\\/platform\\/login/.test(location.pathname);
8
+ var ls = function (k) { try { return window.localStorage.getItem(k); } catch (e) { return null; } };
9
+ var store = window._store && window._store.userStore;
10
+ var r = await fetch(${JSON.stringify(m)}, {
11
+ method: 'POST',
12
+ credentials: 'include',
13
+ headers: { 'Content-Type': 'application/json', 'X-WECHAT-UIN': (store && store.fakeUin) || '0000000000' },
14
+ body: JSON.stringify({
15
+ timestamp: String(Date.now()),
16
+ _log_finder_uin: ls('finder_uin'),
17
+ _log_finder_id: ls('finder_username'),
18
+ rawKeyBuff: ls('finder_raw') || '',
19
+ pluginSessionId: ls('FinderHelperIframeSessionId'),
20
+ scene: 7,
21
+ reqScene: 7,
22
+ }),
23
+ });
24
+ if (!r.ok) return { kind: 'http', httpStatus: r.status, onLogin: onLogin };
25
+ var text = await r.text();
26
+ var body;
27
+ try { body = JSON.parse(text); } catch (e) {
28
+ return { kind: 'unreadable', detail: 'auth_data 响应不是 JSON: ' + text.slice(0, 200), onLogin: onLogin };
29
+ }
30
+ return { kind: 'body', body: body, onLogin: onLogin };
31
+ } catch (e) {
32
+ return { kind: 'pending', detail: String(e && e.message || e) };
33
+ }
34
+ })()`;async function d(e,{navigate:n}){if(n)await e.goto(f);let t=null;for(let i=0;i<30;i++){t=await p(e,C);if(t&&t.kind!=="pending")break;await e.wait({time:0.5})}if(!t||t.kind==="pending")return{kind:"unreadable",detail:`登录探测页面未就绪: ${t?.detail??"无返回"}`};if(t.kind==="http")return{kind:"unreadable",detail:`auth_data HTTP ${t.httpStatus}`};if(t.kind==="body")return classifyAuthData(t.body);return t}function y(e){return new c(`视频号登录状态读取失败(不代表未登录): ${e}`,"网页端登录可能仍然有效;稍后重试 whoami,或直接执行发布命令(发布命令自己会校验登录)")}async function l(e){if(!await o(e))throw new r(a,"WeChat Channels sessionid cookie missing");const n=await d(e,{navigate:!0});if(n.kind==="ok")return{user_id:n.user_id,name:n.name};if(n.kind==="auth")throw new r(a,n.detail);throw y(n.detail)}async function u(e){if(!await o(e))throw new r(a,"Waiting for WeChat Channels sessionid cookie");const n=await d(e,{navigate:!1});if(n.kind==="ok")return{user_id:n.user_id,name:n.name};throw new r(a,`Waiting for WeChat Channels login: ${n.detail}`)}h({site:"wechat-channels",domain:a,loginUrl:"https://channels.weixin.qq.com/login.html?from=assistant",columns:["user_id","name"],quickCheck:o,verify:l,poll:u});export const __test__={classifyAuthData,verifyWechatChannelsIdentity:l,pollWechatChannelsLogin:u};