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.
- package/cli-manifest.json +345 -71
- package/clis/_shared/article/login.js +1 -1
- package/clis/_shared/login-wait.js +1 -0
- package/clis/_shared/site-auth.js +1 -1
- package/clis/baijiahao/article.js +2 -2
- package/clis/baijiahao/auth.js +18 -0
- package/clis/baijiahao/lists.js +1 -1
- package/clis/baijiahao/stats.js +1 -1
- package/clis/bilibili/coin.js +1 -1
- package/clis/bilibili/comment.js +1 -1
- package/clis/bilibili/comments.js +1 -1
- package/clis/bilibili/dm-list.js +1 -1
- package/clis/bilibili/dm-read.js +1 -1
- package/clis/bilibili/favorite-add.js +1 -1
- package/clis/bilibili/me.js +1 -1
- package/clis/bilibili/search.js +1 -1
- package/clis/bilibili/stats.js +1 -1
- package/clis/bilibili/user-videos.js +1 -1
- package/clis/bilibili/utils.js +7 -7
- package/clis/cnblogs/article.js +1 -1
- package/clis/csdn/stats.js +7 -13
- package/clis/devto/publish.js +20 -4
- package/clis/douyin/_shared/work-list.js +1 -0
- package/clis/douyin/publish.js +9 -8
- package/clis/douyin/stats.js +1 -1
- package/clis/douyin/videos.js +1 -1
- package/clis/jike/comments.js +2 -1
- package/clis/jike/like.js +1 -1
- package/clis/jike/notifications.js +2 -115
- package/clis/jike/post.js +2 -2
- package/clis/jike/topic.js +1 -1
- package/clis/jike/topics.js +3 -0
- package/clis/nowcoder/auth.js +1 -24
- package/clis/nowcoder/comment.js +1 -1
- package/clis/nowcoder/comments.js +1 -1
- package/clis/nowcoder/delete.js +1 -1
- package/clis/nowcoder/detail.js +1 -48
- package/clis/nowcoder/dm-list.js +1 -1
- package/clis/nowcoder/dm-read.js +1 -1
- package/clis/nowcoder/dm-send.js +1 -1
- package/clis/nowcoder/experience.js +1 -22
- package/clis/nowcoder/inbox.js +1 -1
- package/clis/nowcoder/like-status.js +1 -0
- package/clis/nowcoder/like.js +1 -1
- package/clis/nowcoder/my.js +1 -1
- package/clis/nowcoder/notifications.js +1 -16
- package/clis/nowcoder/post.js +1 -1
- package/clis/nowcoder/recommend.js +1 -1
- package/clis/nowcoder/search.js +1 -32
- package/clis/nowcoder/utils.js +38 -44
- package/clis/qiita/publish.js +2 -2
- package/clis/quora/__fixtures__/dom.html +24 -0
- package/clis/quora/answer-detail.js +1 -1
- package/clis/quora/question.js +25 -1
- package/clis/quora/search.js +1 -1
- package/clis/quora/shared.js +18 -17
- package/clis/quora/upvote.js +5 -13
- package/clis/reddit/comment.js +25 -25
- package/clis/reddit/reply.js +7 -5
- package/clis/reddit/search.js +1 -51
- package/clis/reddit/shared.js +13 -0
- package/clis/reddit/subreddit-info.js +10 -4
- package/clis/reddit/subreddit-rules.js +1 -0
- package/clis/reddit/subreddit.js +1 -52
- package/clis/reddit/upvote.js +33 -10
- package/clis/reddit/user-comments.js +1 -20
- package/clis/reddit/user-posts.js +1 -17
- package/clis/twitter/post.js +63 -39
- package/clis/wechat-channels/auth.js +34 -31
- package/clis/wechat-channels/publish.js +295 -130
- package/clis/xianyu/watch.js +43 -1
- package/clis/xiaohongshu/_shared/note-manager.js +37 -0
- package/clis/xiaohongshu/creator-notes.js +9 -43
- package/clis/xiaohongshu/delete-note.js +7 -19
- package/clis/xiaohongshu/draft-delete.js +2 -2
- package/clis/xiaohongshu/publish.js +34 -43
- package/clis/xiaohongshu/search.js +18 -3
- package/clis/youtube/publish.js +11 -11
- package/clis/zhihu/answer-comments.js +2 -11
- package/clis/zhihu/answer-detail.js +1 -11
- package/clis/zhihu/comment.js +4 -4
- package/clis/zhihu/like.js +17 -3
- package/clis/zhihu/question.js +20 -6
- package/clis/zhihu/read-shared.js +26 -0
- package/clis/zhihu/recommend.js +1 -7
- package/clis/zhihu/search.js +1 -11
- package/dist/src/browser/base-page.d.ts +41 -1
- package/dist/src/browser/base-page.js +29 -16
- package/dist/src/browser/find.d.ts +31 -4
- package/dist/src/browser/find.js +76 -23
- package/dist/src/browser/utils.d.ts +30 -0
- package/dist/src/browser/utils.js +7 -1
- package/dist/src/cli.js +22 -22
- package/dist/src/commanderAdapter.d.ts +14 -0
- package/dist/src/commanderAdapter.js +4 -3
- package/dist/src/doctor.d.ts +7 -0
- package/dist/src/doctor.js +9 -9
- package/dist/src/errors.d.ts +6 -0
- package/dist/src/errors.js +1 -1
- package/dist/src/execution.d.ts +1 -0
- package/dist/src/execution.js +2 -2
- package/dist/src/main.js +6 -6
- package/dist/src/types.d.ts +28 -0
- package/package.json +1 -1
package/clis/twitter/post.js
CHANGED
|
@@ -1,33 +1,45 @@
|
|
|
1
|
-
import*as
|
|
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
|
|
4
|
-
const
|
|
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
|
-
})()`)}
|
|
9
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
if (box && normalize(
|
|
16
|
-
await new Promise(r => setTimeout(r, ${JSON.stringify(
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
-
|
|
27
|
-
const
|
|
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(
|
|
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 {
|
|
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
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
for (let i = 0; i < ${JSON.stringify(
|
|
47
|
-
await new Promise(r => setTimeout(r, ${JSON.stringify(
|
|
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
|
|
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 (${
|
|
62
|
-
})()`)}async function
|
|
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
|
-
|
|
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(
|
|
72
|
-
|
|
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(
|
|
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
|
|
82
|
-
const expected = ${JSON.stringify(
|
|
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(
|
|
100
|
-
await new Promise(r => setTimeout(r, ${JSON.stringify(
|
|
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
|
|
121
|
-
const r = await fetch(${JSON.stringify(
|
|
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: ${
|
|
147
|
+
headers: ${l},
|
|
124
148
|
credentials: 'include',
|
|
125
|
-
body: ${JSON.stringify(
|
|
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(
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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};
|