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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as te from"node:fs";import*as D from"node:path";import{cli as re,Strategy as ne}from"@jackwener/opencli/registry";import{ArgumentError as f,AuthRequiredError as ie,CommandExecutionError as c}from"@jackwener/opencli/errors";import{injectInputFiles as ae}from"../_shared/file-inject.js";const M="https://channels.weixin.qq.com/platform/post/create",le=[".post-short-title-wrap input",'input[placeholder*="短标题"]','input[placeholder*="填写短标题"]','input.weui-desktop-form__input[placeholder*="短标题"]',"input.weui-desktop-form__input"],oe=[".post-desc-box .input-editor",'div[contenteditable][data-placeholder="添加描述"]','div.input-editor[contenteditable=""][data-placeholder="添加描述"]','div[data-placeholder*="描述"][contenteditable]',"div.input-editor[contenteditable]"],se=["span.add-icon.weui-icon-outlined-add","div.upload-content",".finder-video-upload-btn"],ue=new Set([".mp4",".mov",".avi",".webm"]),ce=['.post-upload-wrap input[type="file"]','input[type="file"][accept*="video"]','input[type="file"]'],A="https://channels.weixin.qq.com/platform/post/list",j=/^[\u2103\u4E00-\u9FA5A-Za-z0-9《》""“”::+??%\s]+$/,q=16,F=6;function R(e){if(!e)return;if(e.length>q)throw new f(`短标题超过 ${q} 字限制(当前 ${e.length} 字)`);if(e.length<F)throw new f(`短标题至少 ${F} 个字(当前 ${e.length} 字)`);if(!j.test(e)){const t=Array.from(new Set(Array.from(e).filter((i)=>!j.test(i)))).join(" ");throw new f(`短标题包含平台不接受的字符: ${t}`,"短标题只能用中文、英文字母、数字、空格,符号仅支持书名号、引号、冒号、加号、问号、百分号、摄氏度;逗号请改用空格")}}function de(e){if(e&&typeof e==="object"&&"data"in e&&"session"in e)return e.data;return e}async function d(e,t){return de(await e.evaluate(t))}function P(e,t,i){const n=D.resolve(String(e));let r;try{r=te.statSync(n)}catch{throw new f(`${t}文件不存在: ${n}`)}if(!r.isFile())throw new f(`${t}路径不是文件: ${n}`);const a=D.extname(n).toLowerCase();if(!i.has(a))throw new f(`不支持的${t}格式: ${a}(支持 ${Array.from(i).join("/")})`);return n}function Q(e){const t=e==null||e===""?600:Number(e);if(!Number.isInteger(t)||t<30)throw new f("--timeout must be an integer >= 30 seconds");return t}function L(e){if(!e)return null;const t=typeof e==="number"?new Date(e<1000000000000?e*1000:e):new Date(String(e));if(Number.isNaN(t.getTime()))throw new f(`无法解析定时时间: ${e}`);if(t.getTime()<=Date.now())throw new f("定时时间必须晚于当前时间");t.setSeconds(0,0);return t}function O(e){return e===!0||e==="true"||e==="1"||e===1}function fe(e,t){const i=e-Date.now();if(i<=0)throw new c(`${t}超时,请增加 --timeout 后重试`);return i}function U({isDraft:e,finalUrl:t,successMsg:i}){const n=String(i||"");if(e)return/已保存|暂存成功|保存成功/.test(n);if(/已发表|发布成功|发表成功|审核中/.test(n))return!0;const r=String(t||"");return/\/platform\/post\/list\b/.test(r)}const p=`
|
|
2
2
|
function wujieRoot() {
|
|
3
3
|
var w = document.querySelector('wujie-app');
|
|
4
4
|
return (w && w.shadowRoot) || null;
|
|
@@ -25,10 +25,74 @@ import*as Q from"node:fs";import*as k from"node:path";import{cli as j,Strategy a
|
|
|
25
25
|
var rect = el.getBoundingClientRect();
|
|
26
26
|
return rect.width > 0 && rect.height > 0;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
`,E=`
|
|
29
|
+
function vmOf(el) {
|
|
30
|
+
while (el) {
|
|
31
|
+
if (el.__vue__) return el.__vue__;
|
|
32
|
+
el = el.parentNode || el.host;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function findVm(vm, pred) {
|
|
37
|
+
for (var guard = 0; vm && guard < 80; guard++) {
|
|
38
|
+
try { if (pred(vm)) return vm; } catch (e) {}
|
|
39
|
+
vm = vm.$parent;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function safe(fn) { try { var v = fn(); return v === undefined ? null : v; } catch (e) { return null; } }
|
|
44
|
+
function pageVm() {
|
|
45
|
+
return findVm(vmOf(deepQuery('.form-btns')), function (v) {
|
|
46
|
+
return v.postValidState && typeof v.postValidState === 'object';
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function scheduleVm() {
|
|
50
|
+
var el = deepQuery('.post-time-wrap');
|
|
51
|
+
return (el && el.__vue__) || null;
|
|
52
|
+
}
|
|
53
|
+
function formVm(pv, sv) {
|
|
54
|
+
var viaRef = safe(function () { return pv.$refs.postCreate; });
|
|
55
|
+
if (viaRef && viaRef.originState) return viaRef;
|
|
56
|
+
return findVm(sv, function (v) { return v.originState && typeof v.originState === 'object'; });
|
|
57
|
+
}
|
|
58
|
+
function readFormState() {
|
|
59
|
+
var pv = pageVm();
|
|
60
|
+
var sv = scheduleVm();
|
|
61
|
+
var fv = formVm(pv, sv);
|
|
62
|
+
var uploadEl = deepQuery('.post-upload-wrap');
|
|
63
|
+
var uv = (uploadEl && uploadEl.__vue__) || null;
|
|
64
|
+
var titleErrEl = deepQuery('.post-short-title-wrap .error-title');
|
|
65
|
+
var files = safe(function () {
|
|
66
|
+
return Array.isArray(uv.value)
|
|
67
|
+
? uv.value.map(function (f) { return { status: String(f.status || ''), percent: Number(f.percent) || 0 }; })
|
|
68
|
+
: null;
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
hasPage: !!pv,
|
|
72
|
+
canPost: pv ? safe(function () { return !!pv.canPost; }) : null,
|
|
73
|
+
formValid: pv ? safe(function () { return !!pv.postValidState.canPost; }) : null,
|
|
74
|
+
disabledTip: pv ? safe(function () { return String(pv.disabledTip || pv.postValidState.disablePostTip || pv.postValidState.videoInVaildTip || ''); }) : null,
|
|
75
|
+
isPostByTime: pv ? safe(function () { return !!pv.isPostByTime; }) : null,
|
|
76
|
+
isEffectiveTimeValid: pv ? safe(function () { return !!pv.isEffectiveTimeValid; }) : null,
|
|
77
|
+
isPosting: pv ? safe(function () { return !!pv.isPosting; }) : null,
|
|
78
|
+
isCaching: pv ? safe(function () { return !!pv.isCaching; }) : null,
|
|
79
|
+
originalIntercept: pv ? safe(function () { return !!pv.visibleOriginalIntercept; }) : null,
|
|
80
|
+
draftObjectId: pv ? safe(function () { return String(pv.draftStore.objectId || ''); }) : null,
|
|
81
|
+
files: files,
|
|
82
|
+
schedule: sv ? {
|
|
83
|
+
checked: safe(function () { return Number(sv.isChecked); }),
|
|
84
|
+
time: safe(function () { return Number(sv.time); }),
|
|
85
|
+
minTime: safe(function () { return Number(sv.minTime); }),
|
|
86
|
+
errMsg: safe(function () { return String(sv.errMsg || ''); }),
|
|
87
|
+
} : null,
|
|
88
|
+
effectiveTime: fv ? safe(function () { return fv.originState.effectiveTime; }) : undefined,
|
|
89
|
+
titleError: titleErrEl ? (titleErrEl.innerText || titleErrEl.textContent || '').trim() : '',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
`;async function x(e){return d(e,`(() => { ${p} ${E} return readFormState(); })()`)}function H(e){if(!e)return{known:!1,seconds:null};const t=e.effectiveTime,i=t===null||typeof t==="number",n=i&&t?t:null,r=e.schedule,a=!!r&&Number.isFinite(r.time);let l=null;if(a&&r.checked&&r.time){const o=Number.isFinite(r.minTime)?Math.max(r.time,r.minTime):r.time;l=Math.floor(o/1000)}if(i&&a&&n!==l)return{known:!0,conflict:!0,seconds:n??l,formSeconds:n,pickerSeconds:l};if(i)return{known:!0,seconds:n};if(a)return{known:!0,seconds:l};return{known:!1,seconds:null}}function g(e){const t=new Date(e*1000),i=(n)=>String(n).padStart(2,"0");return`${t.getFullYear()}-${i(t.getMonth()+1)}-${i(t.getDate())} ${i(t.getHours())}:${i(t.getMinutes())}`}function N(e,{targetSeconds:t,isDraft:i}){if(e?.titleError)return`平台拒绝了短标题: ${e.titleError}`;if(Array.isArray(e?.files)){if(e.files.length===0)return"视频上传失败:上传区是空的,文件没有进入页面的上传队列";if(e.files.some((a)=>a.status==="error"))return"视频上传失败:页面把文件标记为上传出错";const r=e.files.filter((a)=>a.status!=="done");if(r.length)return`视频还没上传完成(状态 ${r.map((a)=>`${a.status||"未知"} ${a.percent}%`).join(", ")})`}const n=H(e);if(n.conflict){const r=(a)=>a?g(a):"不定时";return`表单内部的定时值互相矛盾(表单 ${r(n.formSeconds)} / 时间选择器 ${r(n.pickerSeconds)}),为避免错时发布已中止`}if(t){if(!n.known)return"无法回读表单里的定时发表时间,为避免错时发布已中止";if(n.seconds!==t)return`表单里的定时发表时间与目标不一致:目标 ${g(t)},表单实际 ${n.seconds?g(n.seconds):"不定时"},为避免错时发布已中止`;if(e.isEffectiveTimeValid===!1)return"页面判定定时发表时间不合法,已中止"}else if(n.known&&n.seconds)return`表单处于定时发表状态(${g(n.seconds)}),但本次没有指定 --schedule,已中止`;if(i&&e?.isPostByTime)return"定时发表状态下平台不允许保存草稿";return null}async function pe(e){return await d(e,`
|
|
29
93
|
(() => {
|
|
30
|
-
${
|
|
31
|
-
var sels = ${JSON.stringify(
|
|
94
|
+
${p}
|
|
95
|
+
var sels = ${JSON.stringify(se)};
|
|
32
96
|
for (var i = 0; i < sels.length; i++) {
|
|
33
97
|
var el = deepQuery(sels[i]);
|
|
34
98
|
if (el && isVisible(el)) {
|
|
@@ -38,42 +102,72 @@ import*as Q from"node:fs";import*as k from"node:path";import{cli as j,Strategy a
|
|
|
38
102
|
}
|
|
39
103
|
return { ok: false };
|
|
40
104
|
})()
|
|
41
|
-
`)}async function
|
|
105
|
+
`)}async function me(e,t=15000){const i=500,n=Math.max(1,Math.ceil(t/i));for(let r=0;r<n;r++){if(await d(e,`
|
|
42
106
|
(() => {
|
|
43
|
-
${
|
|
107
|
+
${p}
|
|
44
108
|
var inputSels = ['input[type="file"][accept*="video"]', 'input[type="file"]'];
|
|
45
109
|
for (var i = 0; i < inputSels.length; i++) {
|
|
46
110
|
if (deepQuery(inputSels[i])) return true;
|
|
47
111
|
}
|
|
48
112
|
return false;
|
|
49
113
|
})()
|
|
50
|
-
`))return!0;if(r<
|
|
51
|
-
|
|
114
|
+
`))return!0;if(r<n-1)await e.wait({time:i/1000})}return!1}const B=`((sel) => {
|
|
115
|
+
${p}
|
|
116
|
+
var real = deepQuery(sel);
|
|
117
|
+
if (!real) return null;
|
|
118
|
+
var win = (real.ownerDocument && real.ownerDocument.defaultView) || window;
|
|
119
|
+
return {
|
|
120
|
+
// 赋值结果记在 window.__ppWxInject 上,由 Node 侧读回核对:上传组件在 change 之后会重建
|
|
121
|
+
// input(files 随之清空),事后再看 input.files 是看不出成没成的。这里不往外抛,
|
|
122
|
+
// 否则注入通道会把赋值失败当成「被 Proxy 包过」而兜底改写代持对象,反而报成功。
|
|
123
|
+
set files(list) {
|
|
124
|
+
var report = { count: 0, realm: false, error: '' };
|
|
125
|
+
try {
|
|
126
|
+
var dt = new (win.DataTransfer || DataTransfer)();
|
|
127
|
+
for (var i = 0; i < list.length; i++) {
|
|
128
|
+
var f = list[i];
|
|
129
|
+
dt.items.add(new win.File([f], f.name, { type: f.type, lastModified: f.lastModified }));
|
|
130
|
+
}
|
|
131
|
+
real.files = dt.files;
|
|
132
|
+
report.count = real.files.length;
|
|
133
|
+
report.realm = report.count > 0 && real.files[0] instanceof win.File;
|
|
134
|
+
} catch (e) {
|
|
135
|
+
report.error = String((e && e.message) || e);
|
|
136
|
+
}
|
|
137
|
+
window.__ppWxInject = report;
|
|
138
|
+
},
|
|
139
|
+
get files() { return real.files; },
|
|
140
|
+
dispatchEvent: function (ev) {
|
|
141
|
+
return real.dispatchEvent(new win.Event(ev.type, { bubbles: true }));
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
})`;async function he(e,t){await pe(e);await e.wait({time:1});await me(e);let i;for(const n of ce)try{await ae(e,n,[t],{findInputJs:B});const r=await d(e,"(() => { var r = window.__ppWxInject || null; delete window.__ppWxInject; return r; })()");if(!r||r.error||r.count!==1||!r.realm)throw new c(`文件没能交给页面的上传控件: ${r?.error||`count=${r?.count??"未知"} realm=${r?.realm??"未知"}`}`);return}catch(r){i=r;if(!(r instanceof Error?r.message:String(r)).includes("未找到文件上传输入框"))break}await e.screenshot({path:"/tmp/wechat-channels_publish_upload_debug.png"});throw new c(`视频文件注入失败: ${i instanceof Error?i.message:String(i)}
|
|
145
|
+
截图已保存到 /tmp/wechat-channels_publish_upload_debug.png`)}function J(e){if(!Array.isArray(e))return"unknown";if(e.length===0)return"empty";if(e.some((t)=>t.status==="error"))return"error";return e.every((t)=>t.status==="done")?"done":"pending"}async function we(e,t,i=180000){const n=3000,r=Math.ceil(i/n),a=Math.ceil(20000/n);let l=0;for(let o=0;o<r;o++){let s;try{s=await d(e,`
|
|
52
146
|
((fileName) => {
|
|
53
|
-
${
|
|
147
|
+
${p}
|
|
148
|
+
${E}
|
|
149
|
+
var files = readFormState().files;
|
|
150
|
+
if (Array.isArray(files)) return { files: files };
|
|
151
|
+
|
|
152
|
+
// 兜底:读不到上传组件状态时才看 DOM。隐藏的 <video>(页面用来探测时长)不算预览。
|
|
54
153
|
var root = wujieRoot() || document;
|
|
55
154
|
var bodyText = (root.innerText || root.textContent || '').trim();
|
|
56
155
|
var uploading = deepQuery('[class*="upload"][class*="progress"]') ||
|
|
57
156
|
deepQuery('[class*="uploading"]') ||
|
|
58
157
|
deepQuery('[class*="transcoding"]') ||
|
|
59
158
|
deepQuery('.weui-desktop-upload__status');
|
|
60
|
-
|
|
61
|
-
var preview =
|
|
62
|
-
|
|
63
|
-
deepQuery('[class*="video-thumb"]') ||
|
|
64
|
-
deepQuery('[class*="video"][class*="preview"]');
|
|
65
|
-
|
|
159
|
+
var previews = deepQueryAll('video').concat(deepQueryAll('[class*="preview-video"]'), deepQueryAll('[class*="video-thumb"]'));
|
|
160
|
+
var preview = false;
|
|
161
|
+
for (var p = 0; p < previews.length; p++) { if (isVisible(previews[p])) { preview = true; break; } }
|
|
66
162
|
var uploadFailed = deepQuery('[class*="upload-fail"]') || deepQuery('[class*="upload-error"]');
|
|
67
163
|
if (uploadFailed || /上传失败|转码失败|处理失败/.test(bodyText)) return { done: false, failed: true };
|
|
68
|
-
|
|
69
164
|
var hasFileEvidence = fileName && bodyText.indexOf(fileName) >= 0;
|
|
70
|
-
|
|
71
|
-
return { done: !uploading && (!!preview || hasFileEvidence || hasSuccessText), failed: false };
|
|
165
|
+
return { done: !uploading && (preview || hasFileEvidence), failed: false };
|
|
72
166
|
})(${JSON.stringify(t)})
|
|
73
|
-
`)}catch(
|
|
74
|
-
`);await e.wait({time:
|
|
167
|
+
`)}catch(u){const h=u instanceof Error?u.message:String(u);if(h.includes("ECONNRESET")||h.includes("fetch failed")){process.stderr.write(` [retry] bridge reconnecting after page re-render (${o+1}/${r})...
|
|
168
|
+
`);await e.wait({time:n/1000});continue}throw u}const m=J(s?.files);if(m==="error"||s?.failed){await e.screenshot({path:"/tmp/wechat-channels_publish_upload_debug.png"});throw new c("视频上传失败:页面把文件标记为上传出错,请检查文件格式和网络连接","截图: /tmp/wechat-channels_publish_upload_debug.png。本次没有提交任何内容,可以直接重试")}if(m==="done"||m==="unknown"&&s?.done)return;if(m==="empty"){l+=1;if(l>=a){await e.screenshot({path:"/tmp/wechat-channels_publish_upload_debug.png"});throw new c("视频上传失败:文件已注入但页面上传区仍为空,页面没有收下这个文件","截图: /tmp/wechat-channels_publish_upload_debug.png。本次没有提交任何内容,可以直接重试;反复出现说明视频号上传控件改版")}}else l=0;await e.wait({time:n/1000})}throw new c(`视频上传/转码超时(${Math.ceil(i/1000)}秒),请检查网络或稍后重试`)}async function Y(e,t,i,n){const r=30;let a;for(let l=0;l<r;l++){a=await d(e,`
|
|
75
169
|
(function(selectors, text) {
|
|
76
|
-
${
|
|
170
|
+
${p}
|
|
77
171
|
|
|
78
172
|
var el = null;
|
|
79
173
|
var foundSel = null;
|
|
@@ -117,144 +211,215 @@ import*as Q from"node:fs";import*as k from"node:path";import{cli as j,Strategy a
|
|
|
117
211
|
var actual = el.isContentEditable ? (el.innerText || el.textContent || '') : (el.value || '');
|
|
118
212
|
el.blur();
|
|
119
213
|
return { ok: actual.indexOf(text) >= 0, sel: foundSel, actual: actual };
|
|
120
|
-
})(${JSON.stringify(t)}, ${JSON.stringify(
|
|
121
|
-
`);if(a?.ok)break;if(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
214
|
+
})(${JSON.stringify(t)}, ${JSON.stringify(i)})
|
|
215
|
+
`);if(a?.ok)break;if(l<r-1)await e.wait({time:0.5})}if(!a?.ok){await e.screenshot({path:`/tmp/wechat-channels_publish_${n}_debug.png`});throw new c(`找不到 ${n} 输入框,截图已保存到 /tmp/wechat-channels_publish_${n}_debug.png`)}}const G=`
|
|
216
|
+
(async function(TY, TM, TD, TH, TMin, TARGET_MS) {
|
|
217
|
+
${p}
|
|
218
|
+
${E}
|
|
219
|
+
function sleep(ms) { return new Promise(function(r) { setTimeout(r, ms); }); }
|
|
220
|
+
function click(el) {
|
|
221
|
+
el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window }));
|
|
222
|
+
el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window }));
|
|
223
|
+
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
|
|
224
|
+
}
|
|
225
|
+
var pad = function(n) { return String(n).padStart(2, '0'); };
|
|
226
|
+
|
|
227
|
+
// 0. 浏览器与命令行必须在同一时区,否则按钟点点出来的时刻不是目标时刻。
|
|
228
|
+
var localTarget = new Date(TY, TM - 1, TD, TH, TMin, 0, 0).getTime();
|
|
229
|
+
if (localTarget !== TARGET_MS) {
|
|
230
|
+
return { ok: false, reason: 'timezone-mismatch', browser: localTarget, target: TARGET_MS };
|
|
231
|
+
}
|
|
131
232
|
|
|
132
|
-
|
|
233
|
+
// 1. 选中「定时」:定时区里 value=1 的单选框(0 是不定时)。不按文字找。
|
|
234
|
+
function scheduleChecked() {
|
|
235
|
+
var sv = scheduleVm();
|
|
236
|
+
return sv ? Number(sv.isChecked) === 1 : !!deepQuery('dl.weui-desktop-picker__date');
|
|
237
|
+
}
|
|
238
|
+
if (!scheduleChecked()) {
|
|
239
|
+
var wrap = deepQuery('.post-time-wrap');
|
|
240
|
+
var radios = wrap ? wrap.querySelectorAll('input[type="radio"]') : [];
|
|
241
|
+
for (var ri = 0; ri < radios.length; ri++) {
|
|
242
|
+
if (String(radios[ri].value) === '1') { radios[ri].click(); break; }
|
|
243
|
+
}
|
|
244
|
+
await sleep(600);
|
|
245
|
+
}
|
|
246
|
+
if (!scheduleChecked()) {
|
|
247
|
+
// 兜底:旧结构下按文字找(exact match — "不定时" also contains "定时")。
|
|
133
248
|
var labels = deepQueryAll('label');
|
|
134
|
-
var radioOk = false;
|
|
135
249
|
for (var i = 0; i < labels.length; i++) {
|
|
136
250
|
if ((labels[i].innerText || labels[i].textContent || '').trim() === '定时') {
|
|
137
|
-
click(labels[i]);
|
|
138
251
|
labels[i].click();
|
|
139
|
-
radioOk = true;
|
|
140
252
|
break;
|
|
141
253
|
}
|
|
142
254
|
}
|
|
143
|
-
if (!radioOk) return { ok: false, reason: 'no-radio' };
|
|
144
255
|
await sleep(600);
|
|
256
|
+
}
|
|
257
|
+
if (!scheduleChecked()) return { ok: false, reason: 'no-radio' };
|
|
145
258
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
259
|
+
// 2. Locate the date <dl> and open its panel.
|
|
260
|
+
var scheduleWrap = deepQuery('.post-time-wrap');
|
|
261
|
+
var dateDl = (scheduleWrap && scheduleWrap.querySelector('dl.weui-desktop-picker__date')) ||
|
|
262
|
+
deepQuery('dl.weui-desktop-picker__date');
|
|
263
|
+
if (!dateDl) return { ok: false, reason: 'no-date-dl' };
|
|
264
|
+
var dateDt = dateDl.querySelector('dt.weui-desktop-picker__dt');
|
|
265
|
+
if (!dateDt) return { ok: false, reason: 'no-date-dt' };
|
|
266
|
+
click(dateDt);
|
|
267
|
+
await sleep(500);
|
|
153
268
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
269
|
+
// 3. Navigate months until the panel labels read TY TM(只取数字,不依赖「年/月」字样)。
|
|
270
|
+
var reached = false;
|
|
271
|
+
for (var nav = 0; nav < 24; nav++) {
|
|
272
|
+
var lbls = Array.prototype.map.call(
|
|
273
|
+
dateDl.querySelectorAll('.weui-desktop-picker__panel__label'),
|
|
274
|
+
function(l) { return (l.innerText || '').trim(); }
|
|
275
|
+
);
|
|
276
|
+
var ma = lbls.join(' ').match(/(\\d{4})\\D+(\\d{1,2})/);
|
|
277
|
+
if (!ma) return { ok: false, reason: 'label-parse', labels: lbls.join('|') };
|
|
278
|
+
var cy = parseInt(ma[1], 10), cm = parseInt(ma[2], 10);
|
|
279
|
+
if (cy === TY && cm === TM) { reached = true; break; }
|
|
280
|
+
var goNext = (cy < TY) || (cy === TY && cm < TM);
|
|
281
|
+
var arrow = goNext
|
|
282
|
+
? dateDl.querySelector('.weui-desktop-btn__icon__right')
|
|
283
|
+
: dateDl.querySelector('.weui-desktop-btn__icon__left');
|
|
284
|
+
if (!arrow) return { ok: false, reason: 'no-arrow', cy: cy, cm: cm };
|
|
285
|
+
click(arrow);
|
|
286
|
+
await sleep(350);
|
|
287
|
+
}
|
|
288
|
+
if (!reached) return { ok: false, reason: 'month-not-reached' };
|
|
174
289
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
290
|
+
// 4. Click the target day — an <a> in the body that is neither faded
|
|
291
|
+
// (other month) nor disabled (past).
|
|
292
|
+
var bd = dateDl.querySelector('.weui-desktop-picker__panel__bd');
|
|
293
|
+
var anchors = bd ? Array.prototype.slice.call(bd.querySelectorAll('a')) : [];
|
|
294
|
+
var dayEl = null;
|
|
295
|
+
for (var k = 0; k < anchors.length; k++) {
|
|
296
|
+
var t = (anchors[k].innerText || anchors[k].textContent || '').trim();
|
|
297
|
+
var cls = anchors[k].className || '';
|
|
298
|
+
if (t === String(TD) && cls.indexOf('faded') < 0 && cls.indexOf('disabled') < 0) {
|
|
299
|
+
dayEl = anchors[k];
|
|
300
|
+
break;
|
|
187
301
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
302
|
+
}
|
|
303
|
+
if (!dayEl) return { ok: false, reason: 'day-disabled-or-missing', day: TD };
|
|
304
|
+
click(dayEl);
|
|
305
|
+
await sleep(500);
|
|
191
306
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
307
|
+
// 5. Open the time <dl> and pick hour + minute from the <li> columns.
|
|
308
|
+
var timeDl = dateDl.querySelector('dl.weui-desktop-picker__time') || deepQuery('dl.weui-desktop-picker__time');
|
|
309
|
+
if (!timeDl) return { ok: false, reason: 'no-time-dl' };
|
|
310
|
+
var timeVm = timeDl.__vue__ || null;
|
|
311
|
+
var timeDt = timeDl.querySelector('dt.weui-desktop-picker__dt');
|
|
312
|
+
if (timeDt) click(timeDt);
|
|
313
|
+
await sleep(500);
|
|
198
314
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
315
|
+
function pickFromColumn(ol, value) {
|
|
316
|
+
if (!ol) return false;
|
|
317
|
+
var lis = ol.querySelectorAll('li');
|
|
318
|
+
for (var i = 0; i < lis.length; i++) {
|
|
319
|
+
if ((lis[i].innerText || '').trim() === value &&
|
|
320
|
+
(lis[i].className || '').indexOf('disabled') < 0) {
|
|
321
|
+
click(lis[i]);
|
|
322
|
+
return true;
|
|
208
323
|
}
|
|
209
|
-
return false;
|
|
210
324
|
}
|
|
211
|
-
|
|
212
|
-
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
var hourOk = pickFromColumn(timeDl.querySelector('ol.weui-desktop-picker__time__hour'), pad(TH));
|
|
328
|
+
if (!hourOk) return { ok: false, reason: 'hour-disabled', hour: TH };
|
|
329
|
+
await sleep(300);
|
|
330
|
+
var minOk = pickFromColumn(timeDl.querySelector('ol.weui-desktop-picker__time__minute'), pad(TMin));
|
|
331
|
+
if (!minOk) return { ok: false, reason: 'minute-disabled', minute: TMin };
|
|
332
|
+
await sleep(300);
|
|
333
|
+
|
|
334
|
+
// 6. 收起时间面板,让日期组件把选中的时刻真正交给表单。
|
|
335
|
+
var dateVm = dateDl.__vue__ || null;
|
|
336
|
+
var commit = [];
|
|
337
|
+
if (timeVm && typeof timeVm.hidePicker === 'function') {
|
|
338
|
+
if (timeVm.isShow === false && typeof timeVm.showPicker === 'function') timeVm.showPicker();
|
|
339
|
+
timeVm.hidePicker();
|
|
340
|
+
commit.push('hidePicker');
|
|
213
341
|
await sleep(300);
|
|
214
|
-
|
|
215
|
-
|
|
342
|
+
}
|
|
343
|
+
function pickOf() {
|
|
344
|
+
var p = dateVm ? Number(dateVm.pick) : NaN;
|
|
345
|
+
return isFinite(p) ? p : null;
|
|
346
|
+
}
|
|
347
|
+
function committed() {
|
|
348
|
+
var st = readFormState();
|
|
349
|
+
var sch = st.schedule;
|
|
350
|
+
return !!sch && Math.floor(sch.time / 1000) === Math.floor(TARGET_MS / 1000);
|
|
351
|
+
}
|
|
352
|
+
// 组件内部已是目标时刻但表单还没收到 → 走日期组件自己的时间确认出口再交一次。
|
|
353
|
+
if (!committed() && dateVm && typeof dateVm.handleTimeSelect === 'function' &&
|
|
354
|
+
pickOf() !== null && Math.floor(pickOf() / 1000) === Math.floor(TARGET_MS / 1000)) {
|
|
355
|
+
dateVm.handleTimeSelect();
|
|
356
|
+
commit.push('handleTimeSelect');
|
|
216
357
|
await sleep(300);
|
|
358
|
+
}
|
|
359
|
+
// 收起日期面板,免得挡住后面的按钮。
|
|
360
|
+
if (dateVm && typeof dateVm.handleClickOutside === 'function') dateVm.handleClickOutside();
|
|
361
|
+
await sleep(200);
|
|
217
362
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
`)}
|
|
224
|
-
|
|
225
|
-
|
|
363
|
+
// 7. 回读:显示框、组件选中时间戳、表单状态。
|
|
364
|
+
var inp = dateDl.querySelector('dt input') || deepQuery('input[placeholder*="发表时间"]');
|
|
365
|
+
return { ok: true, value: inp ? inp.value : null, pick: pickOf(), commit: commit.join('+'), state: readFormState() };
|
|
366
|
+
})
|
|
367
|
+
`;async function ve(e,t){const i=t.getFullYear(),n=t.getMonth()+1,r=t.getDate(),a=t.getHours(),l=t.getMinutes(),o=(v)=>String(v).padStart(2,"0"),s=t.getTime(),m=Math.floor(s/1000),u=await d(e,`${G}(${i}, ${n}, ${r}, ${a}, ${l}, ${s})`);if(!u?.ok){await e.screenshot({path:"/tmp/wechat-channels_schedule_debug.png"});const v=u?.reason?String(u.reason):"empty picker result";if(v==="timezone-mismatch")throw new c("定时设置失败:浏览器与命令行所在时区不一致,无法按钟点可靠地选中目标时刻,已中止");throw new c(`定时设置失败 (${v}),截图: /tmp/wechat-channels_schedule_debug.png`)}const h=`${i}-${o(n)}-${o(r)} ${o(a)}:${o(l)}`,w=[];if(!String(u.value||"").includes(h))w.push(`显示框=${u.value||"空"}`);if(u.pick!=null&&Math.floor(u.pick/1000)!==m)w.push(`时间选择器选中=${g(Math.floor(u.pick/1000))}`);const y=N({...u.state,files:null,titleError:""},{targetSeconds:m,isDraft:!1});if(y)w.push(y);if(w.length){await e.screenshot({path:"/tmp/wechat-channels_schedule_debug.png"});throw new c(`定时设置未验证成功: 目标=${h};${w.join(";")}`,"本次没有提交任何内容。截图: /tmp/wechat-channels_schedule_debug.png")}process.stderr.write(` 定时设置完成: ${u.value||h}(表单回读一致)
|
|
368
|
+
`)}const W=`
|
|
369
|
+
(function(isDraft, labels) {
|
|
370
|
+
${p}
|
|
371
|
+
function isDisabled(btn) {
|
|
372
|
+
return btn.disabled || btn.getAttribute('disabled') !== null ||
|
|
373
|
+
btn.classList.contains('weui-desktop-btn_disabled');
|
|
374
|
+
}
|
|
375
|
+
var target = null;
|
|
376
|
+
var how = '';
|
|
377
|
+
var box = deepQuery('.form-btns');
|
|
378
|
+
if (box) {
|
|
379
|
+
var groups = [[]];
|
|
380
|
+
for (var c = 0; c < box.children.length; c++) {
|
|
381
|
+
var kid = box.children[c];
|
|
382
|
+
if (kid.classList.contains('gap')) groups.push([]);
|
|
383
|
+
else groups[groups.length - 1].push(kid);
|
|
384
|
+
}
|
|
385
|
+
if (groups.length >= 2) {
|
|
386
|
+
var group = isDraft ? groups[0] : groups[groups.length - 1];
|
|
387
|
+
for (var g = 0; g < group.length && !target; g++) {
|
|
388
|
+
target = group[g].tagName === 'BUTTON' ? group[g] : group[g].querySelector('button');
|
|
389
|
+
}
|
|
390
|
+
if (target && /primary/.test(target.className || '') === isDraft) target = null;
|
|
391
|
+
if (target) how = 'structure';
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (!target) {
|
|
226
395
|
var btns = deepQueryAll('button');
|
|
227
|
-
for (var i = 0; i < btns.length; i++) {
|
|
228
|
-
var
|
|
229
|
-
var
|
|
230
|
-
|
|
231
|
-
btn.classList.contains('weui-desktop-btn_disabled');
|
|
232
|
-
if (!isDisabled && isVisible(btn)) {
|
|
233
|
-
for (var j = 0; j < labels.length; j++) {
|
|
234
|
-
if (text === labels[j] || text.includes(labels[j])) {
|
|
235
|
-
btn.click();
|
|
236
|
-
return { ok: true, text: text };
|
|
237
|
-
}
|
|
238
|
-
}
|
|
396
|
+
for (var i = 0; i < btns.length && !target; i++) {
|
|
397
|
+
var text = (btns[i].innerText || btns[i].textContent || '').trim();
|
|
398
|
+
for (var j = 0; j < labels.length; j++) {
|
|
399
|
+
if (text === labels[j] || text.includes(labels[j])) { target = btns[i]; how = 'text'; break; }
|
|
239
400
|
}
|
|
240
401
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
402
|
+
}
|
|
403
|
+
if (!target) return { ok: false, found: false };
|
|
404
|
+
if (isDisabled(target) || !isVisible(target)) return { ok: false, found: true, disabled: isDisabled(target) };
|
|
405
|
+
target.click();
|
|
406
|
+
return { ok: true, how: how, text: (target.innerText || target.textContent || '').trim() };
|
|
407
|
+
})
|
|
408
|
+
`;async function be(e,t){const i=t?["保存草稿","存草稿","草稿"]:["发表","发布"],n=t?"保存草稿":"发表",r=30;let a;for(let l=0;l<r;l++){a=await d(e,`${W}(${JSON.stringify(t)}, ${JSON.stringify(i)})`);if(a?.ok)break;if(l<r-1)await e.wait({time:0.5})}if(!a?.ok){await e.screenshot({path:"/tmp/wechat-channels_publish_submit_debug.png"});const l=await x(e).catch(()=>null),o=a?.found?`「${n}」按钮一直是禁用状态${l?.disabledTip?`,页面给出的原因: ${l.disabledTip}`:""}`:`页面上没有「${n}」按钮`;throw new c(`${o}。本次没有提交任何内容`,"截图: /tmp/wechat-channels_publish_submit_debug.png")}return a}re({site:"wechat-channels",name:"publish",access:"write",description:"发布视频到视频号",domain:"channels.weixin.qq.com",strategy:ne.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"视频文件路径 (.mp4/.mov/.avi/.webm)"},{name:"title",required:!1,help:"短标题(6-16 字;只能用中文/字母/数字/空格和 《》“”:+?% ℃,逗号请用空格代替)"},{name:"caption",fromFile:!0,required:!1,help:"描述内容,支持直接写 #话题(如:日常生活 #搞笑 #生活)"},{name:"schedule",required:!1,help:'定时发布时间(ISO8601 或 Unix 秒,如 "2026-05-20 10:00",精确到分钟;不能与 --draft 同用)'},{name:"draft",type:"bool",default:!1,help:"保存为草稿(平台不允许定时稿存草稿)"},{name:"manual",type:"bool",default:!1,help:"填完所有字段后不自动发布,由用户手动点击发表(务必同时传 --site-session persistent,否则表单页约 30 秒后会被重置为空白页)"},{name:"timeout",type:"int",required:!1,default:600,help:"命令整体超时秒数(含登录等待 + 上传转码,默认 600)"}],columns:["status","title","detail","id","manage_url"],func:async(e,t)=>{if(!e)throw new c("需要浏览器页面");const i=Q(t.timeout),n=Date.now()+i*1000,r=P(t.video,"视频",ue),a=String(t.title??"").trim(),l=String(t.caption??"").trim(),o=L(t.schedule||null),s=O(t.draft),m=O(t.manual);R(a);if(s&&o)throw new f("--draft 不能与 --schedule 同用:视频号在定时发表状态下禁用保存草稿");const u=o?Math.floor(o.getTime()/1000):0;await e.goto(M);await e.wait({time:4});{const b=(T)=>typeof T==="string"&&T.includes("/platform/post/create"),I=await d(e,"() => location.href");if(!b(I)){process.stderr.write(`
|
|
244
409
|
⚠️ 未登录视频号(已被重定向离开发布页)。请在 Chrome 中扫码登录...
|
|
245
|
-
`+" 当前地址: "+
|
|
410
|
+
`+" 当前地址: "+I+`
|
|
246
411
|
`+` 登录完成后将自动继续发布。
|
|
247
412
|
|
|
248
|
-
`);const
|
|
249
|
-
`)}if(!
|
|
413
|
+
`);const T=Math.min(n,Date.now()+120000);let V=!1;while(Date.now()<T){await e.wait({time:3});await e.goto(M);await e.wait({time:2});const ee=await d(e,"() => location.href");if(b(ee)){V=!0;break}process.stderr.write(` 等待扫码中...
|
|
414
|
+
`)}if(!V)throw new ie("channels.weixin.qq.com","登录超时(2分钟),请手动登录后重试");process.stderr.write(`✅ 登录成功,继续发布...
|
|
250
415
|
|
|
251
|
-
`);await e.wait({time:3});await e.goto(
|
|
416
|
+
`);await e.wait({time:3});await e.goto(M);await e.wait({time:5})}}await he(e,r);await e.wait({time:2});await we(e,D.basename(r),Math.min(180000,fe(n,"视频上传/转码")));await e.wait({time:1});if(a){await Y(e,le,a,"title");await e.wait({time:0.5});const b=await x(e);if(b?.titleError)throw new c(`平台拒绝了短标题: ${b.titleError}`,"本次没有提交任何内容,修改 --title 后重试")}if(l){await Y(e,oe,l,"caption");await e.wait({time:0.5})}if(o){await ve(e,o);await e.wait({time:0.5})}if(m){process.stderr.write(`
|
|
252
417
|
ℹ️ 手动模式:表单已填好,请在浏览器中检查并点击「发表」。
|
|
253
418
|
`+` 若未加 --site-session persistent,此标签页约 30 秒后会被重置为空白页。
|
|
254
419
|
|
|
255
|
-
`);return[{status:"⏸️ 已填写完毕,请在浏览器中手动点击发表",title:a||"",detail:[
|
|
420
|
+
`);return[{status:"⏸️ 已填写完毕,请在浏览器中手动点击发表",title:a||"",detail:[o?`定时: ${g(u)}(已回读表单确认)`:null].filter(Boolean).join(" · ")||"表单已就绪",id:"",manage_url:A}]}const h=await x(e),w=N(h,{targetSeconds:u,isDraft:s});if(w){await e.screenshot({path:"/tmp/wechat-channels_publish_submit_debug.png"});throw new c(`${w}。本次没有提交任何内容`,"截图: /tmp/wechat-channels_publish_submit_debug.png")}await be(e,s);await e.wait({time:s?1:4});const y=s?["已保存","暂存成功","保存成功"]:["已发表","发布成功","发表成功","审核中"];let v="",k="",S=!1,C="",_=null;const K=h?.draftObjectId||"";for(let b=0;b<30;b++){_=await x(e).catch(()=>null);if(s&&!K&&_?.draftObjectId&&_.isCaching===!1){C=_.draftObjectId;S=!0;break}if(!s&&_?.originalIntercept){await e.screenshot({path:"/tmp/wechat-channels_publish_result_debug.png"});throw new c("发表被平台的原创声明确认框拦下,作品尚未发出","需要在浏览器里人工确认是否声明原创后再发表。截图: /tmp/wechat-channels_publish_result_debug.png")}v=await d(e,"() => location.href");k=await d(e,`
|
|
256
421
|
(function(markers) {
|
|
257
|
-
${
|
|
422
|
+
${p}
|
|
258
423
|
var all = deepQueryAll('*');
|
|
259
424
|
for (var i = 0; i < all.length; i++) {
|
|
260
425
|
var el = all[i];
|
|
@@ -266,5 +431,5 @@ import*as Q from"node:fs";import*as k from"node:path";import{cli as j,Strategy a
|
|
|
266
431
|
}
|
|
267
432
|
}
|
|
268
433
|
return '';
|
|
269
|
-
})(${JSON.stringify(
|
|
270
|
-
`);
|
|
434
|
+
})(${JSON.stringify(y)})
|
|
435
|
+
`);S=U({isDraft:s,finalUrl:v,successMsg:k});if(S)break;await e.wait({time:0.5})}if(!S){await e.screenshot({path:"/tmp/wechat-channels_publish_result_debug.png"});throw new c(`已点击${s?"保存草稿":"发表"},但未能确认是否成功(结果未知)`,`不要直接重试,先到 ${A} 核对${s?"草稿箱":"作品列表"}里是否已有这条,避免重复发布。`+`截图: /tmp/wechat-channels_publish_result_debug.png;url=${v||""} message=${k||""}`)}const z=s?"草稿已保存":o?"定时发表已提交":"发布成功",X=[o?`定时: ${g(u)}(已回读表单确认)`:null,k||null,s?"草稿不公开,到管理页的草稿箱继续编辑":o?"定时稿到点前没有公开链接,到管理页核对计划时间":"平台提交后不返回作品链接,到管理页查看"].filter(Boolean),Z=[{status:`✅ ${z}`,title:a||"",detail:X.join(" · "),id:C,manage_url:A}];await e.goto("https://channels.weixin.qq.com/platform/post/list",{waitUntil:"none"}).catch(()=>{});return Z}});export const __test__={parseTimeoutSeconds:Q,parseScheduleDate:L,parseBooleanFlag:O,requireFilePath:P,submitSucceeded:U,validateShortTitle:R,submittedScheduleSeconds:H,preSubmitBlocker:N,uploadProgress:J,DEEP_QUERY_FN:p,FORM_STATE_FN:E,REALM_FILE_INPUT_JS:B,SCHEDULE_PICK_JS:G,SUBMIT_CLICK_JS:W};
|
package/clis/xianyu/watch.js
CHANGED
|
@@ -1 +1,43 @@
|
|
|
1
|
-
import{registerWatch as
|
|
1
|
+
import{AuthRequiredError as m,CommandExecutionError as u}from"@jackwener/opencli/errors";import{registerWatch as w}from"../_shared/watch.js";import{buildChatUrl as _,buildInboxUrl as o,requireEvaluateObject as p}from"./im.js";const g=/"lwp"\s*:\s*"\/s\//,y=[300,700,1000,1500],l=1e4,S=`
|
|
2
|
+
(() => {
|
|
3
|
+
const rows = Array.from(document.querySelectorAll('#conv-list-scrollable [class*="conversation-item"]'));
|
|
4
|
+
const sessions = [];
|
|
5
|
+
for (const row of rows) {
|
|
6
|
+
const fk = Object.keys(row).find((k) => k.startsWith('__reactFiber'));
|
|
7
|
+
let f = fk ? row[fk] : null;
|
|
8
|
+
let info = null;
|
|
9
|
+
for (let d = 0; f && d < 25; d++, f = f.return) {
|
|
10
|
+
const p = f.memoizedProps;
|
|
11
|
+
if (p && p.sessionInfo) { info = p.sessionInfo; break; }
|
|
12
|
+
}
|
|
13
|
+
if (!info) continue;
|
|
14
|
+
const summary = info.summary || {};
|
|
15
|
+
const lm = summary.latestMessage || {};
|
|
16
|
+
const c = lm.content || {};
|
|
17
|
+
const item = info.itemInfo || {};
|
|
18
|
+
sessions.push({
|
|
19
|
+
session_id: String(info.sessionId || ''),
|
|
20
|
+
session_type: info.sessionType,
|
|
21
|
+
peer_user_id: String((info.userInfo && info.userInfo.userId) || ''),
|
|
22
|
+
peer_name: String((lm.reminder && lm.reminder.title) || (info.userInfo && (info.userInfo.fishNick || info.userInfo.nick)) || ''),
|
|
23
|
+
item_id: String(item.itemId || ''),
|
|
24
|
+
item_title: String(item.title || ''),
|
|
25
|
+
price: String(item.price || ''),
|
|
26
|
+
seller_id: String((item.sellerInfo && item.sellerInfo.userId) || ''),
|
|
27
|
+
sender_id: String((lm.senderInfo && lm.senderInfo.userId) || ''),
|
|
28
|
+
message_id: String(lm.messageId || ''),
|
|
29
|
+
content_type: c.contentType,
|
|
30
|
+
text: String((c.text && c.text.text) || summary.summaryContent || ''),
|
|
31
|
+
ts: Number(lm.timeStamp || summary.timeStamp || 0),
|
|
32
|
+
unread: Number(info.unread || 0),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const bodyText = rows.length ? '' : (document.body ? document.body.innerText : '');
|
|
36
|
+
return {
|
|
37
|
+
rows: rows.length,
|
|
38
|
+
sessions,
|
|
39
|
+
requiresAuth: /请先登录|登录后/.test(bodyText),
|
|
40
|
+
blocked: /验证码|安全验证|异常访问/.test(bodyText),
|
|
41
|
+
};
|
|
42
|
+
})()
|
|
43
|
+
`;async function a(t){const s=p(await t.evaluate(S),"watch sessions");if(!s.rows&&s.requiresAuth)throw new m("www.goofish.com","Xianyu IM requires a logged-in browser session");if(!s.rows&&s.blocked)throw new m("www.goofish.com","Xianyu IM is blocked by verification or risk control");if(s.rows&&!s.sessions.length)throw new u("闲鱼会话列表渲染出来了,但行上读不到 sessionInfo:页面结构可能改版了");return s}function c(t,s){const n=[];for(const e of t){if(!e.message_id||!e.peer_user_id)continue;const i=e.sender_id===e.peer_user_id;if(!i&&!s?.includeOwn)continue;n.push({event:"dm",id:e.message_id,ts:e.ts,conversation_id:e.session_id,sender:{id:e.sender_id,name:i?e.peer_name:""},text:e.text,url:e.item_id?_(e.item_id,e.peer_user_id):o(),raw:{session_id:e.session_id,session_type:e.session_type,peer_user_id:e.peer_user_id,item_id:e.item_id,item_title:e.item_title,price:e.price,seller_id:e.seller_id,content_type:e.content_type,unread_count:e.unread,from_peer:i}})}return n}function d(t){return t.map((s)=>`${s.session_id}:${s.message_id}`).join("|")}w({site:"xianyu",name:"watch",domain:"www.goofish.com",description:"闲鱼私信监听:旁听网页版 IM 自己的 WebSocket 推送,一有新消息就读已打开的会话列表,按 NDJSON 逐条输出",watchUrl:o(),pattern:"wss-goofish.dingtalk.com",parse:async(t,s)=>{if(!g.test(String(t?.responsePreview??"")))return null;const n=Number(t?.timestamp??0);if(n&&s.state.readStartedAt&&n<s.state.readStartedAt)return null;s.state.readStartedAt=Date.now();const e=s.state.fingerprint;let i=null;for(const r of y){await new Promise((f)=>setTimeout(f,r));i=await a(s.page);if(d(i.sessions)!==e)break}s.state.fingerprint=d(i.sessions);return c(i.sessions,s)},reconcile:async(t,s)=>{const n=await(t.getCurrentUrl?t.getCurrentUrl().catch(()=>""):Promise.resolve(""));if(!/https:\/\/www\.goofish\.com\/im\b/.test(String(n||"")))await t.goto(o());const e=Date.now()+l;let i=await a(t);while(!i.rows&&Date.now()<e){await new Promise((r)=>setTimeout(r,500));i=await a(t)}if(!i.rows)throw new u(`闲鱼会话列表 ${l/1000} 秒内没有渲染出来(收件箱为空,或页面没加载完)`);s.state.fingerprint=d(i.sessions);return c(i.sessions,s)}});
|