publishport-opencli 1.0.19 → 1.0.20

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 CHANGED
@@ -3216,6 +3216,13 @@
3216
3216
  "default": "",
3217
3217
  "required": false,
3218
3218
  "help": "定时发布时间(ISO8601 或 Unix 秒;不填即立即发布)"
3219
+ },
3220
+ {
3221
+ "name": "timeout",
3222
+ "type": "int",
3223
+ "default": 540,
3224
+ "required": false,
3225
+ "help": "命令超时(秒,默认 540)。大视频/上行慢时调大;超时不代表没发成功,先用回查命令确认再决定要不要重发。"
3219
3226
  }
3220
3227
  ],
3221
3228
  "columns": [
@@ -5137,6 +5144,13 @@
5137
5144
  "required": false,
5138
5145
  "help": "真正发布投稿;不带(也不带 --draft)则只做 dry-run 校验"
5139
5146
  },
5147
+ {
5148
+ "name": "timeout",
5149
+ "type": "int",
5150
+ "default": 540,
5151
+ "required": false,
5152
+ "help": "命令超时(秒,默认 540)。大视频/上行慢时调大;超时不代表没发成功,先用回查命令确认再决定要不要重发。"
5153
+ },
5140
5154
  {
5141
5155
  "name": "desc-file",
5142
5156
  "type": "str",
@@ -24210,6 +24224,13 @@
24210
24224
  "required": false,
24211
24225
  "help": "定时发布时间(ISO8601 或 Unix 秒;不填即立即发布)"
24212
24226
  },
24227
+ {
24228
+ "name": "timeout",
24229
+ "type": "int",
24230
+ "default": 540,
24231
+ "required": false,
24232
+ "help": "命令超时(秒,默认 540)。大视频/上行慢时调大;超时不代表没发成功,先用回查命令确认再决定要不要重发。"
24233
+ },
24213
24234
  {
24214
24235
  "name": "file",
24215
24236
  "type": "str",
@@ -41148,6 +41169,13 @@
41148
41169
  "default": "",
41149
41170
  "required": false,
41150
41171
  "help": "Scheduled publish time (ISO8601 or Unix seconds); omit to post now"
41172
+ },
41173
+ {
41174
+ "name": "timeout",
41175
+ "type": "int",
41176
+ "default": 540,
41177
+ "required": false,
41178
+ "help": "命令超时(秒,默认 540)。大视频/上行慢时调大;超时不代表没发成功,先用回查命令确认再决定要不要重发。"
41151
41179
  }
41152
41180
  ],
41153
41181
  "columns": [
@@ -52473,6 +52501,13 @@
52473
52501
  "private"
52474
52502
  ]
52475
52503
  },
52504
+ {
52505
+ "name": "timeout",
52506
+ "type": "int",
52507
+ "default": 540,
52508
+ "required": false,
52509
+ "help": "命令超时(秒,默认 540)。大视频/上行慢时调大;超时不代表没发成功,先用回查命令确认再决定要不要重发。"
52510
+ },
52476
52511
  {
52477
52512
  "name": "file",
52478
52513
  "type": "str",
@@ -1,4 +1,4 @@
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(`
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 const videoTimeoutArg=(q=540)=>({name:"timeout",type:"int",default:q,help:`命令超时(秒,默认 ${q})。大视频/上行慢时调大;`+"超时不代表没发成功,先用回查命令确认再决定要不要重发。"});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 *');
@@ -1,4 +1,4 @@
1
- import{cli as $,Strategy as A}from"@jackwener/opencli/registry";import{ArgumentError as N,CommandExecutionError as H}from"@jackwener/opencli/errors";import{resolveVideoFile as f,setVideoInput as B,unwrap as W,currentUrl as L,parseScheduleDate as O,clickByText as X,waitForUrlIncludes as Y,waitForSelector as M}from"../_shared/video-publish.js";const R="https://baijiahao.baidu.com/builder/rc/edit?type=videoV2",j=["div[class^='video-main-container'] input","input[type='file']"];async function P(q,G){let z=G;if(z.length<=8)z+=" 你不知道的";z=z.slice(0,30);if(!Boolean(W(await q.evaluate(`
1
+ import{cli as $,Strategy as N}from"@jackwener/opencli/registry";import{ArgumentError as f,CommandExecutionError as H}from"@jackwener/opencli/errors";import{resolveVideoFile as A,setVideoInput as B,unwrap as W,currentUrl as L,parseScheduleDate as O,clickByText as X,waitForUrlIncludes as Y,waitForSelector as M,videoTimeoutArg as R}from"../_shared/video-publish.js";const j="https://baijiahao.baidu.com/builder/rc/edit?type=videoV2",P=["div[class^='video-main-container'] input","input[type='file']"];async function V(q,G){let z=G;if(z.length<=8)z+=" 你不知道的";z=z.slice(0,30);if(!Boolean(W(await q.evaluate(`
2
2
  ((text) => {
3
3
  const input = document.querySelector('input[placeholder="添加标题获得更多推荐"], textarea[placeholder="添加标题获得更多推荐"], [placeholder*="添加标题"]');
4
4
  if (!input) return false;
@@ -10,7 +10,7 @@ import{cli as $,Strategy as A}from"@jackwener/opencli/registry";import{ArgumentE
10
10
  input.dispatchEvent(new Event('change', { bubbles: true }));
11
11
  return true;
12
12
  })(${JSON.stringify(z)})
13
- `))))throw new H("找不到百家号标题输入框");return z}async function V(q){const G=Date.now()+300000;while(Date.now()<G){const z=W(await q.evaluate(`
13
+ `))))throw new H("找不到百家号标题输入框");return z}async function _(q){const G=Date.now()+300000;while(Date.now()<G){const z=W(await q.evaluate(`
14
14
  (() => {
15
15
  const has = (txt) => Array.from(document.querySelectorAll('div .cover-overlay'))
16
16
  .some((el) => (el.innerText || el.textContent || '').includes(txt));
@@ -18,7 +18,7 @@ import{cli as $,Strategy as A}from"@jackwener/opencli/registry";import{ArgumentE
18
18
  if (has('上传中')) return 'uploading';
19
19
  return 'done';
20
20
  })()
21
- `));if(z==="failed")return!1;if(z==="done")return!0;await q.wait({time:2})}return!1}async function _(q,G){const z=G.getDate(),K=G.getMonth()+1,Q=z>9?`${K}月${z}日`:`${K}月0${z}日`;if(!await M(q,"div.select-wrap",5000))throw new H("百家号定时选择器未出现");for(let J=0;J<3;J++){await q.evaluate("(() => { const w = document.querySelectorAll('div.select-wrap')[0]; if (w) w.click(); })()");if(await M(q,"div.rc-virtual-list div.cheetah-select-item",5000))break}await q.wait({time:2});await q.evaluate(`
21
+ `));if(z==="failed")return!1;if(z==="done")return!0;await q.wait({time:2})}return!1}async function b(q,G){const z=G.getDate(),K=G.getMonth()+1,Q=z>9?`${K}月${z}日`:`${K}月0${z}日`;if(!await M(q,"div.select-wrap",5000))throw new H("百家号定时选择器未出现");for(let J=0;J<3;J++){await q.evaluate("(() => { const w = document.querySelectorAll('div.select-wrap')[0]; if (w) w.click(); })()");if(await M(q,"div.rc-virtual-list div.cheetah-select-item",5000))break}await q.wait({time:2});await q.evaluate(`
22
22
  ((wantDay) => {
23
23
  for (const el of document.querySelectorAll('div.rc-virtual-list div.cheetah-select-item')) {
24
24
  if ((el.innerText || el.textContent || '').includes(wantDay)) { el.click(); return true; }
@@ -35,7 +35,7 @@ import{cli as $,Strategy as A}from"@jackwener/opencli/registry";import{ArgumentE
35
35
  opts[idx].click();
36
36
  return true;
37
37
  })()
38
- `);await q.wait({time:2});await X(q,"定时发布",{timeoutMs:5000})}async function b(q,G){if(!Boolean(W(await q.evaluate(`
38
+ `);await q.wait({time:2});await X(q,"定时发布",{timeoutMs:5000})}async function v(q,G){if(!Boolean(W(await q.evaluate(`
39
39
  (() => {
40
40
  const norm = (v) => (v || '').replace(/\\s+/g, ' ').trim();
41
41
  for (const c of document.querySelectorAll('div.op-btn-outter-content')) {
@@ -47,7 +47,7 @@ import{cli as $,Strategy as A}from"@jackwener/opencli/registry";import{ArgumentE
47
47
  }
48
48
  return false;
49
49
  })()
50
- `))))throw new H("找不到百家号「定时发布」入口");if(!await M(q,"div.select-wrap",3000))throw new H("百家号定时面板未展开");await q.wait({time:2});await _(q,G)}$({site:"baijiahao",name:"publish",access:"write",description:"发布视频到百家号(一条命令完成上传+填写+发布;可选定时)",domain:"baijiahao.baidu.com",strategy:A.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"视频标题(不足 8 字会自动补「你不知道的」,截断到 30 字)"},{name:"schedule",default:"",help:"定时发布时间(ISO8601 或 Unix 秒;不填即立即发布)"}],columns:["status","title","url"],func:async(q,G)=>{const z=String(G.title??"").trim();if(!z)throw new N("--title is required");const K=f(G.video),Q=O(G.schedule);await q.goto(R);if(!await Y(q,"builder/rc/edit",60000))throw new H("未能进入百家号视频编辑页(登录态可能失效,请先 ppcli baijiahao login)");if(!await M(q,"div[class^='video-main-container'] input, input[type='file']",30000))throw new H("百家号视频上传输入框未出现");await B(q,j,[K]);if(!await M(q,"div#formMain",60000))throw new H("百家号视频发布表单未出现");await q.wait({time:1});const J=await P(q,z);if(!await V(q))throw new H(`百家号视频上传失败: ${K}`);const Z=Date.now()+120000;while(Date.now()<Z){if(Boolean(W(await q.evaluate("(() => !!document.querySelector('div.cheetah-spin-container img'))()"))))break;await q.wait({time:3})}if(Q)await b(q,Q);else await X(q,"发布",{exact:!0,timeoutMs:1e4});await q.wait({time:2});if(Boolean(W(await q.evaluate(`
50
+ `))))throw new H("找不到百家号「定时发布」入口");if(!await M(q,"div.select-wrap",3000))throw new H("百家号定时面板未展开");await q.wait({time:2});await b(q,G)}$({site:"baijiahao",name:"publish",access:"write",description:"发布视频到百家号(一条命令完成上传+填写+发布;可选定时)",domain:"baijiahao.baidu.com",strategy:N.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"视频标题(不足 8 字会自动补「你不知道的」,截断到 30 字)"},{name:"schedule",default:"",help:"定时发布时间(ISO8601 或 Unix 秒;不填即立即发布)"},R()],columns:["status","title","url"],func:async(q,G)=>{const z=String(G.title??"").trim();if(!z)throw new f("--title is required");const K=A(G.video),Q=O(G.schedule);await q.goto(j);if(!await Y(q,"builder/rc/edit",60000))throw new H("未能进入百家号视频编辑页(登录态可能失效,请先 ppcli baijiahao login)");if(!await M(q,"div[class^='video-main-container'] input, input[type='file']",30000))throw new H("百家号视频上传输入框未出现");await B(q,P,[K]);if(!await M(q,"div#formMain",60000))throw new H("百家号视频发布表单未出现");await q.wait({time:1});const J=await V(q,z);if(!await _(q))throw new H(`百家号视频上传失败: ${K}`);const Z=Date.now()+120000;while(Date.now()<Z){if(Boolean(W(await q.evaluate("(() => !!document.querySelector('div.cheetah-spin-container img'))()"))))break;await q.wait({time:3})}if(Q)await v(q,Q);else await X(q,"发布",{exact:!0,timeoutMs:1e4});await q.wait({time:2});if(Boolean(W(await q.evaluate(`
51
51
  (() => {
52
52
  for (const el of document.querySelectorAll('div.passMod_dialog-container')) {
53
53
  if (el.offsetParent !== null && (el.innerText || '').includes('百度安全验证')) return true;
@@ -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 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",fromFile:"desc-file",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}`:"(已提交,稍后在创作中心查看)"}}});
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,videoTimeoutArg as w}from"../_shared/video-publish.js";import{setInputFiles as k}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",m=["SESSDATA","bili_jct","DedeUserID"],n="https://member.bilibili.com/platform/home",x="__pp_bili_video_input";function d(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 c(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 o(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 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]);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 C=new FileReader;C.onload=function(){F(String(C.result))};C.onerror=function(){_(Error("FileReader 读取转码结果失败"))};C.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 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.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 s(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 t(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 i(W){for(let J=0;J<8;J++){await W.goto(n);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=m.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",fromFile:"desc-file",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 校验"},w()],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 i(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 k(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,d,{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,c,{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,j,{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=t(String(J.cover)),M=await N(W,p,{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,o,{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,s,{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,C={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}]},A=await N(W,g,{meta:C});if(!A||A.error)throw new L(`B站 ${A&&A.error||"add/v3 无返回"}`);const v=A.bvid?String(A.bvid):"";return{status:J.dtime?"✅ 定时投稿已提交":"✅ 投稿成功",title:Y,bvid:v,url:v?`https://www.bilibili.com/video/${v}`:"(已提交,稍后在创作中心查看)"}}});
@@ -1,4 +1,4 @@
1
- import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentError as O,CommandExecutionError as H}from"@jackwener/opencli/errors";import{resolveVideoFile as _,resolveImageFile as U,setVideoInput as Z,unwrap as W,currentUrl as B,parseScheduleDate as C,clickByText as M,countByText as f,existsByText as I,waitForUrlIncludes as j,waitForSelector as A}from"../_shared/video-publish.js";const D="https://cp.kuaishou.com/article/publish/video",S="button[class^='_upload-btn']",N=["input[class^='upload-btn-input']","div.progress-div [class^='upload-btn-input']","button[class^='_upload-btn'] input[type='file']","input[type='file']"];async function b(q,z){if(!Boolean(W(await q.evaluate(`
1
+ import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentError as O,CommandExecutionError as H}from"@jackwener/opencli/errors";import{resolveVideoFile as _,resolveImageFile as U,setVideoInput as Z,unwrap as W,currentUrl as B,parseScheduleDate as C,clickByText as M,countByText as f,existsByText as I,waitForUrlIncludes as j,waitForSelector as N,videoTimeoutArg as D}from"../_shared/video-publish.js";const S="https://cp.kuaishou.com/article/publish/video",b="button[class^='_upload-btn']",A=["input[class^='upload-btn-input']","div.progress-div [class^='upload-btn-input']","button[class^='_upload-btn'] input[type='file']","input[type='file']"];async function y(q,z){if(!Boolean(W(await q.evaluate(`
2
2
  (() => {
3
3
  const norm = (v) => (v || '').replace(/\\s+/g, ' ').trim();
4
4
  let label = null;
@@ -17,7 +17,7 @@ import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
17
17
  editable.focus();
18
18
  return true;
19
19
  })()
20
- `))))throw new H("找不到快手「描述」输入框");try{await q.pressKey("Backspace")}catch{}try{await q.pressKey("Control+a")}catch{}try{await q.pressKey("Delete")}catch{}await q.insertText(z);await q.pressKey("Enter")}async function y(q,z){const G=(X)=>String(X).padStart(2,"0"),J=`${z.getFullYear()}-${G(z.getMonth()+1)}-${G(z.getDate())} ${G(z.getHours())}:${G(z.getMinutes())}:${G(z.getSeconds())}`;await M(q,"定时发布",{timeoutMs:5000});await q.wait({time:2});await q.click('input[placeholder="选择日期时间"]');await q.wait({time:1});if(!Boolean(W(await q.evaluate(`
20
+ `))))throw new H("找不到快手「描述」输入框");try{await q.pressKey("Backspace")}catch{}try{await q.pressKey("Control+a")}catch{}try{await q.pressKey("Delete")}catch{}await q.insertText(z);await q.pressKey("Enter")}async function P(q,z){const G=(X)=>String(X).padStart(2,"0"),J=`${z.getFullYear()}-${G(z.getMonth()+1)}-${G(z.getDate())} ${G(z.getHours())}:${G(z.getMinutes())}:${G(z.getSeconds())}`;await M(q,"定时发布",{timeoutMs:5000});await q.wait({time:2});await q.click('input[placeholder="选择日期时间"]');await q.wait({time:1});if(!Boolean(W(await q.evaluate(`
21
21
  ((newValue) => {
22
22
  const input = document.querySelector('input[placeholder="选择日期时间"]');
23
23
  if (!input) return false;
@@ -27,7 +27,7 @@ import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
27
27
  input.dispatchEvent(new Event('change', { bubbles: true }));
28
28
  return true;
29
29
  })(${JSON.stringify(J)})
30
- `))))throw new H("找不到快手定时发布时间选择器输入框");await q.wait({time:1});await q.pressKey("Enter");await q.wait({time:2})}async function P(q,z){if(!z)return;if(!Boolean(W(await q.evaluate(`
30
+ `))))throw new H("找不到快手定时发布时间选择器输入框");await q.wait({time:1});await q.pressKey("Enter");await q.wait({time:2})}async function T(q,z){if(!z)return;if(!Boolean(W(await q.evaluate(`
31
31
  (() => {
32
32
  const norm = (v) => (v || '').replace(/\\s+/g, ' ').trim();
33
33
  let label = null;
@@ -42,7 +42,7 @@ import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
42
42
  first.click();
43
43
  return true;
44
44
  })()
45
- `))))throw new H("找不到快手「封面设置」入口");if(!await A(q,'div[role="document"].ant-modal',30000))throw new H("封面设置弹窗未出现");await M(q,"上传封面",{exact:!0,timeoutMs:1e4});await q.wait({time:0.5});await Z(q,['div[role="document"].ant-modal input[type="file"]','input[type="file"]'],[z]);await q.wait({time:1});await M(q,"确认",{exact:!0,timeoutMs:1e4});const J=Date.now()+30000;while(Date.now()<J){if(!Boolean(W(await q.evaluate(`(() => { const m = document.querySelector('div[role="document"].ant-modal'); return !!(m && m.getBoundingClientRect().width > 0); })()`))))break;await q.wait({time:0.5})}}V({site:"kuaishou",name:"publish",access:"write",description:"发布视频到快手(一条命令完成上传+填写+发布;可选封面/定时/话题)",domain:"cp.kuaishou.com",strategy:v.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"作品标题(无 --desc 时作为描述)"},{name:"desc",fromFile:!0,default:"",help:"视频描述(默认用标题)"},{name:"tags",default:"",help:"话题标签,逗号分隔,最多取前 3 个(不含 #)"},{name:"cover",default:"",help:"封面图片路径(可选)"},{name:"schedule",default:"",help:"定时发布时间(ISO8601 或 Unix 秒;不填即立即发布)"}],columns:["status","title","url"],func:async(q,z)=>{const G=String(z.title??"").trim();if(!G)throw new O("快手视频上传时,title 是必须的");const J=_(z.video),Q=z.cover?U(z.cover):"",X=String(z.desc||"").trim()||G,R=z.tags?String(z.tags).split(",").map((K)=>K.trim()).filter(Boolean):[],Y=C(z.schedule);await q.goto(D);if(!await j(q,"article/publish/video",30000))throw new H("未能进入快手上传页(登录态可能失效,请先 ppcli kuaishou login)");if(!await A(q,S,15000))throw new H("快手上传按钮未出现");await Z(q,N,[J]);await q.wait({time:2});if(await I(q,"我知道了",{exact:!0}))await M(q,"我知道了",{exact:!0});await q.evaluate(`
45
+ `))))throw new H("找不到快手「封面设置」入口");if(!await N(q,'div[role="document"].ant-modal',30000))throw new H("封面设置弹窗未出现");await M(q,"上传封面",{exact:!0,timeoutMs:1e4});await q.wait({time:0.5});await Z(q,['div[role="document"].ant-modal input[type="file"]','input[type="file"]'],[z]);await q.wait({time:1});await M(q,"确认",{exact:!0,timeoutMs:1e4});const J=Date.now()+30000;while(Date.now()<J){if(!Boolean(W(await q.evaluate(`(() => { const m = document.querySelector('div[role="document"].ant-modal'); return !!(m && m.getBoundingClientRect().width > 0); })()`))))break;await q.wait({time:0.5})}}V({site:"kuaishou",name:"publish",access:"write",description:"发布视频到快手(一条命令完成上传+填写+发布;可选封面/定时/话题)",domain:"cp.kuaishou.com",strategy:v.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"作品标题(无 --desc 时作为描述)"},{name:"desc",fromFile:!0,default:"",help:"视频描述(默认用标题)"},{name:"tags",default:"",help:"话题标签,逗号分隔,最多取前 3 个(不含 #)"},{name:"cover",default:"",help:"封面图片路径(可选)"},{name:"schedule",default:"",help:"定时发布时间(ISO8601 或 Unix 秒;不填即立即发布)"},D()],columns:["status","title","url"],func:async(q,z)=>{const G=String(z.title??"").trim();if(!G)throw new O("快手视频上传时,title 是必须的");const J=_(z.video),Q=z.cover?U(z.cover):"",X=String(z.desc||"").trim()||G,R=z.tags?String(z.tags).split(",").map((K)=>K.trim()).filter(Boolean):[],Y=C(z.schedule);await q.goto(S);if(!await j(q,"article/publish/video",30000))throw new H("未能进入快手上传页(登录态可能失效,请先 ppcli kuaishou login)");if(!await N(q,b,15000))throw new H("快手上传按钮未出现");await Z(q,A,[J]);await q.wait({time:2});if(await I(q,"我知道了",{exact:!0}))await M(q,"我知道了",{exact:!0});await q.evaluate(`
46
46
  (() => {
47
47
  const tip = document.querySelector('div[id^="react-joyride-step"] div[role="alertdialog"]');
48
48
  if (!tip) return false;
@@ -50,4 +50,4 @@ import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
50
50
  if (skip) { skip.click(); return true; }
51
51
  return false;
52
52
  })()
53
- `);await b(q,X);for(const K of R.slice(0,3)){await q.insertText(`#${K} `);await q.wait({time:2})}const F=60;for(let K=0;K<F;K++){if(await f(q,"上传中")===0)break;if(await f(q,"上传失败")>0)await Z(q,N,[J]);await q.wait({time:2})}if(Q)await P(q,Q);if(Y)await y(q,Y);const L=Date.now()+60000;let $=!1;while(Date.now()<L){await M(q,"发布",{exact:!0});await q.wait({time:1});await M(q,"确认发布");if(await j(q,"article/manage",5000)){$=!0;break}await q.wait({time:1})}if(!$)throw new H("快手发布未确认成功(未跳转到作品管理页),请检查页面状态");return[{status:Y?"✅ 定时发布已提交":"✅ 发布成功",title:G,url:await B(q)}]}});
53
+ `);await y(q,X);for(const K of R.slice(0,3)){await q.insertText(`#${K} `);await q.wait({time:2})}const F=60;for(let K=0;K<F;K++){if(await f(q,"上传中")===0)break;if(await f(q,"上传失败")>0)await Z(q,A,[J]);await q.wait({time:2})}if(Q)await T(q,Q);if(Y)await P(q,Y);const L=Date.now()+60000;let $=!1;while(Date.now()<L){await M(q,"发布",{exact:!0});await q.wait({time:1});await M(q,"确认发布");if(await j(q,"article/manage",5000)){$=!0;break}await q.wait({time:1})}if(!$)throw new H("快手发布未确认成功(未跳转到作品管理页),请检查页面状态");return[{status:Y?"✅ 定时发布已提交":"✅ 发布成功",title:G,url:await B(q)}]}});
@@ -1,10 +1,10 @@
1
- import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentError as N,CommandExecutionError as Q}from"@jackwener/opencli/errors";import{resolveVideoFile as F,setVideoInput as j,unwrap as Z,parseScheduleDate as I,waitForUrlIncludes as x,waitForSelector as G}from"../_shared/video-publish.js";const D="https://www.tiktok.com/creator-center/upload",A=['input[type="file"][accept*="video"]','input[type="file"]'],B="div.public-DraftEditor-content",b="div.btn-post",M="#\\:r9\\:";async function f(q,z){return Boolean(Z(await q.evaluate(`((s) => !!document.querySelector(s))(${JSON.stringify(z)})`)))}async function P(q,z,H){await q.click(B);await q.pressKey("End");await q.pressKey("Control+a");await q.pressKey("Delete");await q.pressKey("End");await q.wait({time:1});await q.insertText(z);await q.wait({time:1});await q.pressKey("End");await q.pressKey("Enter");for(const J of H){await q.pressKey("End");await q.wait({time:1});await q.insertText(`#${J} `);await q.pressKey("Space");await q.wait({time:1});await q.pressKey("Backspace");await q.pressKey("End")}}async function U(q,z){const H=Date.now()+1800000;while(Date.now()<H){if(Z(await q.evaluate(`
1
+ import{cli as V,Strategy as N}from"@jackwener/opencli/registry";import{ArgumentError as v,CommandExecutionError as Q}from"@jackwener/opencli/errors";import{resolveVideoFile as F,setVideoInput as j,unwrap as Z,parseScheduleDate as I,waitForUrlIncludes as x,waitForSelector as G,videoTimeoutArg as D}from"../_shared/video-publish.js";const P="https://www.tiktok.com/creator-center/upload",B=['input[type="file"][accept*="video"]','input[type="file"]'],M="div.public-DraftEditor-content",b="div.btn-post",y="#\\:r9\\:";async function f(q,z){return Boolean(Z(await q.evaluate(`((s) => !!document.querySelector(s))(${JSON.stringify(z)})`)))}async function U(q,z,H){await q.click(M);await q.pressKey("End");await q.pressKey("Control+a");await q.pressKey("Delete");await q.pressKey("End");await q.wait({time:1});await q.insertText(z);await q.wait({time:1});await q.pressKey("End");await q.pressKey("Enter");for(const J of H){await q.pressKey("End");await q.wait({time:1});await q.insertText(`#${J} `);await q.pressKey("Space");await q.wait({time:1});await q.pressKey("Backspace");await q.pressKey("End")}}async function L(q,z){const H=Date.now()+1800000;while(Date.now()<H){if(Z(await q.evaluate(`
2
2
  (() => {
3
3
  const btn = document.querySelector('${b} > button');
4
4
  if (!btn) return null;
5
5
  return btn.getAttribute('disabled') === null;
6
6
  })()
7
- `))===!0)return;if(await f(q,'button[aria-label="Select file"]'))await j(q,['button[aria-label="Select file"] ~ input[type="file"]',...A],[z]);await q.wait({time:2})}throw new Q("TikTok 视频上传超时")}async function L(q,z){if(!Boolean(Z(await q.evaluate(`
7
+ `))===!0)return;if(await f(q,'button[aria-label="Select file"]'))await j(q,['button[aria-label="Select file"] ~ input[type="file"]',...B],[z]);await q.wait({time:2})}throw new Q("TikTok 视频上传超时")}async function k(q,z){if(!Boolean(Z(await q.evaluate(`
8
8
  (() => {
9
9
  const norm = (v) => (v || '').replace(/\\s+/g, ' ').trim();
10
10
  // get_by_label('Schedule')
@@ -18,7 +18,7 @@ import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
18
18
  el.click();
19
19
  return true;
20
20
  })()
21
- `))))throw new Q("找不到 TikTok 定时发布开关 (Schedule)");await q.wait({time:1});const J=(Y)=>String(Y).padStart(2,"0"),$=z.getDate(),K=z.getMonth()+1,W=J(z.getHours()),y=J(Math.floor(z.getMinutes()/5)),X=Z(await q.evaluate(`
21
+ `))))throw new Q("找不到 TikTok 定时发布开关 (Schedule)");await q.wait({time:1});const J=(Y)=>String(Y).padStart(2,"0"),$=z.getDate(),K=z.getMonth()+1,W=J(z.getHours()),A=J(Math.floor(z.getMinutes()/5)),X=Z(await q.evaluate(`
22
22
  ((targetDay, targetMonth) => {
23
23
  const picker = document.querySelector('div.scheduled-picker');
24
24
  if (!picker) return { ok: false, reason: 'no scheduled-picker' };
@@ -71,7 +71,7 @@ import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
71
71
  if ((el.innerText || '').includes(minuteStr)) { el.click(); return true; }
72
72
  }
73
73
  return false;
74
- })(${JSON.stringify(y)})
74
+ })(${JSON.stringify(A)})
75
75
  `);await q.evaluate(`
76
76
  (() => {
77
77
  for (const h of document.querySelectorAll('h1')) {
@@ -79,4 +79,4 @@ import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
79
79
  }
80
80
  return false;
81
81
  })()
82
- `)}async function k(q){const z=Date.now()+300000;while(Date.now()<z){if(await f(q,b))await q.evaluate(`(() => { const b = document.querySelector('${b}'); if (b) b.click(); })()`);await q.wait({time:1});if(await f(q,M))return;await q.wait({time:0.5})}if(await f(q,M))return;throw new Q("TikTok 发布未确认成功(未出现成功标识)")}V({site:"tiktok",name:"publish",access:"write",description:"Publish a video to TikTok (one command: upload + caption + publish; optional schedule)",domain:"www.tiktok.com",strategy:v.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"Video file path"},{name:"title",required:!0,help:"Caption / title text"},{name:"tags",default:"",help:"Comma-separated hashtags (without #)"},{name:"schedule",default:"",help:"Scheduled publish time (ISO8601 or Unix seconds); omit to post now"}],columns:["status","title","url"],func:async(q,z)=>{const H=String(z.title??"").trim();if(!H)throw new N("--title is required");const J=F(z.video),$=z.tags?String(z.tags).split(",").map((W)=>W.trim()).filter(Boolean):[],K=I(z.schedule);await q.goto(D);if(!await x(q,"tiktokstudio/upload",30000))throw new Q("未能进入 TikTok Studio 上传页(登录态可能失效,请先 ppcli tiktok login)");await G(q,'iframe[data-tt="Upload_index_iframe"], div.upload-container',1e4);await G(q,'input[type="file"]',15000);await j(q,A,[J]);if(!await G(q,B,60000))throw new Q("TikTok 文案编辑器未出现");await P(q,H,$);await U(q,J);if(K)await L(q,K);await k(q);return[{status:K?"✅ Scheduled":"✅ Published",title:H,url:await q.evaluate("() => location.href").then(Z)}]}});
82
+ `)}async function R(q){const z=Date.now()+300000;while(Date.now()<z){if(await f(q,b))await q.evaluate(`(() => { const b = document.querySelector('${b}'); if (b) b.click(); })()`);await q.wait({time:1});if(await f(q,y))return;await q.wait({time:0.5})}if(await f(q,y))return;throw new Q("TikTok 发布未确认成功(未出现成功标识)")}V({site:"tiktok",name:"publish",access:"write",description:"Publish a video to TikTok (one command: upload + caption + publish; optional schedule)",domain:"www.tiktok.com",strategy:N.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"Video file path"},{name:"title",required:!0,help:"Caption / title text"},{name:"tags",default:"",help:"Comma-separated hashtags (without #)"},{name:"schedule",default:"",help:"Scheduled publish time (ISO8601 or Unix seconds); omit to post now"},D()],columns:["status","title","url"],func:async(q,z)=>{const H=String(z.title??"").trim();if(!H)throw new v("--title is required");const J=F(z.video),$=z.tags?String(z.tags).split(",").map((W)=>W.trim()).filter(Boolean):[],K=I(z.schedule);await q.goto(P);if(!await x(q,"tiktokstudio/upload",30000))throw new Q("未能进入 TikTok Studio 上传页(登录态可能失效,请先 ppcli tiktok login)");await G(q,'iframe[data-tt="Upload_index_iframe"], div.upload-container',1e4);await G(q,'input[type="file"]',15000);await j(q,B,[J]);if(!await G(q,M,60000))throw new Q("TikTok 文案编辑器未出现");await U(q,H,$);await L(q,J);if(K)await k(q,K);await R(q);return[{status:K?"✅ Scheduled":"✅ Published",title:H,url:await q.evaluate("() => location.href").then(Z)}]}});
@@ -1,4 +1,4 @@
1
- import{cli as U,Strategy as _}from"@jackwener/opencli/registry";import{ArgumentError as C,CommandExecutionError as X}from"@jackwener/opencli/errors";import{resolveVideoFile as h,resolveImageFile as y,setVideoInput as B,unwrap as Z,currentUrl as v,waitForSelector as D}from"../_shared/video-publish.js";const F="https://www.youtube.com/upload",O={public:"PUBLIC",unlisted:"UNLISTED",private:"PRIVATE"},T=`
1
+ import{cli as U,Strategy as _}from"@jackwener/opencli/registry";import{ArgumentError as C,CommandExecutionError as X}from"@jackwener/opencli/errors";import{resolveVideoFile as h,resolveImageFile as y,setVideoInput as B,unwrap as Z,currentUrl as F,waitForSelector as D,videoTimeoutArg as v}from"../_shared/video-publish.js";const S="https://www.youtube.com/upload",O={public:"PUBLIC",unlisted:"UNLISTED",private:"PRIVATE"},Y=`
2
2
  function __ytNorm(v) { return (v || '').replace(/\\s+/g, ' ').trim(); }
3
3
  function __ytVisible(el) {
4
4
  if (!el || el.offsetParent === null) return false;
@@ -29,17 +29,17 @@ import{cli as U,Strategy as _}from"@jackwener/opencli/registry";import{ArgumentE
29
29
  }
30
30
  `;async function G(q,z,H=4000){const Q=Date.now()+H;for(;;){if(Boolean(Z(await q.evaluate(`
31
31
  (() => {
32
- ${T}
32
+ ${Y}
33
33
  var el = __ytResolve(${JSON.stringify(z)});
34
34
  if (!el) return false;
35
35
  el.click();
36
36
  return true;
37
37
  })()
38
38
  `))))return!0;if(Date.now()>=Q)return!1;await q.wait({time:0.4})}}async function M(q,z){return Boolean(Z(await q.evaluate(`
39
- (() => { ${T} return !!__ytResolve(${JSON.stringify(z)}); })()
40
- `)))}async function S(q){try{await q.evaluate("() => { const a = document.activeElement; if (a && a.blur) a.blur(); }")}catch{}try{if(Boolean(Z(await q.evaluate(`
41
- (() => { ${T} return !!__ytResolve('tp-yt-iron-dropdown'); })()
42
- `)))){await q.pressKey("Escape");await q.wait({time:0.2})}}catch{}}async function R(q,z,H){if(!await D(q,z,30000))throw new X(`YouTube 输入框未出现: ${z}`);await q.click(z);await q.pressKey("Control+a");await q.pressKey("Delete");await q.insertText(H);await q.wait({time:0.4});await S(q)}async function L(q,z=360){const H=/处理|检查|上传完成|已上传|Processing|complete|Checks|Finished/;for(let Q=0;Q<z;Q++){const W=String(Z(await q.evaluate(`
39
+ (() => { ${Y} return !!__ytResolve(${JSON.stringify(z)}); })()
40
+ `)))}async function L(q){try{await q.evaluate("() => { const a = document.activeElement; if (a && a.blur) a.blur(); }")}catch{}try{if(Boolean(Z(await q.evaluate(`
41
+ (() => { ${Y} return !!__ytResolve('tp-yt-iron-dropdown'); })()
42
+ `)))){await q.pressKey("Escape");await q.wait({time:0.2})}}catch{}}async function R(q,z,H){if(!await D(q,z,30000))throw new X(`YouTube 输入框未出现: ${z}`);await q.click(z);await q.pressKey("Control+a");await q.pressKey("Delete");await q.insertText(H);await q.wait({time:0.4});await L(q)}async function b(q,z=360){const H=/处理|检查|上传完成|已上传|Processing|complete|Checks|Finished/;for(let Q=0;Q<z;Q++){const W=String(Z(await q.evaluate(`
43
43
  (() => {
44
44
  const sels = ['.progress-label', 'span.progress-label', 'ytcp-video-upload-progress'];
45
45
  for (const sel of sels) {
@@ -48,9 +48,9 @@ import{cli as U,Strategy as _}from"@jackwener/opencli/registry";import{ArgumentE
48
48
  }
49
49
  return '';
50
50
  })()
51
- `))||"");if(W&&H.test(W))return!0;await q.wait({time:5})}return!1}U({site:"youtube",name:"publish",access:"write",description:"Publish a video to YouTube via Studio (one command: upload + metadata + publish)",domain:"www.youtube.com",strategy:_.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"Video file path"},{name:"title",required:!0,help:"Video title (≤100 chars)"},{name:"description",fromFile:!0,default:"",help:"Video description"},{name:"tags",default:"",help:"Comma-separated tags"},{name:"thumbnail",default:"",help:"Thumbnail image path (optional)"},{name:"playlist",default:"",help:"Playlist name to add to (optional)"},{name:"visibility",default:"public",choices:["public","unlisted","private"],help:"Visibility"}],columns:["status","title","visibility","url"],func:async(q,z)=>{const H=String(z.title??"").trim();if(!H)throw new C("--title is required");const Q=h(z.video),W=String(z.description||""),Y=z.thumbnail?y(z.thumbnail):"",$=String(z.playlist||"").trim(),j=O[z.visibility]?z.visibility:"public";await q.goto(F);await q.wait({time:3});const A=await v(q);if(/accounts\.google\.com/.test(A)||/signin/i.test(A))throw new X("YouTube 登录态失效,请先 ppcli youtube login");if(!await D(q,'input[type="file"]',60000))throw new X("YouTube 上传文件输入框未出现");await B(q,['input[type="file"]'],[Q]);if(!await D(q,"#title-textarea",120000))throw new X("YouTube 视频详情对话框未出现");await R(q,"#title-textarea #textbox",H.slice(0,100));if(W.trim())await R(q,"#description-textarea #textbox",W);if(Y)try{await B(q,["#file-loader input[type='file']","ytcp-thumbnail-uploader input[type='file']"],[Y]);await q.wait({time:2})}catch{}if($)try{await G(q,"#basics ytcp-text-dropdown-trigger, ytcp-video-metadata-playlists ytcp-dropdown-trigger",8000);await q.wait({time:1.2});const K=`tp-yt-paper-checkbox:has-text('${$}'), ytcp-checkbox-group:has-text('${$}')`;if(await M(q,K))await G(q,K,3000);else if(await G(q,"ytcp-button:has-text('New playlist'), ytcp-button:has-text('创建播放列表')",4000)){await q.wait({time:0.8});await G(q,"tp-yt-paper-item:has-text('New playlist'), tp-yt-paper-item:has-text('新建播放列表')",3000);const J="ytcp-playlist-metadata-editor #textbox, #create-playlist-form #textbox";if(await D(q,J.split(",")[0].trim(),4000)||await M(q,J)){await G(q,J,2000);await q.insertText($);await G(q,"ytcp-button#create-button, tp-yt-paper-dialog ytcp-button:has-text('Create'), tp-yt-paper-dialog ytcp-button:has-text('创建')",4000)}}}catch{}finally{await G(q,"ytcp-playlist-dialog #save-button, ytcp-button:has-text('Done'), ytcp-button:has-text('完成')",3000);await q.pressKey("Escape");await q.wait({time:0.6})}if(!await G(q,"tp-yt-paper-radio-button[name='VIDEO_MADE_FOR_KIDS_NOT_MFK']",1e4))await G(q,"tp-yt-paper-radio-button:has-text('not made for kids'), tp-yt-paper-radio-button:has-text('不是面向儿童')",6000);if(z.tags){const K=String(z.tags).split(",").map((J)=>J.trim()).filter(Boolean).join(",");try{await G(q,"#toggle-button",6000);await q.wait({time:0.8});const J="#tags-container #text-input, ytcp-form-input-container#tags-container input";if(await M(q,J)){await G(q,J,3000);await q.insertText(`${K.slice(0,500)},`)}}catch{}}for(let K=0;K<5;K++){if(await M(q,"tp-yt-paper-radio-button[name='PUBLIC']"))break;if(!await G(q,"#next-button",6000))await q.wait({time:1.2});await q.wait({time:1})}await G(q,`tp-yt-paper-radio-button[name='${O[j]}']`,1e4);await L(q);await q.wait({time:1.2});let N="";if(!await G(q,"#done-button",15000))throw new X("未找到 YouTube 发布按钮(上传可能未到可发布进度),请在窗口内手动发布");await q.wait({time:4});N=String(Z(await q.evaluate(`
51
+ `))||"");if(W&&H.test(W))return!0;await q.wait({time:5})}return!1}U({site:"youtube",name:"publish",access:"write",description:"Publish a video to YouTube via Studio (one command: upload + metadata + publish)",domain:"www.youtube.com",strategy:_.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"Video file path"},{name:"title",required:!0,help:"Video title (≤100 chars)"},{name:"description",fromFile:!0,default:"",help:"Video description"},{name:"tags",default:"",help:"Comma-separated tags"},{name:"thumbnail",default:"",help:"Thumbnail image path (optional)"},{name:"playlist",default:"",help:"Playlist name to add to (optional)"},{name:"visibility",default:"public",choices:["public","unlisted","private"],help:"Visibility"},v()],columns:["status","title","visibility","url"],func:async(q,z)=>{const H=String(z.title??"").trim();if(!H)throw new C("--title is required");const Q=h(z.video),W=String(z.description||""),j=z.thumbnail?y(z.thumbnail):"",$=String(z.playlist||"").trim(),T=O[z.visibility]?z.visibility:"public";await q.goto(S);await q.wait({time:3});const A=await F(q);if(/accounts\.google\.com/.test(A)||/signin/i.test(A))throw new X("YouTube 登录态失效,请先 ppcli youtube login");if(!await D(q,'input[type="file"]',60000))throw new X("YouTube 上传文件输入框未出现");await B(q,['input[type="file"]'],[Q]);if(!await D(q,"#title-textarea",120000))throw new X("YouTube 视频详情对话框未出现");await R(q,"#title-textarea #textbox",H.slice(0,100));if(W.trim())await R(q,"#description-textarea #textbox",W);if(j)try{await B(q,["#file-loader input[type='file']","ytcp-thumbnail-uploader input[type='file']"],[j]);await q.wait({time:2})}catch{}if($)try{await G(q,"#basics ytcp-text-dropdown-trigger, ytcp-video-metadata-playlists ytcp-dropdown-trigger",8000);await q.wait({time:1.2});const K=`tp-yt-paper-checkbox:has-text('${$}'), ytcp-checkbox-group:has-text('${$}')`;if(await M(q,K))await G(q,K,3000);else if(await G(q,"ytcp-button:has-text('New playlist'), ytcp-button:has-text('创建播放列表')",4000)){await q.wait({time:0.8});await G(q,"tp-yt-paper-item:has-text('New playlist'), tp-yt-paper-item:has-text('新建播放列表')",3000);const J="ytcp-playlist-metadata-editor #textbox, #create-playlist-form #textbox";if(await D(q,J.split(",")[0].trim(),4000)||await M(q,J)){await G(q,J,2000);await q.insertText($);await G(q,"ytcp-button#create-button, tp-yt-paper-dialog ytcp-button:has-text('Create'), tp-yt-paper-dialog ytcp-button:has-text('创建')",4000)}}}catch{}finally{await G(q,"ytcp-playlist-dialog #save-button, ytcp-button:has-text('Done'), ytcp-button:has-text('完成')",3000);await q.pressKey("Escape");await q.wait({time:0.6})}if(!await G(q,"tp-yt-paper-radio-button[name='VIDEO_MADE_FOR_KIDS_NOT_MFK']",1e4))await G(q,"tp-yt-paper-radio-button:has-text('not made for kids'), tp-yt-paper-radio-button:has-text('不是面向儿童')",6000);if(z.tags){const K=String(z.tags).split(",").map((J)=>J.trim()).filter(Boolean).join(",");try{await G(q,"#toggle-button",6000);await q.wait({time:0.8});const J="#tags-container #text-input, ytcp-form-input-container#tags-container input";if(await M(q,J)){await G(q,J,3000);await q.insertText(`${K.slice(0,500)},`)}}catch{}}for(let K=0;K<5;K++){if(await M(q,"tp-yt-paper-radio-button[name='PUBLIC']"))break;if(!await G(q,"#next-button",6000))await q.wait({time:1.2});await q.wait({time:1})}await G(q,`tp-yt-paper-radio-button[name='${O[T]}']`,1e4);await b(q);await q.wait({time:1.2});let N="";if(!await G(q,"#done-button",15000))throw new X("未找到 YouTube 发布按钮(上传可能未到可发布进度),请在窗口内手动发布");await q.wait({time:4});N=String(Z(await q.evaluate(`
52
52
  (() => {
53
53
  const a = document.querySelector("a[href*='youtu.be'], a[href*='watch?v=']");
54
54
  return a ? (a.getAttribute('href') || '') : '';
55
55
  })()
56
- `))||"");await G(q,"ytcp-button:has-text('Close'), ytcp-button:has-text('关闭'), #close-button",8000);return[{status:"✅ Published",title:H,visibility:j,url:N}]}});
56
+ `))||"");await G(q,"ytcp-button:has-text('Close'), ytcp-button:has-text('关闭'), #close-button",8000);return[{status:"✅ Published",title:H,visibility:T,url:N}]}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "publishport-opencli",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": false