publishport-opencli 1.0.9 → 1.0.10
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/clis/_shared/file-inject.js +41 -0
- package/clis/_shared/video-publish.js +4 -4
- package/clis/bilibili/upload.js +1 -1
- package/clis/chatgpt/utils.js +5 -5
- package/clis/claude/utils.js +1 -1
- package/clis/deepseek/utils.js +7 -7
- package/clis/douyin/draft.js +6 -6
- package/clis/douyin/publish-image.js +10 -31
- package/clis/facebook/post.js +9 -36
- package/clis/instagram/post.js +19 -66
- package/clis/instagram/reel.js +11 -11
- package/clis/threads/post.js +15 -34
- package/clis/twitter/post.js +15 -42
- package/clis/twitter/utils.js +4 -36
- package/clis/wechat-channels/publish.js +29 -57
- package/clis/weibo/publish.js +8 -8
- package/clis/weixin/create-draft.js +7 -7
- package/clis/xianyu/publish.js +2 -2
- package/clis/xiaohongshu/publish-video.js +9 -27
- package/clis/xiaohongshu/publish.js +32 -72
- package/dist/src/browser/bridge-readiness.d.ts +1 -1
- package/dist/src/browser/daemon-client.d.ts +72 -1
- package/dist/src/browser/daemon-client.js +1 -1
- package/dist/src/browser/daemon-version.d.ts +1 -1
- package/dist/src/browser/errors.d.ts +2 -0
- package/dist/src/browser/errors.js +3 -3
- package/dist/src/browser/network-interceptor.d.ts +11 -0
- package/dist/src/browser/network-interceptor.js +1 -0
- package/dist/src/cli.js +24 -24
- package/dist/src/commands/daemon.js +1 -1
- package/dist/src/daemon-utils.d.ts +1 -0
- package/dist/src/daemon-utils.js +1 -1
- package/dist/src/daemon.js +1 -1
- package/dist/src/doctor.d.ts +1 -1
- package/dist/src/doctor.js +7 -7
- package/dist/src/errors.d.ts +8 -0
- package/dist/src/errors.js +1 -1
- package/dist/src/execution.js +2 -2
- package/dist/src/session-lease.d.ts +135 -0
- package/dist/src/session-lease.js +1 -0
- package/dist/src/session-lease.test.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import*as X from"node:fs";import*as w from"node:path";import{CommandExecutionError as A}from"@jackwener/opencli/errors";const I=6291456,K={".mp4":"video/mp4",".mov":"video/quicktime",".avi":"video/x-msvideo",".mkv":"video/x-matroska",".m4v":"video/x-m4v",".webm":"video/webm",".flv":"video/x-flv",".wmv":"video/x-ms-wmv",".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp",".bmp":"image/bmp",".gif":"image/gif"};export function mimeForFile(q){return K[w.extname(q).toLowerCase()]??"application/octet-stream"}export function isFileInputCdpUnsupported(q){const O=q instanceof Error?q.message:String(q);return O.includes("Unknown action")||O.includes("not supported")||O.includes("set-file-input")}export function isFileAccessDenied(q){const O=q instanceof Error?q.message:String(q);return O.includes("Not allowed")||O.includes("File access not granted")}export function isStaleNodeError(q){const O=q instanceof Error?q.message:String(q);return O.includes("No element found matching selector")||O.includes("Could not find node with given id")||O.includes("No node with given id found")}function T(q){if(q&&typeof q==="object"&&"data"in q&&"session"in q)return q.data;return q}export async function injectInputFiles(q,O,z,{chunkBytes:j=I,onProgress:V,findInputJs:W}={}){const Z=`__ppInj_${Math.random().toString(36).slice(2,10)}`,G=JSON.stringify(Z),J=JSON.stringify(O),D=W??"((sel) => document.querySelector(sel))",L=T(await q.evaluate(`/*pp-file-inject:probe*/(() => ({ ok: !!(${D})(${J}) }))()`));if(!L||L.ok!==!0)throw new A(`未找到文件上传输入框: ${O}`);const R=z.reduce((Q,$)=>Q+X.statSync($).size,0);let U=0;try{for(const $ of z){const x=X.statSync($).size,_=X.openSync($,"r");let H=0;try{const N=Buffer.allocUnsafe(j);while(H<x){const M=X.readSync(_,N,0,j,H);if(M<=0)throw new A(`读取文件失败(提前 EOF): ${$}`);const F=JSON.stringify(N.subarray(0,M).toString("base64"));H+=M;U+=M;const Y=T(await q.evaluate(`
|
|
2
|
+
/*pp-file-inject:chunk*/(() => {
|
|
3
|
+
const S = window[${G}] ?? (window[${G}] = { parts: [], bytes: 0, files: [] });
|
|
4
|
+
const raw = atob(${F});
|
|
5
|
+
const len = raw.length;
|
|
6
|
+
const bytes = new Uint8Array(len);
|
|
7
|
+
for (let i = 0; i < len; i++) bytes[i] = raw.charCodeAt(i);
|
|
8
|
+
S.parts.push(new Blob([bytes]));
|
|
9
|
+
S.bytes += len;
|
|
10
|
+
return { ok: S.bytes === ${H}, bytes: S.bytes };
|
|
11
|
+
})()
|
|
12
|
+
`));if(!Y||Y.ok!==!0)throw new A(`文件注入中断:页面累计 ${Y?.bytes??"未知"} 字节 ≠ 已发送 ${H} 字节(标签页可能被刷新/导航走了)`,"保持发布页面前台稳定后重试本命令");if(V)V(U,R)}}finally{X.closeSync(_)}const v=T(await q.evaluate(`
|
|
13
|
+
/*pp-file-inject:seal*/(() => {
|
|
14
|
+
const S = window[${G}];
|
|
15
|
+
if (!S) return { ok: false };
|
|
16
|
+
S.files.push(new File(S.parts, ${JSON.stringify(w.basename($))}, { type: ${JSON.stringify(mimeForFile($))} }));
|
|
17
|
+
S.parts = [];
|
|
18
|
+
S.bytes = 0;
|
|
19
|
+
return { ok: true, files: S.files.length };
|
|
20
|
+
})()
|
|
21
|
+
`));if(!v||v.ok!==!0)throw new A("文件注入中断:页面注入状态丢失(标签页可能被刷新/导航走了)","保持发布页面前台稳定后重试本命令")}const Q=T(await q.evaluate(`
|
|
22
|
+
/*pp-file-inject:commit*/(() => {
|
|
23
|
+
const S = window[${G}];
|
|
24
|
+
delete window[${G}];
|
|
25
|
+
if (!S || !S.files.length) return { ok: false, error: '页面注入状态丢失(标签页可能被刷新/导航走了)' };
|
|
26
|
+
const input = (${D})(${J});
|
|
27
|
+
if (!input) return { ok: false, error: '提交时文件输入框已不在页面上: ' + ${J} };
|
|
28
|
+
const dt = new DataTransfer();
|
|
29
|
+
for (const f of S.files) dt.items.add(f);
|
|
30
|
+
try {
|
|
31
|
+
input.files = dt.files;
|
|
32
|
+
} catch {
|
|
33
|
+
// 个别站点用 Proxy 包过 input,直接赋值可能抛 —— defineProperty 兜底。
|
|
34
|
+
// configurable:true 让同一个 input 能被反复注入(重试/追加时 SPA 常复用同一元素)。
|
|
35
|
+
Object.defineProperty(input, 'files', { value: dt.files, writable: false, configurable: true });
|
|
36
|
+
}
|
|
37
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
38
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
39
|
+
return { ok: true, count: input.files.length };
|
|
40
|
+
})()
|
|
41
|
+
`));if(!Q||Q.ok!==!0)throw new A(`文件注入提交失败: ${Q?.error??"页面返回异常"}`,"保持发布页面前台稳定后重试本命令");return{count:Number(Q.count)||z.length,bytes:U}}catch(Q){await q.evaluate(`/*pp-file-inject:cleanup*/(() => { delete window[${G}]; })()`).catch(()=>{});throw Q}}export async function setInputFiles(q,O,z,j={}){const V=Array.isArray(O)?O:[O];let W;if(q.setFileInput)for(const Z of V)try{await q.setFileInput(z,Z);return{selector:Z,method:"cdp"}}catch(G){W=G;if(isFileAccessDenied(G)||isFileInputCdpUnsupported(G)){console.warn(`[file-inject] CDP 直读被拒(${G instanceof Error?G.message:G}),切无权限分块注入通道`);break}if(V.length===1&&isStaleNodeError(G))throw G}for(const Z of V)try{const G=await injectInputFiles(q,Z,z,j);return{selector:Z,method:"inject",...G}}catch(G){if(!(G instanceof Error?G.message:String(G)).includes("未找到文件上传输入框"))throw G;W=G}throw new A(`未找到可用的文件上传输入框(尝试过: ${V.join(" , ")}):${W instanceof Error?W.message:String(W)}`)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as Y from"node:fs";import*as L from"node:path";import{ArgumentError as H,CommandExecutionError as $}from"@jackwener/opencli/errors";import{isFileAccessDenied as k,setInputFiles as C}from"./file-inject.js";export const SUPPORTED_VIDEO_EXTENSIONS=new Set([".mp4",".mov",".avi",".mkv",".m4v",".webm",".flv",".wmv"]),SUPPORTED_IMAGE_EXTENSIONS=new Set([".jpg",".jpeg",".png",".webp",".bmp"]);export function parseScheduleDate(q){if(q==null||q==="")return null;const y=String(q),z=/^\d+$/.test(y)?new Date(Number(y)<1000000000000?Number(y)*1000:Number(y)):new Date(y);if(Number.isNaN(z.getTime()))throw new H(`无法解析定时时间: ${q}`);if(z.getTime()<=Date.now())throw new H("定时发布时间必须晚于当前时间");return z}export function unwrap(q){if(q&&typeof q==="object"&&"data"in q&&"session"in q)return q.data;return q}export async function evalPage(q,y){return unwrap(await q.evaluate(y))}export function sleep(q){return new Promise((y)=>setTimeout(y,q*1000))}export function resolveVideoFile(q){const y=L.resolve(String(q));let z;try{z=Y.statSync(y)}catch{throw new H(`视频文件不存在: ${y}`)}if(!z.isFile())throw new H(`视频路径不是文件: ${y}`);const B=L.extname(y).toLowerCase();if(!SUPPORTED_VIDEO_EXTENSIONS.has(B))throw new H(`不支持的视频格式: ${B}(支持 ${[...SUPPORTED_VIDEO_EXTENSIONS].join("/")})`);return y}export function resolveImageFile(q){const y=L.resolve(String(q));let z;try{z=Y.statSync(y)}catch{throw new H(`图片文件不存在: ${y}`)}if(!z.isFile())throw new H(`图片路径不是文件: ${y}`);const B=L.extname(y).toLowerCase();if(!SUPPORTED_IMAGE_EXTENSIONS.has(B))throw new H(`不支持的图片格式: ${B}(支持 ${[...SUPPORTED_IMAGE_EXTENSIONS].join("/")})`);return y}export function throwIfFileAccessDenied(q){if(!k(q))return;throw new $("Chrome 拦截了扩展读取本地文件(DOM.setFileInputFiles → File access not granted):商店安装的扩展默认没有本地文件访问权限","请让用户在 Chrome 打开 chrome://extensions,找到「ppcli Browser Bridge」→「详情」→ 打开「允许访问文件网址」(Allow access to file URLs),开关即时生效,然后重试本命令")}export async function setVideoInput(q,y,z){return(await C(q,y,z)).selector}export async function currentUrl(q){try{if(q.getCurrentUrl){const y=await q.getCurrentUrl();if(y)return String(y)}}catch{}try{return String(await evalPage(q,"() => location.href")||"")}catch{return""}}export async function waitForUrlIncludes(q,y,z=60000,B=500){const J=Array.isArray(y)?y:[y],K=Date.now()+z;while(Date.now()<K){const Q=await currentUrl(q);if(Q&&J.some((Z)=>Q.includes(Z)))return!0;await q.wait({time:B/1000})}return!1}export async function countByText(q,y){return Number(unwrap(await q.evaluate(`
|
|
2
2
|
((want) => {
|
|
3
3
|
let n = 0;
|
|
4
4
|
const nodes = document.querySelectorAll('body *');
|
|
@@ -12,7 +12,7 @@ import*as Z from"node:fs";import*as L from"node:path";import{ArgumentError as J,
|
|
|
12
12
|
}
|
|
13
13
|
return n;
|
|
14
14
|
})(${JSON.stringify(y)})
|
|
15
|
-
`)))||0}export async function clickByText(q,y,{exact:z=!1,timeoutMs:B=0,pollMs:
|
|
15
|
+
`)))||0}export async function clickByText(q,y,{exact:z=!1,timeoutMs:B=0,pollMs:J=500}={}){const K=Date.now()+Math.max(0,B);for(;;){if(Boolean(unwrap(await q.evaluate(`
|
|
16
16
|
((want, exact) => {
|
|
17
17
|
const norm = (v) => (v || '').replace(/\\s+/g, ' ').trim();
|
|
18
18
|
const isVisible = (el) => {
|
|
@@ -37,7 +37,7 @@ import*as Z from"node:fs";import*as L from"node:path";import{ArgumentError as J,
|
|
|
37
37
|
}
|
|
38
38
|
return false;
|
|
39
39
|
})(${JSON.stringify(y)}, ${z?"true":"false"})
|
|
40
|
-
`))))return!0;if(Date.now()>=K)return!1;await q.wait({time:
|
|
40
|
+
`))))return!0;if(Date.now()>=K)return!1;await q.wait({time:J/1000})}}export async function existsByText(q,y,{exact:z=!1}={}){return Boolean(unwrap(await q.evaluate(`
|
|
41
41
|
((want, exact) => {
|
|
42
42
|
const norm = (v) => (v || '').replace(/\\s+/g, ' ').trim();
|
|
43
43
|
const isVisible = (el) => {
|
|
@@ -52,7 +52,7 @@ import*as Z from"node:fs";import*as L from"node:path";import{ArgumentError as J,
|
|
|
52
52
|
}
|
|
53
53
|
return false;
|
|
54
54
|
})(${JSON.stringify(y)}, ${z?"true":"false"})
|
|
55
|
-
`)))}export async function waitForSelector(q,y,z=30000,B=500){const
|
|
55
|
+
`)))}export async function waitForSelector(q,y,z=30000,B=500){const J=Date.now()+z;while(Date.now()<J){if(Boolean(unwrap(await q.evaluate(`
|
|
56
56
|
((sel) => {
|
|
57
57
|
const el = document.querySelector(sel);
|
|
58
58
|
if (!el) return false;
|
package/clis/bilibili/upload.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as I from"node:fs";import*as P from"node:path";import{cli as E,Strategy as b}from"@jackwener/opencli/registry";import{ArgumentError as D,AuthRequiredError as h,CommandExecutionError as S}from"@jackwener/opencli/errors";import{resolveVideoFile as l,resolveImageFile as u,throwIfFileAccessDenied as w}from"../_shared/video-publish.js";const U={bda2:{os:"upos",upcdn:"bda2",probe_version:20221109},bldsa:{os:"upos",upcdn:"bldsa",probe_version:20221109},qn:{os:"upos",upcdn:"qn",probe_version:20221109},ws:{os:"upos",upcdn:"ws",probe_version:20221109}},f="bda2",k=["SESSDATA","bili_jct","DedeUserID"],m="https://member.bilibili.com/platform/home",_="__pp_bili_video_input";function n(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{error:"文件未就绪(input 为空)"};var Z=J.files[0],Y=Z.size,$=W.line;return async function(){try{var G=new URLSearchParams({profile:"ugcfx/bup",name:Z.name,size:String(Y),r:$.os,ssl:"0",version:"2.14.0",build:"2100400",upcdn:$.upcdn,probe_version:String($.probe_version)}),X=await(await fetch("https://member.bilibili.com/preupload?"+G.toString(),{credentials:"include",headers:{Referer:"https://www.bilibili.com"}})).json();if(X.OK!==1)return{error:"preupload 失败:"+JSON.stringify(X)};var K="https:"+X.endpoint+"/"+String(X.upos_uri).replace(/^upos:\/\//,""),O=new URLSearchParams({uploads:"",output:"json",profile:"ugcfx/bup",filesize:String(Y),partsize:String(X.chunk_size),biz_id:String(X.biz_id)}),H=await(await fetch(K+"?"+O.toString(),{method:"POST",headers:{"x-upos-auth":X.auth}})).json();if(H.OK!==1)return{error:"获取 upload_id 失败:"+JSON.stringify(H)};var T=Math.max(1,Math.ceil(Y/X.chunk_size));return{url:K,auth:X.auth,uploadId:H.upload_id,chunkSize:X.chunk_size,bizId:X.biz_id,size:Y,name:Z.name,total:T}}catch(R){return{error:"preupload 异常:"+String(R&&R.message||R)}}}()}function d(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{ok:!1,error:"文件丢失(页面可能已导航)"};var Z=J.files[0],Y=W.idx*W.chunkSize,$=Z.slice(Y,Math.min(Y+W.chunkSize,W.size)),G=$.size,X=new URLSearchParams({partNumber:String(W.idx+1),uploadId:String(W.uploadId),chunk:String(W.idx),chunks:String(W.total),size:String(G),start:String(Y),end:String(Y+G),total:String(W.size)});return async function(){for(var K=0;K<5;K++){try{var O=await fetch(W.url+"?"+X.toString(),{method:"PUT",headers:{"x-upos-auth":W.auth},body:$});if(O.status<400){var H=await O.text();if(H==="MULTIPART_PUT_SUCCESS"||H==="")return{ok:!0}}}catch(T){}await new Promise(function(T){setTimeout(T,800*(K+1))})}return{ok:!1,error:"分块 "+W.idx+" 多次重试仍失败"}}()}function j(W){var J=[];for(var Z=1;Z<=W.total;Z++)J.push({partNumber:Z,eTag:"etag"});var Y=new URLSearchParams({output:"json",name:W.name,profile:"ugcfx/bup",uploadId:String(W.uploadId),biz_id:String(W.bizId)});return async function(){try{var $=await(await fetch(W.url+"?"+Y.toString(),{method:"POST",headers:{"x-upos-auth":W.auth,"content-type":"application/json; charset=UTF-8"},body:JSON.stringify({parts:J})})).json();if($.OK!==1)return{error:"complete 失败:"+JSON.stringify($)};var G=String($.key).replace(/^\//,"").replace(/\.[^.]+$/,"");return{filename:G,cid:W.bizId}}catch(X){return{error:"complete 异常:"+String(X&&X.message||X)}}}()}function c(W){return async function(){try{var J=await fetch("https://member.bilibili.com/x/vupre/web/topic/type?type_id="+encodeURIComponent(W.tid)+"&pn=0&ps=200",{credentials:"include"}),Z=await J.json();if(!Z||Z.code!==0||!Z.data||!Array.isArray(Z.data.topics))return{error:"获取话题列表失败:"+(Z&&Z.message||"code="+(Z&&Z.code))};var Y=null;for(var $=0;$<Z.data.topics.length;$++)if(Number(Z.data.topics[$].topic_id)===Number(W.topicId)){Y=Z.data.topics[$];break}if(!Y)return{error:"topic_id "+W.topicId+" 不在分区 "+W.tid+" 的可用话题里(用 bilibili topics --tid "+W.tid+" 查合法值)"};return{topicId:Number(Y.topic_id),missionId:Y.mission_id!=null?Number(Y.mission_id):null,name:Y.topic_name||""}}catch(G){return{error:"解析话题异常:"+String(G&&G.message||G)}}}()}function o(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]);try{var Y=W.dataUri;if(typeof Y==="string"&&Y.indexOf("data:image/webp")===0){var $=Y.indexOf(",");if($===-1)return{error:"封面 data URI 格式非法"};var G=Y.slice(5,$),X=Y.slice($+1),K;if(G.indexOf("base64")!==-1){var O=atob(X);K=new Uint8Array(O.length);for(var H=0;H<O.length;H++)K[H]=O.charCodeAt(H)}else K=new TextEncoder().encode(decodeURIComponent(X));var T=new Blob([K],{type:"image/webp"}),R=await createImageBitmap(T),B=document.createElement("canvas");B.width=R.width;B.height=R.height;var M=B.getContext("2d");M.fillStyle="#ffffff";M.fillRect(0,0,B.width,B.height);M.drawImage(R,0,0);var q=await new Promise(function(F){B.toBlob(F,"image/jpeg",0.92)});if(!q)return{error:"封面 webp 转码 JPEG 失败"};Y=await new Promise(function(F,x){var A=new FileReader;A.onload=function(){F(String(A.result))};A.onerror=function(){x(Error("FileReader 读取转码结果失败"))};A.readAsDataURL(q)})}var L=new URLSearchParams;L.append("cover",Y);L.append("csrf",Z);var V=await(await fetch("https://member.bilibili.com/x/vu/web/cover/up",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:L.toString()})).json();if(!V||V.code!==0||!V.data||!V.data.url)return{error:"封面上传失败:"+(V&&V.message?V.message:JSON.stringify(V))};return{url:V.data.url}}catch(F){return{error:"封面上传异常:"+String(F&&F.message||F)}}}()}function p(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.meta;for(var $ in Y)if(Y[$]===null||Y[$]===void 0)delete Y[$];Y.csrf=Z;try{var G=await fetch("https://member.bilibili.com/x/vu/web/add/v3?csrf="+encodeURIComponent(Z)+"&t="+Date.now(),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),X=await G.json();if(!X||X.code!==0)return{error:"add/v3 提交失败:"+(X&&X.message?X.message:"")+"(code="+(X&&X.code)+" status="+G.status+")"};return{bvid:X.data&&X.data.bvid,aid:X.data&&X.data.aid}}catch(K){return{error:"add/v3 异常:"+String(K&&K.message||K)}}}()}function g(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.body;Y.csrf=Z;try{var $=await fetch("https://member.bilibili.com/x/vupre/web/draft/add?t="+Date.now()+"&csrf="+encodeURIComponent(Z),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),G=await $.json();if(!G||G.code!==0)return{error:"draft/add 保存草稿失败:"+(G&&G.message?G.message:"")+"(code="+(G&&G.code)+" status="+$.status+")"};return{data:G.data}}catch(X){return{error:"draft/add 异常:"+String(X&&X.message||X)}}}()}const N=(W,J,Z)=>W.evaluateWithArgs(`(${J.toString()})(a)`,{a:Z});function s(W){const J=u(W),Z=P.extname(J).toLowerCase(),Y=Z===".png"?"image/png":Z===".webp"?"image/webp":Z===".bmp"?"image/bmp":"image/jpeg",$=I.readFileSync(J).toString("base64");return`data:${Y};base64,${$}`}async function t(W){for(let J=0;J<8;J++){await W.goto(m);await W.wait({time:1});let Z="";try{Z=String(await W.evaluate("location.href")||"")}catch{Z=""}if(/^https?:\/\/member\.bilibili\.com\//.test(Z)){const Y=await W.getCookies({url:"https://member.bilibili.com"}),$=new Map;for(const X of Array.isArray(Y)?Y:[])if(X&&typeof X.name==="string")$.set(X.name,String(X.value??""));const G=k.filter((X)=>!$.get(X));if(!G.length)return $;throw new h("member.bilibili.com",`缺少 B站登录 cookie(${G.join(", ")})。请先在客户端登录哔哩哔哩。`)}}throw new h("member.bilibili.com","无法进入 B站创作中心(可能未登录或被风控)。请先在客户端登录哔哩哔哩。")}E({site:"bilibili",name:"upload",access:"write",description:"投稿视频到 B站(在真实浏览器登录态里走 web 投稿:preupload→upos 分块→add/v3)。默认仅 dry-run 校验,加 --execute 才真正上传并提交。",domain:"member.bilibili.com",strategy:b.COOKIE,browser:!0,args:[{name:"file",required:!0,positional:!0,help:"视频文件路径"},{name:"title",help:"稿件标题(默认取文件名)"},{name:"tid",type:"number",required:!0,help:"分区 id(B站 typeid,必填,禁止臆造)。合法值用 `bilibili partitions` 列举后取。"},{name:"tag",required:!0,help:"标签,逗号分隔(B站要求至少 1 个)"},{name:"desc",help:"简介"},{name:"topic",type:"number",help:"参与话题的 topic_id(重要流量入口)。合法值用 `bilibili topics --tid <tid>` 列举,禁止臆造;mission_id 自动匹配。"},{name:"cover",help:"封面图片路径(不传由 B站自动截取)"},{name:"copyright",type:"number",help:"1=自制 2=转载(默认 1)"},{name:"source",help:"转载来源 URL(copyright=2 时必填)"},{name:"dynamic",help:"同步发布的动态文案(可空)"},{name:"no-reprint",type:"boolean",help:"禁止转载(默认允许)"},{name:"dtime",type:"number",help:"定时发布的 10 位 unix 时间戳(可空=立即)"},{name:"line",help:`上传线路:${Object.keys(U).join("/")}(默认 ${f})`},{name:"concurrency",type:"number",help:"分块并发数(默认 3)"},{name:"draft",type:"boolean",help:"存草稿而非直接发布(上传视频后存到创作中心草稿箱,不公开)"},{name:"execute",type:"boolean",help:"真正发布投稿;不带(也不带 --draft)则只做 dry-run 校验"}],columns:["status","title","bvid","url"],func:async(W,J)=>{if(!W)throw new S("bilibili upload 需要浏览器会话");const Z=l(String(J.file??"")),Y=String(J.title??"").trim()||P.basename(Z).replace(/\.[^.]+$/,""),$=String(J.tag??"").split(",").map((Q)=>Q.trim()).filter(Boolean);if(!$.length)throw new D("B站投稿至少需要 1 个标签,用 --tag 传(逗号分隔)");if(J.tid==null||J.tid===""||!Number.isFinite(Number(J.tid)))throw new D("必须用 --tid 指定分区 id(数字)。合法值用 `bilibili partitions` 列举,禁止臆造。");const G=Number(J.tid),X=J.copyright!=null&&J.copyright!==""?Number(J.copyright):1;if(X!==1&&X!==2)throw new D("--copyright 只能是 1(自制) 或 2(转载)");const K=String(J.source??"").trim();if(X===2&&!K)throw new D("转载(copyright=2)必须用 --source 提供转载来源");if(J.cover)u(String(J.cover));const O=String(J.line??f),H=U[O];if(!H)throw new D(`未知上传线路「${O}」,可选:${Object.keys(U).join("/")}`);const T=J.concurrency!=null&&J.concurrency!==""?Number(J.concurrency):3,R=Boolean(J.draft),B=Boolean(J.execute)&&!R;await t(W);if(!W.setFileInput)throw new S("浏览器扩展不支持 CDP 文件上传(set-file-input),无法上传视频;请升级 PublishPort 客户端/扩展");await W.evaluate(`(() => { var id=${JSON.stringify(_)}; var el=document.getElementById(id); if(!el){ el=document.createElement('input'); el.type='file'; el.id=id; el.style.cssText='position:fixed;left:-9999px;top:0;'; document.body.appendChild(el);} el.value=''; return true; })()`);try{await W.setFileInput([Z],`#${_}`)}catch(Q){w(Q);throw Q}if(!R&&!B)return{status:"dry-run",title:Y,bvid:"",url:`校验通过:登录态有效、视频已注入就绪、分区 ${G}、标签 [${$.join(", ")}]、版权 ${X===1?"自制":"转载"}。加 --execute 发布,或 --draft 存草稿。`};const M=await N(W,n,{inputId:_,line:H});if(!M||M.error)throw new S(`B站 preupload 失败:${M&&M.error||"无返回"}`);for(let Q=0;Q<M.total;Q++){const z=await N(W,d,{inputId:_,url:M.url,auth:M.auth,uploadId:M.uploadId,idx:Q,total:M.total,size:M.size,chunkSize:M.chunkSize});if(!z||!z.ok)throw new S(`B站分块上传失败(${Q+1}/${M.total}):${z&&z.error||"无返回"}`)}const q=await N(W,j,{url:M.url,auth:M.auth,uploadId:M.uploadId,bizId:M.bizId,name:M.name,total:M.total});if(!q||q.error||!q.filename)throw new S(`B站 complete 失败:${q&&q.error||"未拿到 filename"}`);let L="";if(J.cover){const Q=s(String(J.cover)),z=await N(W,o,{dataUri:Q});if(!z||z.error||!z.url)throw new S(`B站封面上传失败:${z&&z.error||"未拿到 url"}`);L=z.url}let V=null,F=null;if(J.topic!=null&&J.topic!==""){if(!Number.isFinite(Number(J.topic)))throw new D("--topic 必须是 topic_id 数字,用 `bilibili topics --tid <tid>` 取合法值");const Q=await N(W,c,{tid:G,topicId:Number(J.topic)});if(!Q||Q.error)throw new S(`B站话题解析失败:${Q&&Q.error||"无返回"}`);V=Q.topicId;F=Q.missionId}if(R){const Q={videos:[{filename:q.filename,title:Y,desc:"",cid:q.cid,is_4k:!1,is_8k:!1,is_hdr:!1}],cover:L,cover43:L,ai_cover:0,is_ab_cover:0,ab_cover_info:null,title:Y,copyright:X,tid:G,tag:$.join(","),desc:String(J.desc??""),recreate:-1,dynamic:String(J.dynamic??""),is_only_self:0,space_hidden:2,watermark:{state:0},no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1};if(X===2)Q.source=K;if(V!=null){Q.topic_id=V;Q.topic_detail={from_topic_id:V,from_source:"arc.web.search"};if(F!=null)Q.mission_id=F}const z=await N(W,g,{body:Q});if(!z||z.error)throw new S(`B站 ${z&&z.error||"draft/add 无返回"}`);const y=z.data&&(z.data.aid!=null?z.data.aid:z.data.draft_id!=null?z.data.draft_id:z.data.id);return{status:"✅ 草稿已保存",title:Y,bvid:y!=null?`draft:${y}`:"",url:"https://member.bilibili.com/platform/upload-manager/article?group=draft"}}const x=J.dtime!=null&&J.dtime!==""?Number(J.dtime):null,A={title:Y,copyright:X,tid:G,tag:$.join(","),mission_id:F,topic_id:V,topic_detail:V!=null?{from_topic_id:V,from_source:"arc.web.recommend"}:null,desc_format_id:9999,desc:String(J.desc??""),dtime:x,recreate:-1,dynamic:String(J.dynamic??""),interactive:0,act_reserve_create:0,no_disturbance:0,porder:null,adorder_type:9,no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},neutral_mark:null,dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1,web_os:1,source:X===2?K:null,watermark:{state:0},cover:L,videos:[{title:Y,desc:"",filename:q.filename,cid:q.cid}]},C=await N(W,p,{meta:A});if(!C||C.error)throw new S(`B站 ${C&&C.error||"add/v3 无返回"}`);const v=C.bvid?String(C.bvid):"";return{status:J.dtime?"✅ 定时投稿已提交":"✅ 投稿成功",title:Y,bvid:v,url:v?`https://www.bilibili.com/video/${v}`:"(已提交,稍后在创作中心查看)"}}});
|
|
1
|
+
import*as I from"node:fs";import*as P from"node:path";import{cli as E,Strategy as b}from"@jackwener/opencli/registry";import{ArgumentError as D,AuthRequiredError as h,CommandExecutionError as L}from"@jackwener/opencli/errors";import{resolveVideoFile as l,resolveImageFile as u}from"../_shared/video-publish.js";import{setInputFiles as w}from"../_shared/file-inject.js";const U={bda2:{os:"upos",upcdn:"bda2",probe_version:20221109},bldsa:{os:"upos",upcdn:"bldsa",probe_version:20221109},qn:{os:"upos",upcdn:"qn",probe_version:20221109},ws:{os:"upos",upcdn:"ws",probe_version:20221109}},f="bda2",k=["SESSDATA","bili_jct","DedeUserID"],m="https://member.bilibili.com/platform/home",x="__pp_bili_video_input";function n(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{error:"文件未就绪(input 为空)"};var Z=J.files[0],Y=Z.size,$=W.line;return async function(){try{var G=new URLSearchParams({profile:"ugcfx/bup",name:Z.name,size:String(Y),r:$.os,ssl:"0",version:"2.14.0",build:"2100400",upcdn:$.upcdn,probe_version:String($.probe_version)}),X=await(await fetch("https://member.bilibili.com/preupload?"+G.toString(),{credentials:"include",headers:{Referer:"https://www.bilibili.com"}})).json();if(X.OK!==1)return{error:"preupload 失败:"+JSON.stringify(X)};var K="https:"+X.endpoint+"/"+String(X.upos_uri).replace(/^upos:\/\//,""),O=new URLSearchParams({uploads:"",output:"json",profile:"ugcfx/bup",filesize:String(Y),partsize:String(X.chunk_size),biz_id:String(X.biz_id)}),H=await(await fetch(K+"?"+O.toString(),{method:"POST",headers:{"x-upos-auth":X.auth}})).json();if(H.OK!==1)return{error:"获取 upload_id 失败:"+JSON.stringify(H)};var T=Math.max(1,Math.ceil(Y/X.chunk_size));return{url:K,auth:X.auth,uploadId:H.upload_id,chunkSize:X.chunk_size,bizId:X.biz_id,size:Y,name:Z.name,total:T}}catch(R){return{error:"preupload 异常:"+String(R&&R.message||R)}}}()}function d(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{ok:!1,error:"文件丢失(页面可能已导航)"};var Z=J.files[0],Y=W.idx*W.chunkSize,$=Z.slice(Y,Math.min(Y+W.chunkSize,W.size)),G=$.size,X=new URLSearchParams({partNumber:String(W.idx+1),uploadId:String(W.uploadId),chunk:String(W.idx),chunks:String(W.total),size:String(G),start:String(Y),end:String(Y+G),total:String(W.size)});return async function(){for(var K=0;K<5;K++){try{var O=await fetch(W.url+"?"+X.toString(),{method:"PUT",headers:{"x-upos-auth":W.auth},body:$});if(O.status<400){var H=await O.text();if(H==="MULTIPART_PUT_SUCCESS"||H==="")return{ok:!0}}}catch(T){}await new Promise(function(T){setTimeout(T,800*(K+1))})}return{ok:!1,error:"分块 "+W.idx+" 多次重试仍失败"}}()}function c(W){var J=[];for(var Z=1;Z<=W.total;Z++)J.push({partNumber:Z,eTag:"etag"});var Y=new URLSearchParams({output:"json",name:W.name,profile:"ugcfx/bup",uploadId:String(W.uploadId),biz_id:String(W.bizId)});return async function(){try{var $=await(await fetch(W.url+"?"+Y.toString(),{method:"POST",headers:{"x-upos-auth":W.auth,"content-type":"application/json; charset=UTF-8"},body:JSON.stringify({parts:J})})).json();if($.OK!==1)return{error:"complete 失败:"+JSON.stringify($)};var G=String($.key).replace(/^\//,"").replace(/\.[^.]+$/,"");return{filename:G,cid:W.bizId}}catch(X){return{error:"complete 异常:"+String(X&&X.message||X)}}}()}function j(W){return async function(){try{var J=await fetch("https://member.bilibili.com/x/vupre/web/topic/type?type_id="+encodeURIComponent(W.tid)+"&pn=0&ps=200",{credentials:"include"}),Z=await J.json();if(!Z||Z.code!==0||!Z.data||!Array.isArray(Z.data.topics))return{error:"获取话题列表失败:"+(Z&&Z.message||"code="+(Z&&Z.code))};var Y=null;for(var $=0;$<Z.data.topics.length;$++)if(Number(Z.data.topics[$].topic_id)===Number(W.topicId)){Y=Z.data.topics[$];break}if(!Y)return{error:"topic_id "+W.topicId+" 不在分区 "+W.tid+" 的可用话题里(用 bilibili topics --tid "+W.tid+" 查合法值)"};return{topicId:Number(Y.topic_id),missionId:Y.mission_id!=null?Number(Y.mission_id):null,name:Y.topic_name||""}}catch(G){return{error:"解析话题异常:"+String(G&&G.message||G)}}}()}function o(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]);try{var Y=W.dataUri;if(typeof Y==="string"&&Y.indexOf("data:image/webp")===0){var $=Y.indexOf(",");if($===-1)return{error:"封面 data URI 格式非法"};var G=Y.slice(5,$),X=Y.slice($+1),K;if(G.indexOf("base64")!==-1){var O=atob(X);K=new Uint8Array(O.length);for(var H=0;H<O.length;H++)K[H]=O.charCodeAt(H)}else K=new TextEncoder().encode(decodeURIComponent(X));var T=new Blob([K],{type:"image/webp"}),R=await createImageBitmap(T),S=document.createElement("canvas");S.width=R.width;S.height=R.height;var Q=S.getContext("2d");Q.fillStyle="#ffffff";Q.fillRect(0,0,S.width,S.height);Q.drawImage(R,0,0);var q=await new Promise(function(F){S.toBlob(F,"image/jpeg",0.92)});if(!q)return{error:"封面 webp 转码 JPEG 失败"};Y=await new Promise(function(F,_){var A=new FileReader;A.onload=function(){F(String(A.result))};A.onerror=function(){_(Error("FileReader 读取转码结果失败"))};A.readAsDataURL(q)})}var B=new URLSearchParams;B.append("cover",Y);B.append("csrf",Z);var V=await(await fetch("https://member.bilibili.com/x/vu/web/cover/up",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:B.toString()})).json();if(!V||V.code!==0||!V.data||!V.data.url)return{error:"封面上传失败:"+(V&&V.message?V.message:JSON.stringify(V))};return{url:V.data.url}}catch(F){return{error:"封面上传异常:"+String(F&&F.message||F)}}}()}function p(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.meta;for(var $ in Y)if(Y[$]===null||Y[$]===void 0)delete Y[$];Y.csrf=Z;try{var G=await fetch("https://member.bilibili.com/x/vu/web/add/v3?csrf="+encodeURIComponent(Z)+"&t="+Date.now(),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),X=await G.json();if(!X||X.code!==0)return{error:"add/v3 提交失败:"+(X&&X.message?X.message:"")+"(code="+(X&&X.code)+" status="+G.status+")"};return{bvid:X.data&&X.data.bvid,aid:X.data&&X.data.aid}}catch(K){return{error:"add/v3 异常:"+String(K&&K.message||K)}}}()}function g(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.body;Y.csrf=Z;try{var $=await fetch("https://member.bilibili.com/x/vupre/web/draft/add?t="+Date.now()+"&csrf="+encodeURIComponent(Z),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),G=await $.json();if(!G||G.code!==0)return{error:"draft/add 保存草稿失败:"+(G&&G.message?G.message:"")+"(code="+(G&&G.code)+" status="+$.status+")"};return{data:G.data}}catch(X){return{error:"draft/add 异常:"+String(X&&X.message||X)}}}()}const N=(W,J,Z)=>W.evaluateWithArgs(`(${J.toString()})(a)`,{a:Z});function s(W){const J=u(W),Z=P.extname(J).toLowerCase(),Y=Z===".png"?"image/png":Z===".webp"?"image/webp":Z===".bmp"?"image/bmp":"image/jpeg",$=I.readFileSync(J).toString("base64");return`data:${Y};base64,${$}`}async function t(W){for(let J=0;J<8;J++){await W.goto(m);await W.wait({time:1});let Z="";try{Z=String(await W.evaluate("location.href")||"")}catch{Z=""}if(/^https?:\/\/member\.bilibili\.com\//.test(Z)){const Y=await W.getCookies({url:"https://member.bilibili.com"}),$=new Map;for(const X of Array.isArray(Y)?Y:[])if(X&&typeof X.name==="string")$.set(X.name,String(X.value??""));const G=k.filter((X)=>!$.get(X));if(!G.length)return $;throw new h("member.bilibili.com",`缺少 B站登录 cookie(${G.join(", ")})。请先在客户端登录哔哩哔哩。`)}}throw new h("member.bilibili.com","无法进入 B站创作中心(可能未登录或被风控)。请先在客户端登录哔哩哔哩。")}E({site:"bilibili",name:"upload",access:"write",description:"投稿视频到 B站(在真实浏览器登录态里走 web 投稿:preupload→upos 分块→add/v3)。默认仅 dry-run 校验,加 --execute 才真正上传并提交。",domain:"member.bilibili.com",strategy:b.COOKIE,browser:!0,args:[{name:"file",required:!0,positional:!0,help:"视频文件路径"},{name:"title",help:"稿件标题(默认取文件名)"},{name:"tid",type:"number",required:!0,help:"分区 id(B站 typeid,必填,禁止臆造)。合法值用 `bilibili partitions` 列举后取。"},{name:"tag",required:!0,help:"标签,逗号分隔(B站要求至少 1 个)"},{name:"desc",help:"简介"},{name:"topic",type:"number",help:"参与话题的 topic_id(重要流量入口)。合法值用 `bilibili topics --tid <tid>` 列举,禁止臆造;mission_id 自动匹配。"},{name:"cover",help:"封面图片路径(不传由 B站自动截取)"},{name:"copyright",type:"number",help:"1=自制 2=转载(默认 1)"},{name:"source",help:"转载来源 URL(copyright=2 时必填)"},{name:"dynamic",help:"同步发布的动态文案(可空)"},{name:"no-reprint",type:"boolean",help:"禁止转载(默认允许)"},{name:"dtime",type:"number",help:"定时发布的 10 位 unix 时间戳(可空=立即)"},{name:"line",help:`上传线路:${Object.keys(U).join("/")}(默认 ${f})`},{name:"concurrency",type:"number",help:"分块并发数(默认 3)"},{name:"draft",type:"boolean",help:"存草稿而非直接发布(上传视频后存到创作中心草稿箱,不公开)"},{name:"execute",type:"boolean",help:"真正发布投稿;不带(也不带 --draft)则只做 dry-run 校验"}],columns:["status","title","bvid","url"],func:async(W,J)=>{if(!W)throw new L("bilibili upload 需要浏览器会话");const Z=l(String(J.file??"")),Y=String(J.title??"").trim()||P.basename(Z).replace(/\.[^.]+$/,""),$=String(J.tag??"").split(",").map((z)=>z.trim()).filter(Boolean);if(!$.length)throw new D("B站投稿至少需要 1 个标签,用 --tag 传(逗号分隔)");if(J.tid==null||J.tid===""||!Number.isFinite(Number(J.tid)))throw new D("必须用 --tid 指定分区 id(数字)。合法值用 `bilibili partitions` 列举,禁止臆造。");const G=Number(J.tid),X=J.copyright!=null&&J.copyright!==""?Number(J.copyright):1;if(X!==1&&X!==2)throw new D("--copyright 只能是 1(自制) 或 2(转载)");const K=String(J.source??"").trim();if(X===2&&!K)throw new D("转载(copyright=2)必须用 --source 提供转载来源");if(J.cover)u(String(J.cover));const O=String(J.line??f),H=U[O];if(!H)throw new D(`未知上传线路「${O}」,可选:${Object.keys(U).join("/")}`);const T=J.concurrency!=null&&J.concurrency!==""?Number(J.concurrency):3,R=Boolean(J.draft),S=Boolean(J.execute)&&!R;await t(W);await W.evaluate(`(() => { var id=${JSON.stringify(x)}; var el=document.getElementById(id); if(!el){ el=document.createElement('input'); el.type='file'; el.id=id; el.style.cssText='position:fixed;left:-9999px;top:0;'; document.body.appendChild(el);} el.value=''; return true; })()`);await w(W,`#${x}`,[Z]);if(!R&&!S)return{status:"dry-run",title:Y,bvid:"",url:`校验通过:登录态有效、视频已注入就绪、分区 ${G}、标签 [${$.join(", ")}]、版权 ${X===1?"自制":"转载"}。加 --execute 发布,或 --draft 存草稿。`};const Q=await N(W,n,{inputId:x,line:H});if(!Q||Q.error)throw new L(`B站 preupload 失败:${Q&&Q.error||"无返回"}`);for(let z=0;z<Q.total;z++){const M=await N(W,d,{inputId:x,url:Q.url,auth:Q.auth,uploadId:Q.uploadId,idx:z,total:Q.total,size:Q.size,chunkSize:Q.chunkSize});if(!M||!M.ok)throw new L(`B站分块上传失败(${z+1}/${Q.total}):${M&&M.error||"无返回"}`)}const q=await N(W,c,{url:Q.url,auth:Q.auth,uploadId:Q.uploadId,bizId:Q.bizId,name:Q.name,total:Q.total});if(!q||q.error||!q.filename)throw new L(`B站 complete 失败:${q&&q.error||"未拿到 filename"}`);let B="";if(J.cover){const z=s(String(J.cover)),M=await N(W,o,{dataUri:z});if(!M||M.error||!M.url)throw new L(`B站封面上传失败:${M&&M.error||"未拿到 url"}`);B=M.url}let V=null,F=null;if(J.topic!=null&&J.topic!==""){if(!Number.isFinite(Number(J.topic)))throw new D("--topic 必须是 topic_id 数字,用 `bilibili topics --tid <tid>` 取合法值");const z=await N(W,j,{tid:G,topicId:Number(J.topic)});if(!z||z.error)throw new L(`B站话题解析失败:${z&&z.error||"无返回"}`);V=z.topicId;F=z.missionId}if(R){const z={videos:[{filename:q.filename,title:Y,desc:"",cid:q.cid,is_4k:!1,is_8k:!1,is_hdr:!1}],cover:B,cover43:B,ai_cover:0,is_ab_cover:0,ab_cover_info:null,title:Y,copyright:X,tid:G,tag:$.join(","),desc:String(J.desc??""),recreate:-1,dynamic:String(J.dynamic??""),is_only_self:0,space_hidden:2,watermark:{state:0},no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1};if(X===2)z.source=K;if(V!=null){z.topic_id=V;z.topic_detail={from_topic_id:V,from_source:"arc.web.search"};if(F!=null)z.mission_id=F}const M=await N(W,g,{body:z});if(!M||M.error)throw new L(`B站 ${M&&M.error||"draft/add 无返回"}`);const y=M.data&&(M.data.aid!=null?M.data.aid:M.data.draft_id!=null?M.data.draft_id:M.data.id);return{status:"✅ 草稿已保存",title:Y,bvid:y!=null?`draft:${y}`:"",url:"https://member.bilibili.com/platform/upload-manager/article?group=draft"}}const _=J.dtime!=null&&J.dtime!==""?Number(J.dtime):null,A={title:Y,copyright:X,tid:G,tag:$.join(","),mission_id:F,topic_id:V,topic_detail:V!=null?{from_topic_id:V,from_source:"arc.web.recommend"}:null,desc_format_id:9999,desc:String(J.desc??""),dtime:_,recreate:-1,dynamic:String(J.dynamic??""),interactive:0,act_reserve_create:0,no_disturbance:0,porder:null,adorder_type:9,no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},neutral_mark:null,dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1,web_os:1,source:X===2?K:null,watermark:{state:0},cover:B,videos:[{title:Y,desc:"",filename:q.filename,cid:q.cid}]},C=await N(W,p,{meta:A});if(!C||C.error)throw new L(`B站 ${C&&C.error||"add/v3 无返回"}`);const v=C.bvid?String(C.bvid):"";return{status:J.dtime?"✅ 定时投稿已提交":"✅ 投稿成功",title:Y,bvid:v,url:v?`https://www.bilibili.com/video/${v}`:"(已提交,稍后在创作中心查看)"}}});
|
package/clis/chatgpt/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{htmlToMarkdown as
|
|
1
|
+
import{htmlToMarkdown as m}from"@jackwener/opencli/utils";import{ArgumentError as k,AuthRequiredError as _,CommandExecutionError as F,TimeoutError as O}from"@jackwener/opencli/errors";export const CHATGPT_DOMAIN="chatgpt.com",CHATGPT_URL="https://chatgpt.com";const I={instant:{label:"Instant",labels:["Instant","即时","极速"],optionLabels:["Instant","极速","即时"],testIds:["model-switcher-gpt-5-5"],intelligenceOrder:0},medium:{label:"Medium",labels:["Medium","均衡"],optionLabels:["Medium","均衡"],testIds:[],intelligenceOrder:1},high:{label:"High",labels:["High","高级","Thinking","思考"],optionLabels:["High","高级","Thinking","思考"],testIds:["model-switcher-gpt-5-5-thinking"],intelligenceOrder:2},"extra-high":{label:"Extra High",labels:["Extra High","超高"],optionLabels:["Extra High","超高"],testIds:[],intelligenceOrder:3},pro:{label:"Pro",labels:["Pro","进阶专业","专业"],optionLabels:["专业","Pro","进阶专业"],testIds:["model-switcher-gpt-5-5-pro"],intelligenceOrder:4}},w={thinking:"high"};export const CHATGPT_MODEL_CHOICES=[...Object.keys(I),...Object.keys(w)];const G={"deep-research":{label:"Deep Research",labels:["深度研究","Deep Research"]},"web-search":{label:"Web Search",labels:["网页搜索","搜索","Web Search","Search"]}};export const CHATGPT_TOOL_CHOICES=Object.keys(G);const x=['[contenteditable="true"][role="textbox"]','#prompt-textarea[contenteditable="true"]','[aria-label="Chat with ChatGPT"]','[aria-label="与 ChatGPT 聊天"]','[placeholder="Ask anything"]','[placeholder="有问题,尽管问"]',"#prompt-textarea",'[data-testid="prompt-textarea"]'],U='button[data-testid="send-button"]:not([disabled])',L=["#composer-submit-button:not([disabled])"],y=["Send prompt","Send message","Send","发送","发送消息","发送提示"],A=["Close sidebar","关闭边栏"];function N(Q,X){if(!Q||!X)return!1;return Q===X||Q.startsWith(`${X}?`)||Q.startsWith(`${X}#`)}function v(){return`
|
|
2
2
|
const isVisible = (el) => {
|
|
3
3
|
if (!(el instanceof HTMLElement)) return false;
|
|
4
4
|
const style = window.getComputedStyle(el);
|
|
@@ -475,7 +475,7 @@ import{htmlToMarkdown as i}from"@jackwener/opencli/utils";import{ArgumentError a
|
|
|
475
475
|
rows.push({ role, text, html });
|
|
476
476
|
}
|
|
477
477
|
return rows;
|
|
478
|
-
})()`)),"chatgpt visible messages").map((Y,Z)=>({Index:Z+1,Role:Y?.role==="Assistant"?"Assistant":"User",Text:String(Y?.text||"").trim(),Html:String(Y?.html||"")})).filter((Y)=>Y.Text)}function h(Q,{wantMarkdown:X,generating:Y,stableSeconds:Z}){return Q.map(($)=>({Index:$.Index,Role:$.Role,Text:X&&$.Role==="Assistant"&&$.Html?messageHtmlToMarkdown($.Html)||$.Text:$.Text,Generating:Y,StableSeconds:Z}))}export async function getChatGPTDetailRows(Q,{wantMarkdown:X=!1,stableSeconds:Y=0}={}){const Z=await isGenerating(Q),$=await getVisibleMessages(Q);return{messages:$,rows:h($,{wantMarkdown:X,generating:Z,stableSeconds:Y}),generating:Z}}export async function waitForChatGPTDetailRows(Q,{wantMarkdown:X=!1,timeoutSeconds:Y=120,stableSeconds:Z=6}={}){const $=Date.now();let W="",V=0;while(Date.now()-$<Y*1000){const z=await isGenerating(Q),q=await getVisibleMessages(Q),j=JSON.stringify(q.map((J)=>[J.Role,J.Text]));if(!z&&q.length&&q[q.length-1]?.Role==="Assistant")if(j===W){if(!V)V=Date.now();const J=Math.floor((Date.now()-V)/1000);if(J>=Z)return{messages:q,rows:h(q,{wantMarkdown:X,generating:!1,stableSeconds:J}),generating:!1}}else{W=j;V=Date.now()}else{W=j;V=0}await Q.wait(3)}throw new O("chatgpt detail",Y,"Conversation did not finish or stabilize before timeout. Re-run with a higher --timeout if it is still generating.")}export function messageHtmlToMarkdown(Q){try{return
|
|
478
|
+
})()`)),"chatgpt visible messages").map((Y,Z)=>({Index:Z+1,Role:Y?.role==="Assistant"?"Assistant":"User",Text:String(Y?.text||"").trim(),Html:String(Y?.html||"")})).filter((Y)=>Y.Text)}function h(Q,{wantMarkdown:X,generating:Y,stableSeconds:Z}){return Q.map(($)=>({Index:$.Index,Role:$.Role,Text:X&&$.Role==="Assistant"&&$.Html?messageHtmlToMarkdown($.Html)||$.Text:$.Text,Generating:Y,StableSeconds:Z}))}export async function getChatGPTDetailRows(Q,{wantMarkdown:X=!1,stableSeconds:Y=0}={}){const Z=await isGenerating(Q),$=await getVisibleMessages(Q);return{messages:$,rows:h($,{wantMarkdown:X,generating:Z,stableSeconds:Y}),generating:Z}}export async function waitForChatGPTDetailRows(Q,{wantMarkdown:X=!1,timeoutSeconds:Y=120,stableSeconds:Z=6}={}){const $=Date.now();let W="",V=0;while(Date.now()-$<Y*1000){const z=await isGenerating(Q),q=await getVisibleMessages(Q),j=JSON.stringify(q.map((J)=>[J.Role,J.Text]));if(!z&&q.length&&q[q.length-1]?.Role==="Assistant")if(j===W){if(!V)V=Date.now();const J=Math.floor((Date.now()-V)/1000);if(J>=Z)return{messages:q,rows:h(q,{wantMarkdown:X,generating:!1,stableSeconds:J}),generating:!1}}else{W=j;V=Date.now()}else{W=j;V=0}await Q.wait(3)}throw new O("chatgpt detail",Y,"Conversation did not finish or stabilize before timeout. Re-run with a higher --timeout if it is still generating.")}export function messageHtmlToMarkdown(Q){try{return m(Q).trim()}catch{return String(Q||"").replace(/<[^>]+>/g," ").replace(/\s+/g," ").trim()}}export async function getBubbleCount(Q){return(await getVisibleMessages(Q)).length}function M(Q){return String(Q||"").replace(/\s+/g," ").trim()}function E(Q,X){return JSON.stringify([M(Q?.Text),String(X?.Text||"").trim()])}export function getChatGPTResponsePairKeys(Q,X){const Y=M(X);if(!Y)return[];const Z=[];for(let $=0;$<Q.length;$+=1){const W=Q[$];if(W?.Role!=="User"||M(W.Text)!==Y)continue;const V=Q.slice($+1).find((z)=>z?.Role==="Assistant");if(!V||!String(V.Text||"").trim())continue;Z.push(E(W,V))}return Z}export function getChatGPTResponsePairCounts(Q,X){const Y=new Map;for(const Z of getChatGPTResponsePairKeys(Q,X))Y.set(Z,(Y.get(Z)||0)+1);return Y}function l(Q){if(Q.baselinePairCounts instanceof Map)return Q.baselinePairCounts;return new Map(Array.from(Q.baselinePairKeys||[]).map((X)=>[X,1]))}function r(Q,X,Y){const Z=M(X);if(!Z)return"";const $=getChatGPTResponsePairCounts(Q,X);for(let W=Q.length-1;W>=0;W-=1){const V=Q[W];if(V?.Role!=="User"||M(V.Text)!==Z)continue;const z=Q.findIndex((J,K)=>K>W&&J?.Role==="Assistant"&&String(J.Text||"").trim());if(z<0)continue;const q=Q[z],j=E(V,q);if(($.get(j)||0)<=(Y.get(j)||0))continue;return String(q.Text||"").trim()}return""}export async function waitForChatGPTResponse(Q,X,Y,Z,$={}){const W=Date.now();let V="",z=0;const q=l($);while(Date.now()-W<Z*1000){await Q.wait(3);if($.conversationUrl){const K=await currentChatGPTUrl(Q);if(K&&!N(K,$.conversationUrl))throw new F(`ChatGPT navigated away from the target conversation (${$.conversationUrl}); current URL is ${K}`)}if(await isGenerating(Q)){z=0;continue}const j=await getVisibleMessages(Q),J=r(j,Y,q);if(!J||J===String(Y||"").trim())continue;if(J===V){z+=1;if(z>=2)return J}else{V=J;z=0}}throw new O("chatgpt ask",Z,"No ChatGPT response appeared before timeout. Re-run with a higher --timeout if it is still generating.")}export async function getConversationList(Q){await ensureOnChatGPT(Q);if(requireBooleanEvaluateResult(unwrapEvaluateResult(await Q.evaluate(`(() => {
|
|
479
479
|
const button = Array.from(document.querySelectorAll('button'))
|
|
480
480
|
.find((node) => /open sidebar/i.test(node.getAttribute('aria-label') || ''));
|
|
481
481
|
if (button instanceof HTMLElement) {
|
|
@@ -535,7 +535,7 @@ import{htmlToMarkdown as i}from"@jackwener/opencli/utils";import{ArgumentError a
|
|
|
535
535
|
const previewNodes = Array.from(scope.querySelectorAll('img[src], canvas, video, [style*="background-image"]')).filter(isVisibleMedia);
|
|
536
536
|
return previewNodes.length >= names.length;
|
|
537
537
|
})()
|
|
538
|
-
`)),"chatgpt upload preview detection"))return!0}return!1}export async function uploadChatGPTImages(Q,X){const Y=await import("node:fs"),Z=await import("node:path"),$=await prepareChatGPTImagePaths(X);if(!$.ok)return $;const W=$.paths,V=W.map((j)=>Z.default.basename(j));let z=!1;if(Q.setFileInput)try{await Q.setFileInput(W,'input[type="file"]');z=!0}catch(j){const J=String(j?.message||j);if(!J.includes("Unknown action")&&!J.includes("not supported")&&!J.includes("Not allowed")&&!J.includes("No element found"))throw j}if(!z){const j=W.map((K)=>({name:Z.default.basename(K),mime:P(K),base64:Y.default.readFileSync(K).toString("base64")})),J=requireObjectEvaluateResult(unwrapEvaluateResult(await Q.evaluate(`
|
|
538
|
+
`)),"chatgpt upload preview detection"))return!0}return!1}export async function uploadChatGPTImages(Q,X){const Y=await import("node:fs"),Z=await import("node:path"),$=await prepareChatGPTImagePaths(X);if(!$.ok)return $;const W=$.paths,V=W.map((j)=>Z.default.basename(j));let z=!1;if(Q.setFileInput)try{await Q.setFileInput(W,'input[type="file"]');z=!0}catch(j){const J=String(j?.message||j);if(!J.includes("Unknown action")&&!J.includes("not supported")&&!J.includes("Not allowed")&&!J.includes("File access not granted")&&!J.includes("No element found"))throw j}if(!z){const j=W.map((K)=>({name:Z.default.basename(K),mime:P(K),base64:Y.default.readFileSync(K).toString("base64")})),J=requireObjectEvaluateResult(unwrapEvaluateResult(await Q.evaluate(`
|
|
539
539
|
(() => {
|
|
540
540
|
const files = ${JSON.stringify(j)};
|
|
541
541
|
const input = document.querySelector('input[type="file"]');
|
|
@@ -919,7 +919,7 @@ import{htmlToMarkdown as i}from"@jackwener/opencli/utils";import{ArgumentError a
|
|
|
919
919
|
|
|
920
920
|
return { ok: false };
|
|
921
921
|
})()
|
|
922
|
-
`)),Y=typeof X==="boolean"?{ok:X}:requireObjectEvaluateResult(X,"chatgpt project knowledge dialog open");if(Y.ok){if(Y.nativeClick&&typeof Q.nativeClick==="function")try{await Q.nativeClick(Y.nativeClick.x,Y.nativeClick.y)}catch{}if(Y.nativeClick&&typeof Q.click==="function")try{await Q.click('[role="tab"][id$="-sources"]')}catch{}await Q.wait(1);try{await Q.wait({selector:'[role="dialog"], [data-project-home-sources-surface="true"] input[type="file"]',timeout:5})}catch{}return!0}return!1}export async function uploadChatGPTProjectFiles(Q,X,Y){const Z=parseChatGPTProjectId(X),$=await import("node:fs"),W=await import("node:path"),V=await prepareChatGPTFilePaths(Y);if(!V.ok)return{...V,inputError:!0};const z=V.paths;await navigateToProject(Q,Z);await ensureChatGPTLogin(Q,"ChatGPT project file upload requires a logged-in ChatGPT session.");if(!await openProjectKnowledgeDialog(Q))return{ok:!1,reason:'could not find or click the project "Add files" button'};const j=z.map((B)=>W.default.basename(B));let J=!1;if(Q.setFileInput)for(const B of d)try{await Q.setFileInput(z,B);J=!0;break}catch(H){const D=String(H?.message||H);if(!D.includes("Unknown action")&&!D.includes("not supported")&&!D.includes("Not allowed")&&!D.includes("No element found"))throw H}if(!J){const B=z.map((D)=>({name:W.default.basename(D),mime:
|
|
922
|
+
`)),Y=typeof X==="boolean"?{ok:X}:requireObjectEvaluateResult(X,"chatgpt project knowledge dialog open");if(Y.ok){if(Y.nativeClick&&typeof Q.nativeClick==="function")try{await Q.nativeClick(Y.nativeClick.x,Y.nativeClick.y)}catch{}if(Y.nativeClick&&typeof Q.click==="function")try{await Q.click('[role="tab"][id$="-sources"]')}catch{}await Q.wait(1);try{await Q.wait({selector:'[role="dialog"], [data-project-home-sources-surface="true"] input[type="file"]',timeout:5})}catch{}return!0}return!1}export async function uploadChatGPTProjectFiles(Q,X,Y){const Z=parseChatGPTProjectId(X),$=await import("node:fs"),W=await import("node:path"),V=await prepareChatGPTFilePaths(Y);if(!V.ok)return{...V,inputError:!0};const z=V.paths;await navigateToProject(Q,Z);await ensureChatGPTLogin(Q,"ChatGPT project file upload requires a logged-in ChatGPT session.");if(!await openProjectKnowledgeDialog(Q))return{ok:!1,reason:'could not find or click the project "Add files" button'};const j=z.map((B)=>W.default.basename(B));let J=!1;if(Q.setFileInput)for(const B of d)try{await Q.setFileInput(z,B);J=!0;break}catch(H){const D=String(H?.message||H);if(!D.includes("Unknown action")&&!D.includes("not supported")&&!D.includes("Not allowed")&&!D.includes("File access not granted")&&!D.includes("No element found"))throw H}if(!J){const B=z.map((D)=>({name:W.default.basename(D),mime:i(D),base64:$.default.readFileSync(D).toString("base64")})),H=requireObjectEvaluateResult(unwrapEvaluateResult(await Q.evaluate(`
|
|
923
923
|
(() => {
|
|
924
924
|
const files = ${JSON.stringify(B)};
|
|
925
925
|
|
|
@@ -995,7 +995,7 @@ import{htmlToMarkdown as i}from"@jackwener/opencli/utils";import{ArgumentError a
|
|
|
995
995
|
if (!missing.length) return { ok: true };
|
|
996
996
|
return { ok: false, pending: true, reason: 'uploaded file did not appear in project knowledge: ' + missing.join(', ') };
|
|
997
997
|
})()
|
|
998
|
-
`)),"chatgpt project upload confirmation");if(W.ok===!0)return{ok:!0};Z=String(W.reason||Z);if(!W.pending)return{ok:!1,reason:Z};await Q.wait(0.5)}return{ok:!1,reason:Z}}export async function prepareChatGPTFilePaths(Q){const X=await import("node:fs"),Y=await import("node:path"),Z=Q.map(($)=>Y.default.resolve($));for(const $ of Z){if(!X.default.existsSync($))return{ok:!1,reason:`File not found: ${$}`};const W=X.default.statSync($);if(!W.isFile())return{ok:!1,reason:`Not a file: ${$}`};if(W.size>536870912)return{ok:!1,reason:`File too large (${(W.size/1024/1024).toFixed(1)} MB). Max: 512 MB`}}return{ok:!0,paths:Z}}function
|
|
998
|
+
`)),"chatgpt project upload confirmation");if(W.ok===!0)return{ok:!0};Z=String(W.reason||Z);if(!W.pending)return{ok:!1,reason:Z};await Q.wait(0.5)}return{ok:!1,reason:Z}}export async function prepareChatGPTFilePaths(Q){const X=await import("node:fs"),Y=await import("node:path"),Z=Q.map(($)=>Y.default.resolve($));for(const $ of Z){if(!X.default.existsSync($))return{ok:!1,reason:`File not found: ${$}`};const W=X.default.statSync($);if(!W.isFile())return{ok:!1,reason:`Not a file: ${$}`};if(W.size>536870912)return{ok:!1,reason:`File too large (${(W.size/1024/1024).toFixed(1)} MB). Max: 512 MB`}}return{ok:!0,paths:Z}}function i(Q){const X=String(Q||"").toLowerCase();if(X.endsWith(".pdf"))return"application/pdf";if(X.endsWith(".doc"))return"application/msword";if(X.endsWith(".docx"))return"application/vnd.openxmlformats-officedocument.wordprocessingml.document";if(X.endsWith(".xls"))return"application/vnd.ms-excel";if(X.endsWith(".xlsx"))return"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";if(X.endsWith(".ppt"))return"application/vnd.ms-powerpoint";if(X.endsWith(".pptx"))return"application/vnd.openxmlformats-officedocument.presentationml.presentation";if(X.endsWith(".csv"))return"text/csv";if(X.endsWith(".txt"))return"text/plain";if(X.endsWith(".json"))return"application/json";if(X.endsWith(".xml"))return"application/xml";if(X.endsWith(".html")||X.endsWith(".htm"))return"text/html";if(X.endsWith(".md"))return"text/markdown";if(X.endsWith(".py"))return"text/x-python";if(X.endsWith(".js"))return"text/javascript";if(X.endsWith(".ts"))return"application/typescript";if(X.endsWith(".jsx"))return"text/jsx";if(X.endsWith(".tsx"))return"text/tsx";if(X.endsWith(".png"))return"image/png";if(X.endsWith(".jpg")||X.endsWith(".jpeg"))return"image/jpeg";if(X.endsWith(".gif"))return"image/gif";if(X.endsWith(".webp"))return"image/webp";if(X.endsWith(".svg"))return"image/svg+xml";return"application/octet-stream"}export const __test__={COMPOSER_SELECTORS:x,SEND_BUTTON_SELECTOR:U,SEND_BUTTON_FALLBACK_SELECTORS:L,SEND_BUTTON_LABELS:y,CLOSE_SIDEBAR_LABELS:A,buildComposerLocatorScript:v,isSameChatGPTConversation:N,parseChatGPTConversationId,parseChatGPTProjectId,imageMimeFromPath:P,mimeFromFilePath:i,PROJECT_LINK_SELECTOR:c};export async function getChatGPTImageAssets(Q,X){const Y=JSON.stringify(X);return requireArrayEvaluateResult(unwrapEvaluateResult(await Q.evaluate(`
|
|
999
999
|
(async (targetUrls) => {
|
|
1000
1000
|
const blobToDataUrl = (blob) => new Promise((resolve, reject) => {
|
|
1001
1001
|
const reader = new FileReader();
|
package/clis/claude/utils.js
CHANGED
|
@@ -156,7 +156,7 @@ import{ArgumentError as Y,AuthRequiredError as F,CommandExecutionError as W}from
|
|
|
156
156
|
}
|
|
157
157
|
inp.dispatchEvent(new Event('change', { bubbles: true }));
|
|
158
158
|
return { ok: true, via: 'native' };
|
|
159
|
-
})()`);if(!j?.ok)return j;Z=!0}catch(j){const q=String(j?.message||j);if(!q.includes("Unknown action")&&!q.includes("not supported")&&!q.includes("Not allowed"))throw j}if(!Z){const q=K.default.readFileSync(Q).toString("base64"),J=await z.evaluate(`(async () => {
|
|
159
|
+
})()`);if(!j?.ok)return j;Z=!0}catch(j){const q=String(j?.message||j);if(!q.includes("Unknown action")&&!q.includes("not supported")&&!q.includes("Not allowed")&&!q.includes("File access not granted"))throw j}if(!Z){const q=K.default.readFileSync(Q).toString("base64"),J=await z.evaluate(`(async () => {
|
|
160
160
|
var binary = atob('${q}');
|
|
161
161
|
var bytes = new Uint8Array(binary.length);
|
|
162
162
|
for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
package/clis/deepseek/utils.js
CHANGED
|
@@ -55,7 +55,7 @@ import{ArgumentError as F}from"@jackwener/opencli/errors";export const DEEPSEEK_
|
|
|
55
55
|
return { ok: true, method: 'enter' };
|
|
56
56
|
})()`)}export async function getBubbleCount(q){return await q.evaluate(`(() => {
|
|
57
57
|
return document.querySelectorAll('${MESSAGE_SELECTOR}').length;
|
|
58
|
-
})()`)||0}export function parseThinkingResponse(q){if(!q)return null;const z=q.match(/^(Thought for ([\d.]+) seconds?|已思考(用时 ([\d.]+) 秒))\s*/);if(!z)return{response:q,thinking:null,thinking_time:null};const B=z[2]||z[3];return{response:"",thinking:q.slice(z[0].length).trim(),thinking_time:B}}export async function waitForResponse(q,z,B,Q,Z=!1){const Y=Date.now();let $="",
|
|
58
|
+
})()`)||0}export function parseThinkingResponse(q){if(!q)return null;const z=q.match(/^(Thought for ([\d.]+) seconds?|已思考(用时 ([\d.]+) 秒))\s*/);if(!z)return{response:q,thinking:null,thinking_time:null};const B=z[2]||z[3];return{response:"",thinking:q.slice(z[0].length).trim(),thinking_time:B}}export async function waitForResponse(q,z,B,Q,Z=!1){const Y=Date.now();let $="",K=0;while(Date.now()-Y<Q){await q.wait(3);let W;try{W=await q.evaluate(`(() => {
|
|
59
59
|
const bubbles = document.querySelectorAll('${MESSAGE_SELECTOR}');
|
|
60
60
|
const texts = Array.from(bubbles).map(b => (b.innerText || '').trim()).filter(Boolean);
|
|
61
61
|
var last = texts[texts.length - 1] || '';
|
|
@@ -103,7 +103,7 @@ import{ArgumentError as F}from"@jackwener/opencli/errors";export const DEEPSEEK_
|
|
|
103
103
|
answerText: answerEl ? (answerEl.innerText || '').trim() : null,
|
|
104
104
|
thinkTime: thinkTime,
|
|
105
105
|
};
|
|
106
|
-
})()`)}catch{continue}if(!W)continue;const j=W.last;if(j&&W.count>z&&j!==B.trim()){if(j===$){
|
|
106
|
+
})()`)}catch{continue}if(!W)continue;const j=W.last;if(j&&W.count>z&&j!==B.trim()){if(j===$){K++;if(K>=3){if(Z){if(W.thinkText!=null||W.answerText!=null)return{thinking:W.thinkText||"",response:W.answerText||"",thinking_time:W.thinkTime||null};return parseThinkingResponse(j)}return j}}else K=0;$=j}}if(Z&&$)return parseThinkingResponse($);return $||null}export async function getVisibleMessages(q){const z=await q.evaluate(`(() => {
|
|
107
107
|
const msgs = document.querySelectorAll('${MESSAGE_SELECTOR}');
|
|
108
108
|
return Array.from(msgs).map(m => {
|
|
109
109
|
// User messages carry an extra hash-class alongside ds-message
|
|
@@ -164,17 +164,17 @@ import{ArgumentError as F}from"@jackwener/opencli/errors";export const DEEPSEEK_
|
|
|
164
164
|
while (c && !c.querySelector('div[role="button"]')) c = c.parentElement;
|
|
165
165
|
if (!c) return false;
|
|
166
166
|
return !!c.querySelector('img[src], canvas, video, [style*="background-image"], [class*="preview"], [class*="upload"]');
|
|
167
|
-
})()`))return!0}return!1}export async function sendWithFile(q,z,B){const Q=await import("node:fs"),Z=await import("node:path"),Y=Z.default.resolve(z);if(!Q.default.existsSync(Y))return{ok:!1,reason:`File not found: ${Y}`};const $=Q.default.statSync(Y);if($.size>104857600)return{ok:!1,reason:`File too large (${($.size/1024/1024).toFixed(1)} MB). Max: 100 MB`};const
|
|
167
|
+
})()`))return!0}return!1}export async function sendWithFile(q,z,B){const Q=await import("node:fs"),Z=await import("node:path"),Y=Z.default.resolve(z);if(!Q.default.existsSync(Y))return{ok:!1,reason:`File not found: ${Y}`};const $=Q.default.statSync(Y);if($.size>104857600)return{ok:!1,reason:`File too large (${($.size/1024/1024).toFixed(1)} MB). Max: 100 MB`};const K=Z.default.basename(Y);await q.evaluate(`(() => {
|
|
168
168
|
if (document.querySelectorAll('a[href*="/a/chat/s/"]').length > 0) {
|
|
169
169
|
const btn = document.querySelector('div[tabindex="0"][role="button"]');
|
|
170
170
|
if (btn) btn.click();
|
|
171
171
|
}
|
|
172
|
-
})()`);await q.wait(0.5);let W=!1;if(q.setFileInput)try{await q.setFileInput([Y],'input[type="file"]');W=!0}catch(y){const
|
|
173
|
-
var binary = atob('${
|
|
172
|
+
})()`);await q.wait(0.5);let W=!1;if(q.setFileInput)try{await q.setFileInput([Y],'input[type="file"]');W=!0}catch(y){const J=String(y?.message||y);if(!J.includes("Unknown action")&&!J.includes("not supported")&&!J.includes("Not allowed")&&!J.includes("File access not granted"))throw y}if(!W){const J=Q.default.readFileSync(Y).toString("base64"),U=await q.evaluate(`(async () => {
|
|
173
|
+
var binary = atob('${J}');
|
|
174
174
|
var bytes = new Uint8Array(binary.length);
|
|
175
175
|
for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
176
176
|
|
|
177
|
-
var file = new File([bytes], ${JSON.stringify(
|
|
177
|
+
var file = new File([bytes], ${JSON.stringify(K)});
|
|
178
178
|
var dt = new DataTransfer();
|
|
179
179
|
dt.items.add(file);
|
|
180
180
|
|
|
@@ -190,7 +190,7 @@ import{ArgumentError as F}from"@jackwener/opencli/errors";export const DEEPSEEK_
|
|
|
190
190
|
// Use inp.files, not dt.files; assignment transfers ownership
|
|
191
191
|
inp[propsKey].onChange({ target: { files: inp.files } });
|
|
192
192
|
return { ok: true };
|
|
193
|
-
})()`);if(U&&!U.ok)return U}if(!await G(q,
|
|
193
|
+
})()`);if(U&&!U.ok)return U}if(!await G(q,K))return{ok:!1,reason:"file preview did not appear"};let V=!1;for(let y=0;y<15;y++){if(await q.evaluate(`(() => {
|
|
194
194
|
var box = document.querySelector('${TEXTAREA_SELECTOR}');
|
|
195
195
|
if (!box) return false;
|
|
196
196
|
var c = box.parentElement;
|
package/clis/douyin/draft.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as W from"node:fs";import*as J from"node:path";import{cli as M,Strategy as f}from"@jackwener/opencli/registry";import{ArgumentError as N,CommandExecutionError as $}from"@jackwener/opencli/errors";import{setInputFiles as U}from"../_shared/file-inject.js";const j={public:"公开",friends:"好友可见",private:"仅自己可见"},R="https://creator.douyin.com/creator-micro/content/upload",D=120,F=20,O=20,y=200;async function B(q){await q.evaluate(`() => {
|
|
2
2
|
const targets = ['我知道了', '知道了', '关闭'];
|
|
3
3
|
for (const text of targets) {
|
|
4
4
|
const btn = Array.from(document.querySelectorAll('button,[role="button"]'))
|
|
5
5
|
.find((el) => (el.textContent || '').trim() === text);
|
|
6
6
|
if (btn instanceof HTMLElement) btn.click();
|
|
7
7
|
}
|
|
8
|
-
}`)}async function
|
|
8
|
+
}`)}async function b(q){let G={href:"",ready:!1,bodyText:""};for(let z=0;z<D;z+=1){G=await q.evaluate(`() => ({
|
|
9
9
|
href: location.href,
|
|
10
10
|
ready: !!Array.from(document.querySelectorAll('input')).find(
|
|
11
11
|
(el) => (el.placeholder || '').includes('填写作品标题')
|
|
@@ -13,7 +13,7 @@ import*as Y from"node:fs";import*as J from"node:path";import{cli as V,Strategy a
|
|
|
13
13
|
(el) => (el.textContent || '').includes('暂存离开')
|
|
14
14
|
),
|
|
15
15
|
bodyText: document.body?.innerText || ''
|
|
16
|
-
})`);if(G.ready)return;await q.wait({time:0.5})}throw new $("等待抖音草稿编辑页超时",`当前页面: ${G.href||"unknown"}`)}async function
|
|
16
|
+
})`);if(G.ready)return;await q.wait({time:0.5})}throw new $("等待抖音草稿编辑页超时",`当前页面: ${G.href||"unknown"}`)}async function x(q,G){for(let z=0;z<y;z+=1){const H=await q.evaluate(`() => {
|
|
17
17
|
const text = document.body?.innerText || '';
|
|
18
18
|
const uploading = /上传中|上传\\s*\\d+%|视频上传中|处理中/.test(text);
|
|
19
19
|
const saveBtn = Array.from(document.querySelectorAll('button')).find(
|
|
@@ -71,7 +71,7 @@ import*as Y from"node:fs";import*as J from"node:path";import{cli as V,Strategy a
|
|
|
71
71
|
if (!(visibility instanceof HTMLElement)) return false;
|
|
72
72
|
visibility.click();
|
|
73
73
|
return true;
|
|
74
|
-
}`);if(Q)break;await q.wait({time:0.5})}if(!Q)throw new $("填写抖音草稿表单失败: visibility-missing")}async function
|
|
74
|
+
}`);if(Q)break;await q.wait({time:0.5})}if(!Q)throw new $("填写抖音草稿表单失败: visibility-missing")}async function h(q){let G="cover-input-missing";const z=await q.evaluate(`() => Array.from(document.querySelectorAll('input[type="file"]')).length`);for(let H=0;H<F;H+=1){const Q=await q.evaluate(`() => {
|
|
75
75
|
const coverLabel = Array.from(document.querySelectorAll('label')).find(
|
|
76
76
|
(el) => (el.textContent || '').includes('上传新封面')
|
|
77
77
|
);
|
|
@@ -110,7 +110,7 @@ import*as Y from"node:fs";import*as J from"node:path";import{cli as V,Strategy a
|
|
|
110
110
|
root = root.parentElement;
|
|
111
111
|
}
|
|
112
112
|
return '';
|
|
113
|
-
}`}async function
|
|
113
|
+
}`}async function L(q){return await q.evaluate(buildCoverCheckPanelTextJs())||""}async function k(q){let G="",z=!1;for(let H=0;H<O;H+=1){const Q=await L(q),X=Q.includes("检测中"),Z=Q.includes("重新检测")||Q.includes("横/竖双封面缺失");if(X)z=!0;if(z&&Z&&!X)return;G=Q;await q.wait({time:0.5})}throw new $("等待抖音封面处理完成超时",G||"unknown")}async function S(q){const G=60;let z=null;for(let H=0;H<G;H+=1){z=await q.evaluate(`() => {
|
|
114
114
|
const extractCreationId = () => {
|
|
115
115
|
const titleInput = Array.from(document.querySelectorAll('input')).find(
|
|
116
116
|
(el) => (el.placeholder || '').includes('填写作品标题')
|
|
@@ -163,4 +163,4 @@ import*as Y from"node:fs";import*as J from"node:path";import{cli as V,Strategy a
|
|
|
163
163
|
}`);if(z?.ok)break;await q.wait({time:0.5})}if(!z?.ok)throw new $(`点击草稿按钮失败: ${z?.reason||"unknown"}`);if(!z.creationId)throw new $("点击草稿按钮失败: creation-id-missing");return{text:z.text||"暂存离开",creationId:z.creationId}}async function _(q,G){let z={href:"",bodyText:""};for(let H=0;H<40;H+=1){z=await q.evaluate(`() => ({
|
|
164
164
|
href: location.href,
|
|
165
165
|
bodyText: document.body?.innerText || ''
|
|
166
|
-
})`);const Q=z.href||"",X=z.bodyText||"",Z=Q.includes("/creator-micro/content/upload")&&/继续编辑/.test(X),
|
|
166
|
+
})`);const Q=z.href||"",X=z.bodyText||"",Z=Q.includes("/creator-micro/content/upload")&&/继续编辑/.test(X),K=/草稿保存成功|已保存到草稿|存草稿成功|保存成功/.test(X),Y=/\/creator-micro\/content\/(manage|drafts|works)/.test(Q)||/创作中心/.test(X)&&!Q.includes("/content/upload");if(Z||K||Y)return G;await q.wait({time:1})}throw new $("未检测到抖音草稿保存确认",`当前页面: ${z.href||"unknown"}`)}M({site:"douyin",name:"draft",access:"write",description:"上传视频并保存为草稿",domain:"creator.douyin.com",strategy:f.COOKIE,navigateBefore:!1,defaultWindowMode:"foreground",args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"视频标题(≤30字)"},{name:"caption",default:"",help:"正文内容(≤1000字,支持 #话题)"},{name:"cover",default:"",help:"封面图片路径"},{name:"visibility",default:"public",choices:["public","friends","private"]},{name:"timeout",type:"int",default:180,help:"命令超时(秒),视频上传/转码慢时可调大"}],columns:["status","draft_id"],func:async(q,G)=>{const z=J.resolve(G.video);if(!W.existsSync(z))throw new N(`视频文件不存在: ${z}`);const H=J.extname(z).toLowerCase();if(![".mp4",".mov",".avi",".webm"].includes(H))throw new N(`不支持的视频格式: ${H}(支持 mp4/mov/avi/webm)`);const Q=G.title;if(Q.length>30)throw new N("标题不能超过 30 字");const X=G.caption||"";if(X.length>1000)throw new N("正文不能超过 1000 字");const Z=G.cover;if(Z){if(!W.existsSync(J.resolve(Z)))throw new N(`封面文件不存在: ${J.resolve(Z)}`)}const K=j[G.visibility]??j.public;await q.goto(R);await q.wait({selector:'input[type="file"]',timeout:20});await B(q);await U(q,'input[type="file"]',[z]);await b(q);await x(q,K);await B(q);if(Z){const V=await h(q);await U(q,V,[J.resolve(Z)]);await k(q)}await w(q,{title:Q,caption:X,visibilityLabel:K});await q.wait({time:1});const Y=await S(q);return[{status:"✅ 草稿已保存,可在创作中心继续编辑",draft_id:await _(q,Y.creationId)}]}});
|
|
@@ -1,39 +1,18 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
(async () => {
|
|
3
|
-
const images = ${JSON.stringify(V)};
|
|
4
|
-
const input = document.querySelector(${JSON.stringify(D)});
|
|
5
|
-
if (!input) return { ok: false, error: '页面上找不到图片输入框' };
|
|
6
|
-
const dt = new DataTransfer();
|
|
7
|
-
for (const img of images) {
|
|
8
|
-
try {
|
|
9
|
-
const binary = atob(img.base64);
|
|
10
|
-
const bytes = new Uint8Array(binary.length);
|
|
11
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
12
|
-
dt.items.add(new File([new Blob([bytes], { type: img.mimeType })], img.name, { type: img.mimeType }));
|
|
13
|
-
} catch (e) {
|
|
14
|
-
return { ok: false, error: 'File 构造失败: ' + e.message };
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
Object.defineProperty(input, 'files', { value: dt.files, writable: false });
|
|
18
|
-
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
19
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
20
|
-
return { ok: true };
|
|
21
|
-
})()
|
|
22
|
-
`)}async function p(q,J=20000){const K=500,V=Math.ceil(J/K);for(let z=0;z<V;z++){if(await q.evaluate(`(() => {
|
|
1
|
+
import*as y from"node:fs";import*as N from"node:path";import{cli as h,Strategy as d}from"@jackwener/opencli/registry";import{ArgumentError as Z,CommandExecutionError as B}from"@jackwener/opencli/errors";import{assertLiteralContent as T}from"../_shared/content-guard.js";import{setInputFiles as u}from"../_shared/file-inject.js";import{browserFetch as m}from"./_shared/browser-fetch.js";const c="https://creator.douyin.com/creator-micro/content/upload?default-tab=3",x=35,L=20,w=1000,E=2500,l={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".bmp":"image/bmp",".webp":"image/webp",".tif":"image/tiff",".tiff":"image/tiff"},M='input[type="file"][accept*="image"]',A=['input[placeholder="添加作品标题"]','input[placeholder*="标题"]'],n='input[placeholder="添加作品标题"]',_='.zone-container[contenteditable="true"]';function o(q){return q.map((J)=>{const z=N.resolve(J);if(!y.existsSync(z))throw new Z(`图片文件不存在: ${z}`);const K=N.extname(z).toLowerCase();if(!l[K])throw new Z(`不支持的图片格式 "${K}",支持: jpg, png, bmp, webp, tif`);return z})}async function i(q,J=15000){const z=500,K=Math.max(1,Math.ceil(J/z));for(let Q=0;Q<K;Q++){if(await q.evaluate(`(() => !!document.querySelector(${JSON.stringify(M)}))()`))return!0;if(Q<K-1)await q.wait({time:z/1000})}return!1}async function p(q,J){if(!await i(q))return{ok:!1,error:"上传页未渲染出图片输入框(等待 15s 超时)"};try{await u(q,M,J);return{ok:!0}}catch(K){return{ok:!1,error:K instanceof Error?K.message:String(K)}}}async function s(q,J=20000){const z=500,K=Math.ceil(J/z);for(let Q=0;Q<K;Q++){if(await q.evaluate(`(() => {
|
|
23
2
|
const onEditor = location.href.includes('/content/post/image');
|
|
24
|
-
const titleReady = ${JSON.stringify(
|
|
3
|
+
const titleReady = ${JSON.stringify(A)}.some((s) => !!document.querySelector(s));
|
|
25
4
|
return onEditor && titleReady;
|
|
26
|
-
})()`))return!0;await q.wait({time:
|
|
5
|
+
})()`))return!0;await q.wait({time:z/1000})}return!1}async function g(q,J){await q.typeText(n,J);await q.wait({time:0.3});const z=await q.evaluate(`
|
|
27
6
|
((selectors) => {
|
|
28
7
|
const el = selectors.map((s) => document.querySelector(s)).find(Boolean);
|
|
29
8
|
return el ? (el.value || '') : '';
|
|
30
|
-
})(${JSON.stringify(
|
|
31
|
-
`);if(
|
|
9
|
+
})(${JSON.stringify(A)})
|
|
10
|
+
`);if(z.trim()!==J){await q.screenshot({path:"/tmp/douyin_image_title_debug.png"});throw new B(`填写标题失败,期望 "${J}",实际 "${z}"。截图: /tmp/douyin_image_title_debug.png`)}}async function r(q,J){await q.typeText(_,J);await q.wait({time:0.5});const z=await q.evaluate(`
|
|
32
11
|
((selector) => {
|
|
33
12
|
const el = Array.from(document.querySelectorAll(selector)).find((n) => n && n.offsetParent !== null);
|
|
34
13
|
return el ? (el.innerText || el.textContent || '').replace(/\\u200b/g, '').trim() : '';
|
|
35
14
|
})(${JSON.stringify(_)})
|
|
36
|
-
`),
|
|
15
|
+
`),K=J.split("#")[0].replace(/\s+/g,"").trim(),Q=z.replace(/\s+/g,"");if(K&&!Q.includes(K.slice(0,Math.min(K.length,20)))){await q.screenshot({path:"/tmp/douyin_image_body_debug.png"});throw new B(`正文写入校验失败,实际读到 "${z.slice(0,80)}"。截图: /tmp/douyin_image_body_debug.png`)}}async function t(q,J){const z=J?["暂存离开"]:["发布"];return await q.evaluate(`
|
|
37
16
|
((labels) => {
|
|
38
17
|
const isVisible = (el) => {
|
|
39
18
|
if (!el || el.offsetParent === null) return false;
|
|
@@ -48,8 +27,8 @@ import*as R from"node:fs";import*as G from"node:path";import{cli as P,Strategy a
|
|
|
48
27
|
if (btn) { btn.click(); return { ok: true, text: label }; }
|
|
49
28
|
}
|
|
50
29
|
return { ok: false, seen: btns.filter(isVisible).map((b) => norm(b.innerText || b.textContent)).filter(Boolean).slice(0, 12) };
|
|
51
|
-
})(${JSON.stringify(
|
|
52
|
-
`)}
|
|
30
|
+
})(${JSON.stringify(z)})
|
|
31
|
+
`)}h({site:"douyin",name:"publish-image",access:"write",description:"发布图文(图集)到抖音(UI 自动化,最多 35 张图;话题以 #词 写进正文)",domain:"creator.douyin.com",strategy:d.COOKIE,browser:!0,navigateBefore:!1,defaultWindowMode:"foreground",args:[{name:"content",required:!1,positional:!0,help:"正文(字面文本,不展开 @文件 引用;长正文用 --file)"},{name:"file",required:!1,help:"从本机文件读取正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"作品标题(≤20字)"},{name:"images",required:!0,help:"图片路径,逗号分隔,最多35张 (jpg/png/bmp/webp/tif)"},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号"},{name:"draft",type:"bool",default:!1,help:"暂存为草稿,不直接发布"}],columns:["status","detail"],func:async(q,J)=>{if(!q)throw Error("Browser page required");const z=String(J.title??"").trim(),K=J.file?String(J.file).trim():"";let Q=String(J.content??"").trim();if(K){if(Q)throw new Z("正文位置参数和 --file 只能二选一");const V=N.resolve(K);if(!y.existsSync(V))throw new Z(`--file 指向的文件不存在: ${V}`);Q=y.readFileSync(V,"utf-8").trim();if(!Q)throw new Z(`--file 指向的文件内容为空: ${V}`)}const G=String(J.images??"").split(",").map((V)=>V.trim()).filter(Boolean),j=J.topics?String(J.topics).split(",").map((V)=>V.trim().replace(/^#/,"")).filter(Boolean):[],$=Boolean(J.draft);if(!z)throw new Z("--title 必填");if(z.length>L)throw new Z(`标题 ${z.length} 字,需 ≤ ${L} 字`);if(!Q)throw new Z("缺少正文:给位置参数 <content>,或用 --file <本机文件路径>");T(Q,{fileFlag:"--file"});if(Q.length>w)throw new Z(`正文 ${Q.length} 字,需 ≤ ${w} 字`);if(G.length===0)throw new Z("至少提供 1 张图片(--images)");if(G.length>x)throw new Z(`图片过多: ${G.length}(最多 ${x} 张)`);const D=o(G),C=j.length?`${Q} ${j.map((V)=>`#${V}`).join(" ")} `:Q;await q.goto(c);let W="";for(let V=0;V<30;V++){await q.wait({time:0.5});W=await q.evaluate("() => location.href");if(W.includes("creator.douyin.com/creator-micro/content"))break}if(!W.includes("creator.douyin.com")){await q.screenshot({path:"/tmp/douyin_image_redirect_debug.png"});throw new B(`被重定向离开创作者中心(当前 ${W})——登录态可能已过期。`+"用 ppcli douyin login 重新登录。截图: /tmp/douyin_image_redirect_debug.png")}const O=await p(q,D);if(!O.ok){await q.screenshot({path:"/tmp/douyin_image_upload_debug.png"});throw new B(`图片上传失败: ${O.error??"unknown"}。截图: /tmp/douyin_image_upload_debug.png`)}if(!await s(q)){await q.screenshot({path:"/tmp/douyin_image_form_debug.png"});throw new B("上传后编辑页未就绪(标题输入框未出现),页面结构可能变化。截图: /tmp/douyin_image_form_debug.png")}await q.wait({time:E/1000});await g(q,z);await q.wait({time:0.4});await r(q,C);await q.wait({time:0.6});const I=await t(q,$);if(!I?.ok){await q.screenshot({path:"/tmp/douyin_image_submit_debug.png"});throw new B(`找不到「${$?"暂存离开":"发布"}」按钮。可见按钮: ${(I?.seen||[]).join(" | ")||"无"}。截图: /tmp/douyin_image_submit_debug.png`)}const P=$?["暂存成功","保存成功","存入草稿"]:["发布成功","提交成功"];let Y=W,X=!1,F="";for(let V=0;V<24;V++){await q.wait({time:0.5});const H=await q.evaluate(`
|
|
53
32
|
((markers) => {
|
|
54
33
|
const url = location.href;
|
|
55
34
|
let toast = '';
|
|
@@ -60,5 +39,5 @@ import*as R from"node:fs";import*as G from"node:path";import{cli as P,Strategy a
|
|
|
60
39
|
if (text && text.length <= 40 && markers.some((m) => text.includes(m))) { toast = text; break; }
|
|
61
40
|
}
|
|
62
41
|
return { url, toast };
|
|
63
|
-
})(${JSON.stringify(
|
|
64
|
-
`);
|
|
42
|
+
})(${JSON.stringify(P)})
|
|
43
|
+
`);Y=H?.url??Y;if(H?.toast){F=H.toast;break}if(Y.includes("/content/manage")||$&&Y.includes("/content/draft")){X=!0;break}}let R=null;if(!X&&!F&&Y.includes("creator.douyin.com"))try{const H=await m(q,"GET",$?"https://creator.douyin.com/web/api/media/aweme/draft/?aid=1128":"https://creator.douyin.com/janus/douyin/creator/pc/work_list?page_size=20&page_num=1&status=0"),b=H.aweme_list??H.data?.work_list??[],U=Q.replace(/\s+/g,"").slice(0,20),f=Date.now()/1000;R=b.find((v)=>{const S=String(v.desc??v.title??"").replace(/\s+/g,""),k=Number(v.create_time??0);return k>0&&f-k<900&&(S.includes(z.replace(/\s+/g,""))||U&&S.includes(U))})??null}catch{}if(!X&&!F&&!R){await q.screenshot({path:"/tmp/douyin_image_verify_debug.png"});throw new B(`${$?"暂存":"发布"}结果无法确认:未跳转作品管理页、未见成功提示,`+`后台${$?"草稿箱":"作品列表"}回查也没找到本次内容——大概率没发出去,可以安全重试。`+`当前 URL: ${Y}。截图: /tmp/douyin_image_verify_debug.png`)}return[{status:`✅ ${$?"暂存成功":"发布成功"}`,detail:[`"${z}"`,`${D.length}张图片`,j.length?`话题: ${j.map((V)=>`#${V}`).join(" ")}`:"",R?.aweme_id?`aweme_id: ${R.aweme_id}(后台回查坐实)`:"",X?Y:F].filter(Boolean).join(" · ")}]}});
|