publishport-opencli 1.0.27 → 1.0.28

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
@@ -3111,7 +3111,8 @@
3111
3111
  "target_type",
3112
3112
  "target",
3113
3113
  "created_target",
3114
- "created_url"
3114
+ "created_url",
3115
+ "management_url"
3115
3116
  ],
3116
3117
  "type": "js",
3117
3118
  "modulePath": "baijiahao/article.js",
@@ -4115,6 +4116,72 @@
4115
4116
  "sourceFile": "bilibili/comments.js",
4116
4117
  "navigateBefore": "https://www.bilibili.com"
4117
4118
  },
4119
+ {
4120
+ "site": "bilibili",
4121
+ "name": "danmaku",
4122
+ "description": "给 B站视频指定时间发送普通滚动弹幕(默认预览,--execute 才发送)",
4123
+ "access": "write",
4124
+ "domain": "www.bilibili.com",
4125
+ "strategy": "cookie",
4126
+ "browser": true,
4127
+ "args": [
4128
+ {
4129
+ "name": "bvid",
4130
+ "type": "str",
4131
+ "required": true,
4132
+ "positional": true,
4133
+ "help": "Video BV ID / URL / b23.tv short link"
4134
+ },
4135
+ {
4136
+ "name": "message",
4137
+ "type": "str",
4138
+ "required": false,
4139
+ "positional": true,
4140
+ "help": "弹幕正文,亦可使用 --file",
4141
+ "fromFile": true,
4142
+ "requiredUnlessFile": true
4143
+ },
4144
+ {
4145
+ "name": "at",
4146
+ "type": "str",
4147
+ "required": true,
4148
+ "help": "弹幕出现时间,单位秒,支持小数(例如 12.5)"
4149
+ },
4150
+ {
4151
+ "name": "page",
4152
+ "type": "str",
4153
+ "required": false,
4154
+ "help": "分 P 序号,默认使用 URL 的 p 或第 1 P"
4155
+ },
4156
+ {
4157
+ "name": "execute",
4158
+ "type": "boolean",
4159
+ "required": false,
4160
+ "help": "实际发送一次;平台受理不代表公开可见"
4161
+ },
4162
+ {
4163
+ "name": "file",
4164
+ "type": "str",
4165
+ "required": false,
4166
+ "help": "从本机文件读取message(UTF-8),与 <message> 二选一。长正文建议走文件:命令行上只剩短参数,不受 shell 引号 / 换行 / 编码影响",
4167
+ "generatedFileFor": "message"
4168
+ }
4169
+ ],
4170
+ "columns": [
4171
+ "dmid",
4172
+ "bvid",
4173
+ "cid",
4174
+ "page",
4175
+ "at",
4176
+ "message",
4177
+ "status",
4178
+ "url"
4179
+ ],
4180
+ "type": "js",
4181
+ "modulePath": "bilibili/danmaku.js",
4182
+ "sourceFile": "bilibili/danmaku.js",
4183
+ "navigateBefore": "https://www.bilibili.com"
4184
+ },
4118
4185
  {
4119
4186
  "site": "bilibili",
4120
4187
  "name": "dm-list",
@@ -5096,6 +5163,12 @@
5096
5163
  "required": false,
5097
5164
  "help": "1=自制 2=转载(默认 1)"
5098
5165
  },
5166
+ {
5167
+ "name": "ai-generated",
5168
+ "type": "boolean",
5169
+ "required": false,
5170
+ "help": "声明含 AI 生成内容(发布和草稿均支持;按当前账号可用选项校验,不能与转载同时选择)"
5171
+ },
5099
5172
  {
5100
5173
  "name": "source",
5101
5174
  "type": "str",
@@ -5276,6 +5349,73 @@
5276
5349
  "sourceFile": "bilibili/video-drafts.js",
5277
5350
  "navigateBefore": "https://member.bilibili.com"
5278
5351
  },
5352
+ {
5353
+ "site": "bilibili",
5354
+ "name": "video-edit",
5355
+ "description": "编辑自己的视频标题、简介和普通标签;未指定字段及活动话题保持原样。默认 dry-run,加 --execute 才保存并回查后台。AI 声明在官网编辑页不可修改。",
5356
+ "access": "write",
5357
+ "domain": "member.bilibili.com",
5358
+ "strategy": "ui",
5359
+ "browser": true,
5360
+ "args": [
5361
+ {
5362
+ "name": "bvid",
5363
+ "type": "string",
5364
+ "required": true,
5365
+ "positional": true,
5366
+ "help": "已有视频的完整 BV 号"
5367
+ },
5368
+ {
5369
+ "name": "title",
5370
+ "type": "string",
5371
+ "required": false,
5372
+ "help": "新的完整标题,最多 80 字"
5373
+ },
5374
+ {
5375
+ "name": "desc",
5376
+ "type": "string",
5377
+ "required": false,
5378
+ "help": "替换简介(纯文本),可传空字符串清空"
5379
+ },
5380
+ {
5381
+ "name": "desc-file",
5382
+ "type": "string",
5383
+ "required": false,
5384
+ "help": "从本机 UTF-8 文件读取新的完整简介"
5385
+ },
5386
+ {
5387
+ "name": "tag",
5388
+ "type": "string",
5389
+ "required": false,
5390
+ "help": "替换全部普通标签,逗号分隔;活动标签保留,不要重复传入"
5391
+ },
5392
+ {
5393
+ "name": "execute",
5394
+ "type": "boolean",
5395
+ "required": false,
5396
+ "help": "保存修改;缺省仅读取并展示修改内容"
5397
+ },
5398
+ {
5399
+ "name": "timeout",
5400
+ "type": "int",
5401
+ "default": 120,
5402
+ "required": false,
5403
+ "help": "整段编辑流程的等待预算(秒);超时后先回查稿件,勿直接重提"
5404
+ }
5405
+ ],
5406
+ "columns": [
5407
+ "bvid",
5408
+ "status",
5409
+ "field",
5410
+ "before",
5411
+ "after",
5412
+ "url"
5413
+ ],
5414
+ "type": "js",
5415
+ "modulePath": "bilibili/video-edit.js",
5416
+ "sourceFile": "bilibili/video-edit.js",
5417
+ "navigateBefore": true
5418
+ },
5279
5419
  {
5280
5420
  "site": "bilibili",
5281
5421
  "name": "watch",
@@ -22054,6 +22194,71 @@
22054
22194
  "sourceFile": "jike/comment.js",
22055
22195
  "navigateBefore": "https://web.okjike.com"
22056
22196
  },
22197
+ {
22198
+ "site": "jike",
22199
+ "name": "comments",
22200
+ "description": "读取即刻帖子的评论、楼中楼,或按评论回执 ID 精确回查(需登录)",
22201
+ "access": "read",
22202
+ "example": "ppcli jike comments 6a9f43388262cfba44f550db --limit 20 -f json",
22203
+ "domain": "web.okjike.com",
22204
+ "strategy": "cookie",
22205
+ "browser": true,
22206
+ "args": [
22207
+ {
22208
+ "name": "id",
22209
+ "type": "string",
22210
+ "required": true,
22211
+ "positional": true,
22212
+ "help": "帖子 ID"
22213
+ },
22214
+ {
22215
+ "name": "comment-id",
22216
+ "type": "string",
22217
+ "required": false,
22218
+ "help": "只回查这一条评论(例如 comment 返回的 comment_id)"
22219
+ },
22220
+ {
22221
+ "name": "thread",
22222
+ "type": "string",
22223
+ "required": false,
22224
+ "help": "读取这个顶级评论 ID 下的楼中楼;与 --comment-id 互斥"
22225
+ },
22226
+ {
22227
+ "name": "sort",
22228
+ "type": "string",
22229
+ "default": "time",
22230
+ "required": false,
22231
+ "help": "列表排序:time 时间 / likes 官网智能排序",
22232
+ "choices": [
22233
+ "time",
22234
+ "likes"
22235
+ ]
22236
+ },
22237
+ {
22238
+ "name": "limit",
22239
+ "type": "int",
22240
+ "default": 20,
22241
+ "required": false,
22242
+ "help": "列表最多返回多少条;不含各条 hotReplies 的预览"
22243
+ }
22244
+ ],
22245
+ "columns": [
22246
+ "id",
22247
+ "postId",
22248
+ "threadId",
22249
+ "author",
22250
+ "content",
22251
+ "likes",
22252
+ "replies",
22253
+ "time",
22254
+ "level",
22255
+ "status"
22256
+ ],
22257
+ "type": "js",
22258
+ "modulePath": "jike/comments.js",
22259
+ "sourceFile": "jike/comments.js",
22260
+ "navigateBefore": "https://web.okjike.com"
22261
+ },
22057
22262
  {
22058
22263
  "site": "jike",
22059
22264
  "name": "create",
@@ -22089,6 +22294,59 @@
22089
22294
  "sourceFile": "jike/create.js",
22090
22295
  "navigateBefore": true
22091
22296
  },
22297
+ {
22298
+ "site": "jike",
22299
+ "name": "delete",
22300
+ "description": "删除自己的即刻动态、转发或指定评论。默认 dry-run 核对目标;删除不可恢复,加 --execute 才执行。按 ID 校验作者与所属帖子,提交后回查,不自动重试。",
22301
+ "access": "write",
22302
+ "domain": "web.okjike.com",
22303
+ "strategy": "cookie",
22304
+ "browser": true,
22305
+ "args": [
22306
+ {
22307
+ "name": "id",
22308
+ "type": "string",
22309
+ "required": true,
22310
+ "positional": true,
22311
+ "help": "动态/转发 ID;删除评论时传该评论所属的帖子 ID"
22312
+ },
22313
+ {
22314
+ "name": "type",
22315
+ "type": "string",
22316
+ "default": "post",
22317
+ "required": false,
22318
+ "help": "目标帖子类型:post 原创动态,repost 转发",
22319
+ "choices": [
22320
+ "post",
22321
+ "repost"
22322
+ ]
22323
+ },
22324
+ {
22325
+ "name": "comment-id",
22326
+ "type": "string",
22327
+ "required": false,
22328
+ "help": "删除此评论,id 必须是评论所属的帖子;不删除帖子本身"
22329
+ },
22330
+ {
22331
+ "name": "execute",
22332
+ "type": "boolean",
22333
+ "required": false,
22334
+ "help": "实际删除;不带时只核对目标并展示内容"
22335
+ }
22336
+ ],
22337
+ "columns": [
22338
+ "id",
22339
+ "postId",
22340
+ "type",
22341
+ "status",
22342
+ "content",
22343
+ "url"
22344
+ ],
22345
+ "type": "js",
22346
+ "modulePath": "jike/delete.js",
22347
+ "sourceFile": "jike/delete.js",
22348
+ "navigateBefore": "https://web.okjike.com"
22349
+ },
22092
22350
  {
22093
22351
  "site": "jike",
22094
22352
  "name": "feed",
@@ -22323,9 +22581,9 @@
22323
22581
  {
22324
22582
  "site": "jike",
22325
22583
  "name": "topic",
22326
- "description": "即刻话题/圈子帖子",
22584
+ "description": "即刻话题/圈子帖子(登录后按游标分页)",
22327
22585
  "access": "read",
22328
- "domain": "m.okjike.com",
22586
+ "domain": "web.okjike.com",
22329
22587
  "strategy": "cookie",
22330
22588
  "browser": true,
22331
22589
  "args": [
@@ -22341,7 +22599,7 @@
22341
22599
  "type": "int",
22342
22600
  "default": 20,
22343
22601
  "required": false,
22344
- "help": "Number of posts"
22602
+ "help": "最多返回多少条帖子"
22345
22603
  }
22346
22604
  ],
22347
22605
  "columns": [
@@ -22350,12 +22608,13 @@
22350
22608
  "likes",
22351
22609
  "comments",
22352
22610
  "time",
22353
- "url"
22611
+ "url",
22612
+ "id"
22354
22613
  ],
22355
22614
  "type": "js",
22356
22615
  "modulePath": "jike/topic.js",
22357
22616
  "sourceFile": "jike/topic.js",
22358
- "navigateBefore": "https://m.okjike.com"
22617
+ "navigateBefore": "https://web.okjike.com"
22359
22618
  },
22360
22619
  {
22361
22620
  "site": "jike",
@@ -30017,7 +30276,11 @@
30017
30276
  "likes",
30018
30277
  "comments",
30019
30278
  "views",
30020
- "id"
30279
+ "content",
30280
+ "id",
30281
+ "momentId",
30282
+ "userId",
30283
+ "entityType"
30021
30284
  ],
30022
30285
  "type": "js",
30023
30286
  "modulePath": "nowcoder/recommend.js",
@@ -35096,7 +35359,9 @@
35096
35359
  ],
35097
35360
  "columns": [
35098
35361
  "status",
35099
- "message"
35362
+ "message",
35363
+ "comment_id",
35364
+ "url"
35100
35365
  ],
35101
35366
  "type": "js",
35102
35367
  "modulePath": "reddit/comment.js",
@@ -51189,6 +51454,13 @@
51189
51454
  "default": false,
51190
51455
  "required": false,
51191
51456
  "help": "保存为草稿,不直接发布"
51457
+ },
51458
+ {
51459
+ "name": "timeout",
51460
+ "type": "int",
51461
+ "default": 300,
51462
+ "required": false,
51463
+ "help": "整段发布流程的等待预算(秒);超时后先用 creator-notes 回查,勿直接重复发布"
51192
51464
  }
51193
51465
  ],
51194
51466
  "columns": [
@@ -1,4 +1,4 @@
1
- import*as l from"node:fs";import*as a from"node:path";import{ArgumentError as o,CommandExecutionError as f}from"@jackwener/opencli/errors";import{isFileAccessDenied as w,setInputFiles as m}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(e){if(e==null||e==="")return null;const t=String(e),n=/^\d+$/.test(t)?new Date(Number(t)<1000000000000?Number(t)*1000:Number(t)):new Date(t);if(Number.isNaN(n.getTime()))throw new o(`无法解析定时时间: ${e}`);if(n.getTime()<=Date.now())throw new o("定时发布时间必须晚于当前时间");return n}export function unwrap(e){if(e&&typeof e==="object"&&"data"in e&&"session"in e)return e.data;return e}export async function evalPage(e,t){return unwrap(await e.evaluate(t))}export function sleep(e){return new Promise((t)=>setTimeout(t,e*1000))}export const videoTimeoutArg=(e=540)=>({name:"timeout",type:"int",default:e,help:`命令超时(秒,默认 ${e})。大视频/上行慢时调大;`+"超时不代表没发成功,先用回查命令确认再决定要不要重发。"});export function resolveVideoFile(e){const t=a.resolve(String(e));let n;try{n=l.statSync(t)}catch{throw new o(`视频文件不存在: ${t}`)}if(!n.isFile())throw new o(`视频路径不是文件: ${t}`);const r=a.extname(t).toLowerCase();if(!SUPPORTED_VIDEO_EXTENSIONS.has(r))throw new o(`不支持的视频格式: ${r}(支持 ${[...SUPPORTED_VIDEO_EXTENSIONS].join("/")})`);return t}export function resolveImageFile(e){const t=a.resolve(String(e));let n;try{n=l.statSync(t)}catch{throw new o(`图片文件不存在: ${t}`)}if(!n.isFile())throw new o(`图片路径不是文件: ${t}`);const r=a.extname(t).toLowerCase();if(!SUPPORTED_IMAGE_EXTENSIONS.has(r))throw new o(`不支持的图片格式: ${r}(支持 ${[...SUPPORTED_IMAGE_EXTENSIONS].join("/")})`);return t}export function throwIfFileAccessDenied(e){if(!w(e))return;throw new f("Chrome 拦截了扩展读取本地文件(DOM.setFileInputFiles → File access not granted):商店安装的扩展默认没有本地文件访问权限","请让用户在 Chrome 打开 chrome://extensions,找到「ppcli Browser Bridge」→「详情」→ 打开「允许访问文件网址」(Allow access to file URLs),开关即时生效,然后重试本命令")}export async function setVideoInput(e,t,n){return(await m(e,t,n)).selector}export async function currentUrl(e){try{if(e.getCurrentUrl){const t=await e.getCurrentUrl();if(t)return String(t)}}catch{}try{return String(await evalPage(e,"() => location.href")||"")}catch{return""}}export async function waitForUrlIncludes(e,t,n=60000,r=500){const i=Array.isArray(t)?t:[t],s=Date.now()+n;while(Date.now()<s){const c=await currentUrl(e);if(c&&i.some((u)=>c.includes(u)))return!0;await e.wait({time:r/1000})}return!1}export async function countByText(e,t){return Number(unwrap(await e.evaluate(`
1
+ import*as c from"node:fs";import*as a from"node:path";import{ArgumentError as o,CommandExecutionError as f}from"@jackwener/opencli/errors";import{isFileAccessDenied as w,setInputFiles as m}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(e){if(e==null||e==="")return null;const t=String(e),n=/^\d+$/.test(t)?new Date(Number(t)<1000000000000?Number(t)*1000:Number(t)):new Date(t);if(Number.isNaN(n.getTime()))throw new o(`无法解析定时时间: ${e}`);if(n.getTime()<=Date.now())throw new o("定时发布时间必须晚于当前时间");return n}export function unwrap(e){if(e&&typeof e==="object"&&"data"in e&&"session"in e)return e.data;return e}export async function evalPage(e,t){return unwrap(await e.evaluate(t))}export function sleep(e){return new Promise((t)=>setTimeout(t,e*1000))}export const videoTimeoutArg=(e=540)=>({name:"timeout",type:"int",default:e,help:`命令超时(秒,默认 ${e})。大视频/上行慢时调大;`+"超时不代表没发成功,先用回查命令确认再决定要不要重发。"});export function resolveVideoFile(e){const t=a.resolve(String(e));let n;try{n=c.statSync(t)}catch{throw new o(`视频文件不存在: ${t}`)}if(!n.isFile())throw new o(`视频路径不是文件: ${t}`);const r=a.extname(t).toLowerCase();if(!SUPPORTED_VIDEO_EXTENSIONS.has(r))throw new o(`不支持的视频格式: ${r}(支持 ${[...SUPPORTED_VIDEO_EXTENSIONS].join("/")})`);return t}export function resolveImageFile(e){const t=a.resolve(String(e));let n;try{n=c.statSync(t)}catch{throw new o(`图片文件不存在: ${t}`)}if(!n.isFile())throw new o(`图片路径不是文件: ${t}`);const r=a.extname(t).toLowerCase();if(!SUPPORTED_IMAGE_EXTENSIONS.has(r))throw new o(`不支持的图片格式: ${r}(支持 ${[...SUPPORTED_IMAGE_EXTENSIONS].join("/")})`);return t}export function throwIfFileAccessDenied(e){if(!w(e))return;throw new f("Chrome 拦截了扩展读取本地文件(DOM.setFileInputFiles → File access not granted):商店安装的扩展默认没有本地文件访问权限","请让用户在 Chrome 打开 chrome://extensions,找到「ppcli Browser Bridge」→「详情」→ 打开「允许访问文件网址」(Allow access to file URLs),开关即时生效,然后重试本命令")}export async function setVideoInput(e,t,n){return(await m(e,t,n)).selector}export async function currentUrl(e){try{if(e.getCurrentUrl){const t=await e.getCurrentUrl();if(t)return String(t)}}catch{}try{return String(await evalPage(e,"() => location.href")||"")}catch{return""}}export async function waitForUrlIncludes(e,t,n=60000,r=500){const i=Array.isArray(t)?t:[t],s=Date.now()+n;while(Date.now()<s){const l=await currentUrl(e);if(l&&i.some((u)=>l.includes(u)))return!0;await e.wait({time:r/1000})}return!1}export async function countByText(e,t){return Number(unwrap(await e.evaluate(`
2
2
  ((want) => {
3
3
  let n = 0;
4
4
  const nodes = document.querySelectorAll('body *');
@@ -56,6 +56,7 @@ import*as l from"node:fs";import*as a from"node:path";import{ArgumentError as o,
56
56
  ((sel) => {
57
57
  const el = document.querySelector(sel);
58
58
  if (!el) return false;
59
+ if (el.tagName === 'INPUT' && el.type === 'file') return true;
59
60
  const r = el.getBoundingClientRect();
60
61
  return r.width > 0 && r.height > 0;
61
62
  })(${JSON.stringify(t)})
@@ -1 +1,17 @@
1
- import{CliError as u,CommandExecutionError as P}from"@jackwener/opencli/errors";import{cli as C,Strategy as U}from"@jackwener/opencli/registry";import{publishArticle as L}from"../_shared/article/publish.js";import{readFile as k,stat as O}from"node:fs/promises";function R(a){if(!a.execute)throw new u("INVALID_INPUT","此百家号写操作需要 --execute 参数才能真正发布。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function J(a){const e=typeof a.text==="string"?a.text:void 0,t=typeof a.file==="string"?a.file:void 0;if(e&&t)throw new u("INVALID_INPUT","正文和 --file 只能选一个");let r=e??"";if(t){let i;try{i=await O(t)}catch{throw new u("INVALID_INPUT",`找不到文件: ${t}`)}if(!i.isFile())throw new u("INVALID_INPUT",`--file 必须是可读文本文件: ${t}`);let n;try{n=await k(t)}catch{throw new u("INVALID_INPUT",`文件无法读取: ${t}`)}try{r=new TextDecoder("utf-8",{fatal:!0}).decode(n)}catch{throw new u("INVALID_INPUT",`文件必须是 UTF-8 编码: ${t}`)}}if(!r.trim())throw new u("INVALID_INPUT","正文不能为空");return r}function F(a,e,t,r,i={}){return[{status:"success",outcome:r,message:a,target_type:e,target:t,...i}]}export const baijiahaoProfile={home:"https://baijiahao.baidu.com",outputFormat:"html",preprocessConfig:{},image:{skip:["baijiahao.baidu.com","bdstatic.com","bcebos.com"],uploadFn:async(a,e)=>{const t=await fetch(a,{credentials:"omit"});if(!t.ok)throw Error("图片下载失败: "+a+" HTTP "+t.status);let r=await t.blob();r=await e.webpToJpeg(r);const i=new FormData;i.append("media",r,"image.jpg");i.append("type","image");i.append("app_id","1589639493090963");i.append("is_waterlog","1");i.append("save_material","1");i.append("no_compress","0");i.append("is_events","");i.append("article_type","news");const o=await(await fetch("https://baijiahao.baidu.com/pcui/picture/uploadproxy",{method:"POST",credentials:"include",body:i})).json();if(o.errmsg!=="success"||!o.ret||!o.ret.https_url)throw Error(o.errmsg||"图片上传失败");return{url:o.ret.https_url}}},publish:async(a,e)=>{const t=a.params||{},r=await fetch("https://baijiahao.baidu.com/builder/rc/edit",{credentials:"include"}),n=(await r.text()).match(/window\.__BJH__INIT__AUTH__\s*=\s*['"]([^'"]+)['"]/);if(!n)return{ok:!1,stage:"auth",status:r.status,message:"登录失效,请重新登录百家号"};const o=n[1],c=a.content;if(a.draftOnly){const l=new URLSearchParams({title:a.title,content:c,feed_cat:"1",len:String(c.length),activity_list:JSON.stringify([{id:408,is_checked:0}]),source_reprinted_allow:"0",original_status:"0",original_handler_status:"1",isBeautify:"false",subtitle:"",bjhtopic_id:"",bjhtopic_info:"",type:"news"}),_=await fetch("https://baijiahao.baidu.com/pcui/article/save?callback=bjhdraft",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",token:o},body:l}),s=(await _.text()).replace(/^bjhdraft\(/,"").replace(/\)$/,"");let p=null;try{p=JSON.parse(s)}catch(V){}if(!p||p.errmsg!=="success"||!p.ret||!p.ret.article_id)return{ok:!1,stage:"save",status:_.status,message:p&&p.errmsg||"保存草稿失败"};const h=String(p.ret.article_id),x="https://baijiahao.baidu.com/builder/rc/edit?type=news&article_id="+h;return{ok:!0,id:h,url:x,draft:!0}}if(!t.cover||!String(t.cover).trim())return{ok:!1,stage:"cover",message:"正式发布百家号文章必须提供封面图(--cover)"};const f=String(t.cover);let m="";try{m=new URL(f,location.href).hostname}catch(l){}const w=/(^|\.)baijiahao\.baidu\.com$|(^|\.)bdstatic\.com$|(^|\.)bcebos\.com$/.test(m);let b="";if(w)b=f;else try{const l=m===location.hostname,_=await fetch(f,{credentials:l?"include":"omit"});if(!_.ok)return{ok:!1,stage:"cover",status:_.status,message:"封面图下载失败:"+t.cover};let g=await _.blob();if(/text\/html/i.test(g.type))return{ok:!1,stage:"cover",message:"封面图下载到的是 HTML 页面而非图片(防盗链/登录墙?):"+t.cover};g=await e.webpToJpeg(g);const s=new FormData;s.append("media",g,"cover.jpg");s.append("type","image");s.append("app_id","1589639493090963");s.append("is_waterlog","1");s.append("save_material","1");s.append("no_compress","0");s.append("is_events","");s.append("article_type","news");const h=await(await fetch("https://baijiahao.baidu.com/pcui/picture/uploadproxy",{method:"POST",credentials:"include",body:s})).json();if(h.errmsg!=="success"||!h.ret||!h.ret.https_url)return{ok:!1,stage:"cover",message:"封面图上传失败:"+(h.errmsg||"未知错误")};b=h.ret.https_url}catch(l){return{ok:!1,stage:"cover",message:"封面图处理异常:"+String(l&&l.message||l)}}const y=c+'<img src="'+b+'"><br>',A=[{src:b,cropData:{x:0,y:0,width:2048,height:1365},machine_chooseimg:0,isLegal:1}],I=[{src:b}],j={type:"news",title:a.title,author:t.author?String(t.author):"",abstract:t.abstract?String(t.abstract):"",content:y,auto_mount_goods:"1",len:String(y.length),vertical_cover:b,cover_images:JSON.stringify(A),_cover_images_map:JSON.stringify(I)},T="&activity_list%5B0%5D%5Bid%5D=408&activity_list%5B0%5D%5Bis_checked%5D=0&activity_list%5B1%5D%5Bid%5D=ttv&activity_list%5B1%5D%5Bis_checked%5D=1&activity_list%5B2%5D%5Bid%5D=reward&activity_list%5B2%5D%5Bis_checked%5D=1&activity_list%5B3%5D%5Bid%5D=aigc_bjh_status&activity_list%5B3%5D%5Bis_checked%5D=0&source_reprinted_allow=0&abstract_from=2&isBeautify=false&usingImgFilter=false&cover_layout=one",S="&source=upload&cover_source=upload&subtitle=&bjhtopic_id=&bjhtopic_info=&clue=1&bjhmt=&order_id=&aigc_rebuild=&image_edit_point=%5B%7B%22img_type%22%3A%22cover%22%2C%22img_num%22%3A%7B%22template%22%3A0%2C%22font%22%3A0%2C%22filter%22%3A0%2C%22paster%22%3A0%2C%22cut%22%3A0%2C%22any%22%3A0%7D%7D%2C%7B%22img_type%22%3A%22body%22%2C%22img_num%22%3A%7B%22template%22%3A0%2C%22font%22%3A0%2C%22filter%22%3A0%2C%22paster%22%3A0%2C%22cut%22%3A0%2C%22any%22%3A0%7D%7D%5D",B=new URLSearchParams(j).toString()+T+S,v=await fetch("https://baijiahao.baidu.com/pcui/article/publish?callback=bjhpublish",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",token:o},body:B}),N=(await v.text()).replace(/^bjhpublish\(/,"").replace(/\)$/,"");let d=null;try{d=JSON.parse(N)}catch(l){}if(!d||d.errno!==0||!d.ret)return{ok:!1,stage:"publish",status:v.status,message:d&&(d.errmsg||d.errno)||"百家号发布失败"};const D=d.ret.url||"";return{ok:!0,id:D,url:D,draft:!1}},checkAuth:async(a)=>{try{const t=await(await fetch("https://baijiahao.baidu.com/builder/app/appinfo?_="+Date.now(),{credentials:"include"})).json();if(t.errmsg==="success"&&t.data&&t.data.user){const r=t.data.user;return{isAuthenticated:!0,userId:String(r.userid||""),username:r.name||"",avatar:r.avatar||""}}return{isAuthenticated:!1}}catch(e){return{isAuthenticated:!1,error:String(e&&e.message||e)}}}};C({site:"baijiahao",name:"article",access:"write",description:"发布百家号文章。默认正式发布(需 --cover 封面),加 --draft 仅存草稿(草稿不需要封面)。正文默认 Markdown,图片自动转存到百家号图床。",domain:"baijiahao.baidu.com",strategy:U.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;传 --html 则视为原始 HTML)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"cover",help:"封面图 URL(正式发布必填;会先转存到百家号图床。草稿模式可不传)"},{name:"abstract",help:"文章摘要(可空,正式发布时写入;草稿模式忽略)"},{name:"author",help:"作者署名(可空,正式发布时写入;草稿模式忽略)"},{name:"draft",type:"boolean",help:"仅保存草稿,不正式发布(草稿不需要封面)"},{name:"execute",type:"boolean",help:"真正执行写操作。不加此参数时命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(a,e)=>{if(!a)throw new P("百家号文章发布需要浏览器会话");R(e);const t=String(e.title??"").trim();if(!t)throw new u("INVALID_INPUT","文章标题不能为空");const r=await J(e),i=Boolean(e.draft),n=typeof e.cover==="string"?e.cover.trim():"";if(!i&&!n)throw new u("INVALID_INPUT","正式发布百家号文章必须提供封面图:请传 --cover <图片URL>(或加 --draft 只存草稿)");const o={cover:n,abstract:typeof e.abstract==="string"?e.abstract:"",author:typeof e.author==="string"?e.author:""},c=await L(a,{title:t,body:r,format:e.html?"html":"markdown",draftOnly:i,profile:baijiahaoProfile,publishParams:o}),f=c.images.uploaded.length|0,m=c.images.failed.length|0;let w=c.draft?"已保存百家号文章草稿":"已正式发布百家号文章";if(f||m)w+=`(图片:${f} 张已转存${m?`,${m} 张失败`:""})`;return F(w,"article","",c.draft?"draft":"created",{created_target:"article:"+c.id,created_url:c.url})}});
1
+ import{CliError as u,CommandExecutionError as P}from"@jackwener/opencli/errors";import{cli as U,Strategy as k}from"@jackwener/opencli/registry";import{publishArticle as C}from"../_shared/article/publish.js";import{readFile as L,stat as O}from"node:fs/promises";function R(a){if(!a.execute)throw new u("INVALID_INPUT","此百家号写操作需要 --execute 参数才能真正发布。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function E(a){const e=typeof a.text==="string"?a.text:void 0,t=typeof a.file==="string"?a.file:void 0;if(e&&t)throw new u("INVALID_INPUT","正文和 --file 只能选一个");let r=e??"";if(t){let i;try{i=await O(t)}catch{throw new u("INVALID_INPUT",`找不到文件: ${t}`)}if(!i.isFile())throw new u("INVALID_INPUT",`--file 必须是可读文本文件: ${t}`);let n;try{n=await L(t)}catch{throw new u("INVALID_INPUT",`文件无法读取: ${t}`)}try{r=new TextDecoder("utf-8",{fatal:!0}).decode(n)}catch{throw new u("INVALID_INPUT",`文件必须是 UTF-8 编码: ${t}`)}}if(!r.trim())throw new u("INVALID_INPUT","正文不能为空");return r}function J(a,e,t,r,i={}){return[{status:"success",outcome:r,message:a,target_type:e,target:t,...i}]}export async function resolvePublishedReceipt(a,e){const t=new URL(e,"https://baijiahao.baidu.com");if(t.hostname!=="baijiahao.baidu.com")throw Error("发布回执域名不匹配");const r=t.searchParams.get("id")||t.searchParams.get("nid");if(!r||!/^\d+$/.test(r))throw Error("发布回执缺少可识别的文章 ID");if(t.pathname==="/s")return{id:r,url:`https://baijiahao.baidu.com/s?id=${r}`};if(!t.pathname.startsWith("/builder/preview"))throw Error("未识别的发布回执地址");const i=await a.evaluate(`(async () => {
2
+ const html = await (await fetch('/builder/rc/edit', { credentials: 'include' })).text();
3
+ const token = html.match(/window\\.__BJH__INIT__AUTH__\\s*=\\s*['"]([^'"]+)['"]/)?.[1];
4
+ if (!token) throw new Error('无法读取百家号登录凭据');
5
+ const response = await fetch('/pcui/article/lists?currentPage=1&pageSize=50&search=&type=&collection=&dynamic=1', {
6
+ credentials: 'include', headers: { token },
7
+ });
8
+ const payload = await response.json();
9
+ if (!response.ok || payload.errno !== 0 || !Array.isArray(payload.data?.list)) {
10
+ throw new Error('百家号文章列表读取失败:' + response.status + ' / ' + payload.errmsg);
11
+ }
12
+ const matches = payload.data.list.filter(a => a.nid === ${JSON.stringify(r)});
13
+ if (matches.length !== 1) throw new Error('文章列表尚未唯一匹配本次发布的 nid');
14
+ const articleId = matches[0].article_id;
15
+ if (typeof articleId !== 'string' || !/^\\d+$/.test(articleId)) throw new Error('列表未返回精确的 article_id');
16
+ return articleId;
17
+ })()`);return{id:i,url:`https://baijiahao.baidu.com/s?id=${i}`}}export const baijiahaoProfile={home:"https://baijiahao.baidu.com",outputFormat:"html",preprocessConfig:{},image:{skip:["baijiahao.baidu.com","bdstatic.com","bcebos.com"],uploadFn:async(a,e)=>{const t=await fetch(a,{credentials:"omit"});if(!t.ok)throw Error("图片下载失败: "+a+" HTTP "+t.status);let r=await t.blob();r=await e.webpToJpeg(r);const i=new FormData;i.append("media",r,"image.jpg");i.append("type","image");i.append("app_id","1589639493090963");i.append("is_waterlog","1");i.append("save_material","1");i.append("no_compress","0");i.append("is_events","");i.append("article_type","news");const c=await(await fetch("https://baijiahao.baidu.com/pcui/picture/uploadproxy",{method:"POST",credentials:"include",body:i})).json();if(c.errmsg!=="success"||!c.ret||!c.ret.https_url)throw Error(c.errmsg||"图片上传失败");return{url:c.ret.https_url}}},publish:async(a,e)=>{const t=a.params||{},r=await fetch("https://baijiahao.baidu.com/builder/rc/edit",{credentials:"include"}),n=(await r.text()).match(/window\.__BJH__INIT__AUTH__\s*=\s*['"]([^'"]+)['"]/);if(!n)return{ok:!1,stage:"auth",status:r.status,message:"登录失效,请重新登录百家号"};const c=n[1],s=a.content;if(a.draftOnly){const d=new URLSearchParams({title:a.title,content:s,feed_cat:"1",len:String(s.length),activity_list:JSON.stringify([{id:408,is_checked:0}]),source_reprinted_allow:"0",original_status:"0",original_handler_status:"1",isBeautify:"false",subtitle:"",bjhtopic_id:"",bjhtopic_info:"",type:"news"}),g=await fetch("https://baijiahao.baidu.com/pcui/article/save?callback=bjhdraft",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",token:c},body:d}),o=(await g.text()).replace(/^bjhdraft\(/,"").replace(/\)$/,"");let p=null;try{p=JSON.parse(o)}catch(H){}if(!p||p.errmsg!=="success"||!p.ret||!p.ret.article_id)return{ok:!1,stage:"save",status:g.status,message:p&&p.errmsg||"保存草稿失败"};const m=String(p.ret.article_id),x="https://baijiahao.baidu.com/builder/rc/edit?type=news&article_id="+m;return{ok:!0,id:m,url:x,draft:!0}}if(!t.cover||!String(t.cover).trim())return{ok:!1,stage:"cover",message:"正式发布百家号文章必须提供封面图(--cover)"};const b=String(t.cover);let f="";try{f=new URL(b,location.href).hostname}catch(d){}const w=/(^|\.)baijiahao\.baidu\.com$|(^|\.)bdstatic\.com$|(^|\.)bcebos\.com$/.test(f);let l="";if(w)l=b;else try{const d=f===location.hostname,g=await fetch(b,{credentials:d?"include":"omit"});if(!g.ok)return{ok:!1,stage:"cover",status:g.status,message:"封面图下载失败:"+t.cover};let y=await g.blob();if(/text\/html/i.test(y.type))return{ok:!1,stage:"cover",message:"封面图下载到的是 HTML 页面而非图片(防盗链/登录墙?):"+t.cover};y=await e.webpToJpeg(y);const o=new FormData;o.append("media",y,"cover.jpg");o.append("type","image");o.append("app_id","1589639493090963");o.append("is_waterlog","1");o.append("save_material","1");o.append("no_compress","0");o.append("is_events","");o.append("article_type","news");const m=await(await fetch("https://baijiahao.baidu.com/pcui/picture/uploadproxy",{method:"POST",credentials:"include",body:o})).json();if(m.errmsg!=="success"||!m.ret||!m.ret.https_url)return{ok:!1,stage:"cover",message:"封面图上传失败:"+(m.errmsg||"未知错误")};l=m.ret.https_url}catch(d){return{ok:!1,stage:"cover",message:"封面图处理异常:"+String(d&&d.message||d)}}const _=s+'<img src="'+l+'"><br>',j=[{src:l,cropData:{x:0,y:0,width:2048,height:1365},machine_chooseimg:0,isLegal:1}],D=[{src:l}],A={type:"news",title:a.title,author:t.author?String(t.author):"",abstract:t.abstract?String(t.abstract):"",content:_,auto_mount_goods:"1",len:String(_.length),vertical_cover:l,cover_images:JSON.stringify(j),_cover_images_map:JSON.stringify(D)},S="&activity_list%5B0%5D%5Bid%5D=408&activity_list%5B0%5D%5Bis_checked%5D=0&activity_list%5B1%5D%5Bid%5D=ttv&activity_list%5B1%5D%5Bis_checked%5D=1&activity_list%5B2%5D%5Bid%5D=reward&activity_list%5B2%5D%5Bis_checked%5D=1&activity_list%5B3%5D%5Bid%5D=aigc_bjh_status&activity_list%5B3%5D%5Bis_checked%5D=0&source_reprinted_allow=0&abstract_from=2&isBeautify=false&usingImgFilter=false&cover_layout=one",T="&source=upload&cover_source=upload&subtitle=&bjhtopic_id=&bjhtopic_info=&clue=1&bjhmt=&order_id=&aigc_rebuild=&image_edit_point=%5B%7B%22img_type%22%3A%22cover%22%2C%22img_num%22%3A%7B%22template%22%3A0%2C%22font%22%3A0%2C%22filter%22%3A0%2C%22paster%22%3A0%2C%22cut%22%3A0%2C%22any%22%3A0%7D%7D%2C%7B%22img_type%22%3A%22body%22%2C%22img_num%22%3A%7B%22template%22%3A0%2C%22font%22%3A0%2C%22filter%22%3A0%2C%22paster%22%3A0%2C%22cut%22%3A0%2C%22any%22%3A0%7D%7D%5D",B=new URLSearchParams(A).toString()+S+T,v=await fetch("https://baijiahao.baidu.com/pcui/article/publish?callback=bjhpublish",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",token:c},body:B}),N=(await v.text()).replace(/^bjhpublish\(/,"").replace(/\)$/,"");let h=null;try{h=JSON.parse(N)}catch(d){}if(!h||h.errno!==0||!h.ret)return{ok:!1,stage:"publish",status:v.status,message:h&&(h.errmsg||h.errno)||"百家号发布失败"};const I=h.ret.url||"";return{ok:!0,id:I,url:I,draft:!1}},checkAuth:async(a)=>{try{const t=await(await fetch("https://baijiahao.baidu.com/builder/app/appinfo?_="+Date.now(),{credentials:"include"})).json();if(t.errmsg==="success"&&t.data&&t.data.user){const r=t.data.user;return{isAuthenticated:!0,userId:String(r.userid||""),username:r.name||"",avatar:r.avatar||""}}return{isAuthenticated:!1}}catch(e){return{isAuthenticated:!1,error:String(e&&e.message||e)}}}};U({site:"baijiahao",name:"article",access:"write",description:"发布百家号文章。默认正式发布(需 --cover 封面),加 --draft 仅存草稿(草稿不需要封面)。正文默认 Markdown,图片自动转存到百家号图床。",domain:"baijiahao.baidu.com",strategy:k.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;传 --html 则视为原始 HTML)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"cover",help:"封面图 URL(正式发布必填;会先转存到百家号图床。草稿模式可不传)"},{name:"abstract",help:"文章摘要(可空,正式发布时写入;草稿模式忽略)"},{name:"author",help:"作者署名(可空,正式发布时写入;草稿模式忽略)"},{name:"draft",type:"boolean",help:"仅保存草稿,不正式发布(草稿不需要封面)"},{name:"execute",type:"boolean",help:"真正执行写操作。不加此参数时命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url","management_url"],func:async(a,e)=>{if(!a)throw new P("百家号文章发布需要浏览器会话");R(e);const t=String(e.title??"").trim();if(!t)throw new u("INVALID_INPUT","文章标题不能为空");const r=await E(e),i=Boolean(e.draft),n=typeof e.cover==="string"?e.cover.trim():"";if(!i&&!n)throw new u("INVALID_INPUT","正式发布百家号文章必须提供封面图:请传 --cover <图片URL>(或加 --draft 只存草稿)");const c={cover:n,abstract:typeof e.abstract==="string"?e.abstract:"",author:typeof e.author==="string"?e.author:""},s=await C(a,{title:t,body:r,format:e.html?"html":"markdown",draftOnly:i,profile:baijiahaoProfile,publishParams:c}),b=s.images.uploaded.length|0,f=s.images.failed.length|0;let w=s.draft?"已保存百家号文章草稿":"已正式发布百家号文章";const l=s.url;if(!s.draft){s.url="";try{Object.assign(s,await resolvePublishedReceipt(a,l))}catch(_){w+=`;公开链接尚未确认:${_ instanceof Error?_.message:String(_)}。请用 lists 按预览 nid 回查,禁止重复发布`}}if(b||f)w+=`(图片:${b} 张已转存${f?`,${f} 张失败`:""})`;return J(w,"article","",s.draft?"draft":"created",{created_target:"article:"+s.id,created_url:s.url,management_url:l})}});
@@ -1 +1 @@
1
- import{cli as p,Strategy as f}from"@jackwener/opencli/registry";import{ArgumentError as c,CommandExecutionError as u}from"@jackwener/opencli/errors";import{apiGet as d,apiPost as y,requireOkPayload as l,resolveVideoIds as h}from"./utils.js";p({site:"bilibili",name:"coin",access:"write",description:"给 B站视频投币(官方 API,需登录;消耗硬币不可撤销,需 --execute)",domain:"www.bilibili.com",strategy:f.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID / URL / b23.tv short link"},{name:"count",type:"int",default:1,help:"投币数量,1 或 2(单视频累计上限 2 枚)"},{name:"with-like",type:"boolean",help:"投币同时点赞"},{name:"execute",type:"boolean",help:"Actually spend coins. Without it the command refuses to write."}],columns:["bvid","coined","total","status","url"],func:async(o,n)=>{if(!o)throw new u("Browser session required for bilibili coin");const i=Number(n.count??1);if(i!==1&&i!==2)throw new c("bilibili coin --count must be 1 or 2");if(!n.execute)throw new c("Refusing to spend coins: pass --execute to actually pay coins");const{bvid:e,aid:a}=await h(o,n.bvid),s=`https://www.bilibili.com/video/${e}`,m=await d(o,"/x/web-interface/archive/coins",{params:{aid:a,bvid:e}}),t=Number(l(m,"archive coins")?.multiply??0);if(t>=2)return[{bvid:e,coined:0,total:t,status:"already-maxed",url:s}];if(t+i>2)throw new c(`已投 ${t} 枚,再投 ${i} 枚会超过单视频 2 枚上限;改用 --count ${2-t}`);const b=await y(o,"/x/web-interface/coin/add",{params:{aid:a,bvid:e,multiply:i,like:n["with-like"]?1:0}});l(b,"coin add");const w=await d(o,"/x/web-interface/archive/coins",{params:{aid:a,bvid:e}}),r=Number(l(w,"archive coins")?.multiply??0);if(r<t+i)throw new u(`Bilibili coin did not verify: multiply=${r} after paying ${i}`);return[{bvid:e,coined:i,total:r,status:"coined",url:s}]}});
1
+ import{cli as f,Strategy as h}from"@jackwener/opencli/registry";import{ArgumentError as s,CommandExecutionError as c}from"@jackwener/opencli/errors";import{apiGet as d,apiPost as y,requireOkPayload as m,resolveVideoIds as v}from"./utils.js";function w(t){const i=m(t,"archive coins")?.multiply;if(!Number.isInteger(i)||i<0||i>2)throw new c("Bilibili archive coins returned a missing or invalid multiply");return i}f({site:"bilibili",name:"coin",access:"write",description:"给 B站视频投币(官方 API,需登录;消耗硬币不可撤销,需 --execute)",domain:"www.bilibili.com",strategy:h.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID / URL / b23.tv short link"},{name:"count",type:"int",default:1,help:"投币数量,1 或 2(单视频累计上限 2 枚)"},{name:"with-like",type:"boolean",help:"投币同时点赞"},{name:"execute",type:"boolean",help:"Actually spend coins. Without it the command refuses to write."}],columns:["bvid","coined","total","status","url"],func:async(t,i)=>{if(!t)throw new c("Browser session required for bilibili coin");const e=Number(i.count??1);if(e!==1&&e!==2)throw new s("bilibili coin --count must be 1 or 2");if(!i.execute)throw new s("Refusing to spend coins: pass --execute to actually pay coins");const{bvid:o,aid:l}=await v(t,i.bvid),u=`https://www.bilibili.com/video/${o}`,b=await d(t,"/x/web-interface/archive/coins",{params:{aid:l,bvid:o}}),n=w(b);if(n>=2)return[{bvid:o,coined:0,total:n,status:"already-maxed",url:u}];if(n+e>2)throw new s(`已投 ${n} 枚,再投 ${e} 枚会超过单视频 2 枚上限;改用 --count ${2-n}`);const p=await y(t,"/x/web-interface/coin/add",{params:{aid:l,bvid:o,multiply:e,like:i["with-like"]?1:0}});m(p,"coin add");let a;try{const r=await d(t,"/x/web-interface/archive/coins",{params:{aid:l,bvid:o}});a=w(r);if(a<n+e)throw new c(`multiply=${a}, expected at least ${n+e}`)}catch(r){throw new c(`Bilibili 已受理 ${o} 的 ${e} 枚投币请求,但回查未确认:${r instanceof Error?r.message:String(r)}`,"硬币可能已扣除,禁止直接重投。请核对当前账号的硬币余额、投币记录和目标视频;本次请求只提交了一次。")}return[{bvid:o,coined:e,total:a,status:"coined",url:u}]}});
@@ -0,0 +1,5 @@
1
+ import{cli as N,Strategy as k}from"@jackwener/opencli/registry";import{ArgumentError as e,AuthRequiredError as I,CommandExecutionError as r}from"@jackwener/opencli/errors";import{apiGet as _,apiPost as S,requireOkPayload as h,resolveBvid as x,parsePageArg as P,selectVideoPart as U}from"./utils.js";import{assertLiteralContent as E}from"../_shared/content-guard.js";N({site:"bilibili",name:"danmaku",access:"write",description:"给 B站视频指定时间发送普通滚动弹幕(默认预览,--execute 才发送)",domain:"www.bilibili.com",strategy:k.COOKIE,browser:!0,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID / URL / b23.tv short link"},{name:"message",required:!0,positional:!0,fromFile:!0,help:"弹幕正文,亦可使用 --file"},{name:"at",required:!0,help:"弹幕出现时间,单位秒,支持小数(例如 12.5)"},{name:"page",help:"分 P 序号,默认使用 URL 的 p 或第 1 P"},{name:"execute",type:"boolean",help:"实际发送一次;平台受理不代表公开可见"}],columns:["dmid","bvid","cid","page","at","message","status","url"],func:async(n,t)=>{const m=String(t.message??"");if(!m.trim())throw new e("弹幕正文不能为空");E(m,{label:"弹幕正文"});const g=String(t.at??"");if(!/^\d+(?:\.\d+)?$/.test(g))throw new e("--at 必须是非负秒数,例如 0 或 12.5");const s=Math.ceil(Number(g)*1000);if(!Number.isSafeInteger(s))throw new e("--at 超出有效范围");let u=null;if(/^https?:\/\//i.test(String(t.bvid))){let i;try{i=new URL(t.bvid)}catch{throw new e("视频 URL 无效")}if(i.searchParams.has("p")){const f=i.searchParams.getAll("p");if(f.length!==1||!f[0])throw new e("视频 URL 的 p 参数无效");u=P(f[0])}}const w=P(t.page);if(w!==null&&u!==null&&w!==u)throw new e("--page 与视频 URL 的 p 不一致,请明确目标分 P");const c=w??u??1;let d;try{d=await x(t.bvid)}catch(i){throw new e("无法解析视频 BV ID",String(i))}if(!n)throw new r("Browser session required for bilibili danmaku");const l=h(await _(n,"/x/web-interface/view",{params:{bvid:d}}),"view");if(l?.bvid!==d||!Number.isSafeInteger(l?.aid)||l.aid<=0)throw new r("Bilibili view 返回的视频 ID 与目标不符或 aid 无效");const a=U(l,c);if(!Number.isFinite(a.duration)||a.duration<=0)throw new r("Bilibili view 未返回有效分 P 时长");if(s>=a.duration*1000)throw new e(`--at 必须小于第 ${c} P 的时长 ${a.duration} 秒`);const v=h(await _(n,"/x/web-interface/nav"),"nav");if(v?.isLogin!==!0||!v?.mid)throw new I("bilibili.com");const y={dmid:null,bvid:d,cid:String(a.cid),page:c,at:s/1000,message:m,status:"dry-run",url:`https://www.bilibili.com/video/${d}/?p=${c}&t=${s/1000}`};if(!t.execute)return[y];const p=await n.evaluate(`() => {
2
+ const key = '__NANO_UNIQUE_IDENTIFIER__';
3
+ if (typeof document[key] !== 'number' || isNaN(document[key])) document[key] = 0;
4
+ return ++document[key];
5
+ }`);if(!Number.isSafeInteger(p)||p<=0)throw new r("无法生成播放器弹幕请求编号");let o;try{o=await S(n,"/x/v2/dm/post",{params:{type:1,oid:a.cid,aid:l.aid,msg:m,progress:s,rnd:p,mode:1,fontsize:25,color:16777215,pool:0,plat:1,checkbox_type:0,gaiasource:"main_web",polaris_app_id:100,polaris_platform:5}})}catch(i){throw new r(`弹幕发送结果未确认:${String(i)}`,"请求可能已生效,禁止直接重发;请回查目标视频。")}if(!o||typeof o.code!=="number"||o.code===-1)throw new r(`弹幕发送回执无效,结果未确认:${o?.message??"missing response code"}`,"禁止直接重发,请回查目标视频。");const b=h(o,"danmaku post");if(typeof b?.dmid_str!=="string"||!/^[1-9]\d*$/.test(b.dmid_str))throw new r("平台已受理弹幕但未返回精确 dmid_str,结果未确认","禁止直接重发,请回查目标视频。");return[{...y,dmid:b.dmid_str,status:"accepted"}]}});
@@ -1 +1 @@
1
- import{cli as y,Strategy as h}from"@jackwener/opencli/registry";import{ArgumentError as c,CommandExecutionError as l}from"@jackwener/opencli/errors";import{apiGet as _,apiPost as g,getSelfUid as S,requireOkPayload as m,resolveVideoIds as I}from"./utils.js";async function u(t,o){const i=await S(t),a=await _(t,"/x/v3/fav/folder/created/list-all",{params:{up_mid:i,type:2,rid:o},signed:!0}),d=m(a,"fav folder list")?.list??[];if(!Array.isArray(d)||d.length===0)throw new l("Bilibili fav folder list returned no folders for this account");return d}function v(t,o){const i=String(o??"").trim();if(!i)return t[0];const a=t.find((r)=>String(r.id)===i);if(a)return a;const e=t.filter((r)=>String(r.title??"")===i);if(e.length===1)return e[0];if(e.length>1)throw new c(`收藏夹标题「${i}」有 ${e.length} 个同名,请改用 ID:${e.map((r)=>r.id).join(", ")}`);const d=t.map((r)=>`${r.id}=${r.title}`).join(", ");throw new c(`找不到收藏夹「${i}」;现有收藏夹:${d}`)}y({site:"bilibili",name:"favorite-add",access:"write",description:"把 B站视频加入 / 移出收藏夹(官方 API,需登录;--folder 缺省用默认收藏夹)",domain:"www.bilibili.com",strategy:h.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID / URL / b23.tv short link"},{name:"folder",help:"目标收藏夹 ID 或标题(缺省用默认收藏夹)"},{name:"remove",type:"boolean",help:"从收藏夹移出(默认是加入)"}],columns:["bvid","folder_id","folder","status","url"],func:async(t,o)=>{if(!t)throw new l("Browser session required for bilibili favorite-add");const i=Boolean(o.remove),{bvid:a,aid:e}=await I(t,o.bvid),d=`https://www.bilibili.com/video/${a}`,r=await u(t,e),n=v(r,o.folder),w=Number(n.fav_state??0)===1,s={bvid:a,folder_id:String(n.id),folder:String(n.title??""),url:d};if(w===!i)return[{...s,status:i?"not-in-folder":"already-favorited"}];const b=await g(t,"/x/v3/fav/resource/deal",{params:{rid:e,type:2,add_media_ids:i?"":String(n.id),del_media_ids:i?String(n.id):""}});m(b,"fav resource deal");const f=(await u(t,e)).find((p)=>String(p.id)===String(n.id));if(Number(f?.fav_state??0)!==(i?0:1))throw new l(`Bilibili favorite did not verify: fav_state=${f?.fav_state} after ${i?"remove":"add"}`);return[{...s,status:i?"removed":"favorited"}]}});export const __test__={pickFolder:v};
1
+ import{cli as g,Strategy as y}from"@jackwener/opencli/registry";import{ArgumentError as v,CommandExecutionError as s}from"@jackwener/opencli/errors";import{apiGet as S,apiPost as B,getSelfUid as I,requireOkPayload as m,resolveVideoIds as E}from"./utils.js";async function u(t,a){const i=await I(t),o=await S(t,"/x/v3/fav/folder/created/list-all",{params:{up_mid:i,type:2,rid:a},signed:!0}),n=m(o,"fav folder list")?.list??[];if(!Array.isArray(n)||n.length===0)throw new s("Bilibili fav folder list returned no folders for this account");return n}function w(t,a){const i=String(a??"").trim();if(!i)return t[0];const o=t.find((r)=>String(r.id)===i);if(o)return o;const e=t.filter((r)=>String(r.title??"")===i);if(e.length===1)return e[0];if(e.length>1)throw new v(`收藏夹标题「${i}」有 ${e.length} 个同名,请改用 ID:${e.map((r)=>r.id).join(", ")}`);const n=t.map((r)=>`${r.id}=${r.title}`).join(", ");throw new v(`找不到收藏夹「${i}」;现有收藏夹:${n}`)}function b(t){if(!t||t.fav_state!==0&&t.fav_state!==1)throw new s("Bilibili fav folder list is missing the target folder or its fav_state");return t.fav_state===1}g({site:"bilibili",name:"favorite-add",access:"write",description:"把 B站视频加入 / 移出收藏夹(官方 API,需登录;--folder 缺省用默认收藏夹)",domain:"www.bilibili.com",strategy:y.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID / URL / b23.tv short link"},{name:"folder",help:"目标收藏夹 ID 或标题(缺省用默认收藏夹)"},{name:"remove",type:"boolean",help:"从收藏夹移出(默认是加入)"}],columns:["bvid","folder_id","folder","status","url"],func:async(t,a)=>{if(!t)throw new s("Browser session required for bilibili favorite-add");const i=Boolean(a.remove),{bvid:o,aid:e}=await E(t,a.bvid),n=`https://www.bilibili.com/video/${o}`,r=await u(t,e),d=w(r,a.folder),h=b(d),f={bvid:o,folder_id:String(d.id),folder:String(d.title??""),url:n};if(h===!i)return[{...f,status:i?"not-in-folder":"already-favorited"}];const p=await B(t,"/x/v3/fav/resource/deal",{params:{rid:e,type:2,add_media_ids:i?"":String(d.id),del_media_ids:i?String(d.id):""}});m(p,"fav resource deal");try{const c=(await u(t,e)).find((_)=>String(_.id)===String(d.id));if(b(c)!==!i)throw new s(`fav_state=${c.fav_state} after ${i?"remove":"add"}`)}catch(l){throw new s(`Bilibili 已受理 ${o} 在收藏夹 ${d.id} 的${i?"移除":"添加"}请求,但回查未确认:${l instanceof Error?l.message:String(l)}`,"请先在同一账号的目标收藏夹核对该视频,禁止直接重跑写命令;本次请求只提交了一次。")}return[{...f,status:i?"removed":"favorited"}]}});export const __test__={pickFolder:w};
@@ -1 +1 @@
1
- import*as B from"node:fs";import*as z from"node:path";import{cli as A,Strategy as F}from"@jackwener/opencli/registry";import{ArgumentError as P,AuthRequiredError as T,CommandExecutionError as y}from"@jackwener/opencli/errors";import{resolveVideoFile as L,resolveImageFile as N,videoTimeoutArg as D}from"../_shared/video-publish.js";import{setInputFiles as J}from"../_shared/file-inject.js";const R={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}},C="bda2",M=["SESSDATA","bili_jct","DedeUserID"],q="https://member.bilibili.com/platform/home",O="__pp_bili_video_input";function K(t){var e=document.getElementById(t.inputId);if(!e||!e.files||!e.files[0])return{error:"文件未就绪(input 为空)"};var n=e.files[0],r=n.size,o=t.line;return async function(){try{var a=new URLSearchParams({profile:"ugcfx/bup",name:n.name,size:String(r),r:o.os,ssl:"0",version:"2.14.0",build:"2100400",upcdn:o.upcdn,probe_version:String(o.probe_version)}),i=await(await fetch("https://member.bilibili.com/preupload?"+a.toString(),{credentials:"include",headers:{Referer:"https://www.bilibili.com"}})).json();if(i.OK!==1)return{error:"preupload 失败:"+JSON.stringify(i)};var s="https:"+i.endpoint+"/"+String(i.upos_uri).replace(/^upos:\/\//,""),b=new URLSearchParams({uploads:"",output:"json",profile:"ugcfx/bup",filesize:String(r),partsize:String(i.chunk_size),biz_id:String(i.biz_id)}),m=await(await fetch(s+"?"+b.toString(),{method:"POST",headers:{"x-upos-auth":i.auth}})).json();if(m.OK!==1)return{error:"获取 upload_id 失败:"+JSON.stringify(m)};var S=Math.max(1,Math.ceil(r/i.chunk_size));return{url:s,auth:i.auth,uploadId:m.upload_id,chunkSize:i.chunk_size,bizId:i.biz_id,size:r,name:n.name,total:S}}catch(h){return{error:"preupload 异常:"+String(h&&h.message||h)}}}()}function V(t){var e=document.getElementById(t.inputId);if(!e||!e.files||!e.files[0])return{ok:!1,error:"文件丢失(页面可能已导航)"};var n=e.files[0],r=t.idx*t.chunkSize,o=n.slice(r,Math.min(r+t.chunkSize,t.size)),a=o.size,i=new URLSearchParams({partNumber:String(t.idx+1),uploadId:String(t.uploadId),chunk:String(t.idx),chunks:String(t.total),size:String(a),start:String(r),end:String(r+a),total:String(t.size)});return async function(){for(var s=0;s<5;s++){try{var b=await fetch(t.url+"?"+i.toString(),{method:"PUT",headers:{"x-upos-auth":t.auth},body:o});if(b.status<400){var m=await b.text();if(m==="MULTIPART_PUT_SUCCESS"||m==="")return{ok:!0}}}catch(S){}await new Promise(function(S){setTimeout(S,800*(s+1))})}return{ok:!1,error:"分块 "+t.idx+" 多次重试仍失败"}}()}function G(t){var e=[];for(var n=1;n<=t.total;n++)e.push({partNumber:n,eTag:"etag"});var r=new URLSearchParams({output:"json",name:t.name,profile:"ugcfx/bup",uploadId:String(t.uploadId),biz_id:String(t.bizId)});return async function(){try{var o=await(await fetch(t.url+"?"+r.toString(),{method:"POST",headers:{"x-upos-auth":t.auth,"content-type":"application/json; charset=UTF-8"},body:JSON.stringify({parts:e})})).json();if(o.OK!==1)return{error:"complete 失败:"+JSON.stringify(o)};var a=String(o.key).replace(/^\//,"").replace(/\.[^.]+$/,"");return{filename:a,cid:t.bizId}}catch(i){return{error:"complete 异常:"+String(i&&i.message||i)}}}()}function H(t){return async function(){try{var e=await fetch("https://member.bilibili.com/x/vupre/web/topic/type?type_id="+encodeURIComponent(t.tid)+"&pn=0&ps=200",{credentials:"include"}),n=await e.json();if(!n||n.code!==0||!n.data||!Array.isArray(n.data.topics))return{error:"获取话题列表失败:"+(n&&n.message||"code="+(n&&n.code))};var r=null;for(var o=0;o<n.data.topics.length;o++)if(Number(n.data.topics[o].topic_id)===Number(t.topicId)){r=n.data.topics[o];break}if(!r)return{error:"topic_id "+t.topicId+" 不在分区 "+t.tid+" 的可用话题里(用 bilibili topics --tid "+t.tid+" 查合法值)"};return{topicId:Number(r.topic_id),missionId:r.mission_id!=null?Number(r.mission_id):null,name:r.topic_name||""}}catch(a){return{error:"解析话题异常:"+String(a&&a.message||a)}}}()}function Q(t){return async function(){var e=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!e)return{error:"未找到 bili_jct CSRF token"};var n=decodeURIComponent(e[1]);try{var r=t.dataUri;if(typeof r==="string"&&r.indexOf("data:image/webp")===0){var o=r.indexOf(",");if(o===-1)return{error:"封面 data URI 格式非法"};var a=r.slice(5,o),i=r.slice(o+1),s;if(a.indexOf("base64")!==-1){var b=atob(i);s=new Uint8Array(b.length);for(var m=0;m<b.length;m++)s[m]=b.charCodeAt(m)}else s=new TextEncoder().encode(decodeURIComponent(i));var S=new Blob([s],{type:"image/webp"}),h=await createImageBitmap(S),v=document.createElement("canvas");v.width=h.width;v.height=h.height;var c=v.getContext("2d");c.fillStyle="#ffffff";c.fillRect(0,0,v.width,v.height);c.drawImage(h,0,0);var p=await new Promise(function(f){v.toBlob(f,"image/jpeg",0.92)});if(!p)return{error:"封面 webp 转码 JPEG 失败"};r=await new Promise(function(f,x){var w=new FileReader;w.onload=function(){f(String(w.result))};w.onerror=function(){x(Error("FileReader 读取转码结果失败"))};w.readAsDataURL(p)})}var _=new URLSearchParams;_.append("cover",r);_.append("csrf",n);var u=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:_.toString()})).json();if(!u||u.code!==0||!u.data||!u.data.url)return{error:"封面上传失败:"+(u&&u.message?u.message:JSON.stringify(u))};return{url:u.data.url}}catch(f){return{error:"封面上传异常:"+String(f&&f.message||f)}}}()}function W(t){return async function(){var e=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!e)return{error:"未找到 bili_jct CSRF token"};var n=decodeURIComponent(e[1]),r=t.meta;for(var o in r)if(r[o]===null||r[o]===void 0)delete r[o];r.csrf=n;try{var a=await fetch("https://member.bilibili.com/x/vu/web/add/v3?csrf="+encodeURIComponent(n)+"&t="+Date.now(),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(r)}),i=await a.json();if(!i||i.code!==0)return{error:"add/v3 提交失败:"+(i&&i.message?i.message:"")+"(code="+(i&&i.code)+" status="+a.status+")"};return{bvid:i.data&&i.data.bvid,aid:i.data&&i.data.aid}}catch(s){return{error:"add/v3 异常:"+String(s&&s.message||s)}}}()}function X(t){return async function(){var e=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!e)return{error:"未找到 bili_jct CSRF token"};var n=decodeURIComponent(e[1]),r=t.body;r.csrf=n;try{var o=await fetch("https://member.bilibili.com/x/vupre/web/draft/add?t="+Date.now()+"&csrf="+encodeURIComponent(n),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(r)}),a=await o.json();if(!a||a.code!==0)return{error:"draft/add 保存草稿失败:"+(a&&a.message?a.message:"")+"(code="+(a&&a.code)+" status="+o.status+")"};return{data:a.data}}catch(i){return{error:"draft/add 异常:"+String(i&&i.message||i)}}}()}const g=(t,e,n)=>t.evaluateWithArgs(`(${e.toString()})(a)`,{a:n});function Y(t){const e=N(t),n=z.extname(e).toLowerCase(),r=n===".png"?"image/png":n===".webp"?"image/webp":n===".bmp"?"image/bmp":"image/jpeg",o=B.readFileSync(e).toString("base64");return`data:${r};base64,${o}`}async function Z(t){for(let e=0;e<8;e++){await t.goto(q);await t.wait({time:1});let n="";try{n=String(await t.evaluate("location.href")||"")}catch{n=""}if(/^https?:\/\/member\.bilibili\.com\//.test(n)){const r=await t.getCookies({url:"https://member.bilibili.com"}),o=new Map;for(const i of Array.isArray(r)?r:[])if(i&&typeof i.name==="string")o.set(i.name,String(i.value??""));const a=M.filter((i)=>!o.get(i));if(!a.length)return o;throw new T("member.bilibili.com",`缺少 B站登录 cookie(${a.join(", ")})。请先在客户端登录哔哩哔哩。`)}}throw new T("member.bilibili.com","无法进入 B站创作中心(可能未登录或被风控)。请先在客户端登录哔哩哔哩。")}A({site:"bilibili",name:"upload",access:"write",description:"投稿视频到 B站(在真实浏览器登录态里走 web 投稿:preupload→upos 分块→add/v3)。默认仅 dry-run 校验,加 --execute 才真正上传并提交。",domain:"member.bilibili.com",strategy:F.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(R).join("/")}(默认 ${C})`},{name:"concurrency",type:"number",help:"分块并发数(默认 3)"},{name:"draft",type:"boolean",help:"存草稿而非直接发布(上传视频后存到创作中心草稿箱,不公开)"},{name:"execute",type:"boolean",help:"真正发布投稿;不带(也不带 --draft)则只做 dry-run 校验"},D()],columns:["status","title","bvid","url"],func:async(t,e)=>{if(!t)throw new y("bilibili upload 需要浏览器会话");const n=L(String(e.file??"")),r=String(e.title??"").trim()||z.basename(n).replace(/\.[^.]+$/,""),o=String(e.tag??"").split(",").map((d)=>d.trim()).filter(Boolean);if(!o.length)throw new P("B站投稿至少需要 1 个标签,用 --tag 传(逗号分隔)");if(e.tid==null||e.tid===""||!Number.isFinite(Number(e.tid)))throw new P("必须用 --tid 指定分区 id(数字)。合法值用 `bilibili partitions` 列举,禁止臆造。");const a=Number(e.tid),i=e.copyright!=null&&e.copyright!==""?Number(e.copyright):1;if(i!==1&&i!==2)throw new P("--copyright 只能是 1(自制) 或 2(转载)");const s=String(e.source??"").trim();if(i===2&&!s)throw new P("转载(copyright=2)必须用 --source 提供转载来源");if(e.cover)N(String(e.cover));const b=String(e.line??C),m=R[b];if(!m)throw new P(`未知上传线路「${b}」,可选:${Object.keys(R).join("/")}`);const S=e.concurrency!=null&&e.concurrency!==""?Number(e.concurrency):3,h=Boolean(e.draft),v=Boolean(e.execute)&&!h;await Z(t);await t.evaluate(`(() => { var id=${JSON.stringify(O)}; 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 J(t,`#${O}`,[n]);if(!h&&!v)return{status:"dry-run",title:r,bvid:"",url:`校验通过:登录态有效、视频已注入就绪、分区 ${a}、标签 [${o.join(", ")}]、版权 ${i===1?"自制":"转载"}。加 --execute 发布,或 --draft 存草稿。`};const c=await g(t,K,{inputId:O,line:m});if(!c||c.error)throw new y(`B站 preupload 失败:${c&&c.error||"无返回"}`);for(let d=0;d<c.total;d++){const l=await g(t,V,{inputId:O,url:c.url,auth:c.auth,uploadId:c.uploadId,idx:d,total:c.total,size:c.size,chunkSize:c.chunkSize});if(!l||!l.ok)throw new y(`B站分块上传失败(${d+1}/${c.total}):${l&&l.error||"无返回"}`)}const p=await g(t,G,{url:c.url,auth:c.auth,uploadId:c.uploadId,bizId:c.bizId,name:c.name,total:c.total});if(!p||p.error||!p.filename)throw new y(`B站 complete 失败:${p&&p.error||"未拿到 filename"}`);let _="";if(e.cover){const d=Y(String(e.cover)),l=await g(t,Q,{dataUri:d});if(!l||l.error||!l.url)throw new y(`B站封面上传失败:${l&&l.error||"未拿到 url"}`);_=l.url}let u=null,f=null;if(e.topic!=null&&e.topic!==""){if(!Number.isFinite(Number(e.topic)))throw new P("--topic 必须是 topic_id 数字,用 `bilibili topics --tid <tid>` 取合法值");const d=await g(t,H,{tid:a,topicId:Number(e.topic)});if(!d||d.error)throw new y(`B站话题解析失败:${d&&d.error||"无返回"}`);u=d.topicId;f=d.missionId}if(h){const d={videos:[{filename:p.filename,title:r,desc:"",cid:p.cid,is_4k:!1,is_8k:!1,is_hdr:!1}],cover:_,cover43:_,ai_cover:0,is_ab_cover:0,ab_cover_info:null,title:r,copyright:i,tid:a,tag:o.join(","),desc:String(e.desc??""),recreate:-1,dynamic:String(e.dynamic??""),is_only_self:0,space_hidden:2,watermark:{state:0},no_reprint:e["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(i===2)d.source=s;if(u!=null){d.topic_id=u;d.topic_detail={from_topic_id:u,from_source:"arc.web.search"};if(f!=null)d.mission_id=f}const l=await g(t,X,{body:d});if(!l||l.error)throw new y(`B站 ${l&&l.error||"draft/add 无返回"}`);const U=l.data&&(l.data.aid!=null?l.data.aid:l.data.draft_id!=null?l.data.draft_id:l.data.id);return{status:"✅ 草稿已保存",title:r,bvid:U!=null?`draft:${U}`:"",url:"https://member.bilibili.com/platform/upload-manager/article?group=draft"}}const x=e.dtime!=null&&e.dtime!==""?Number(e.dtime):null,w={title:r,copyright:i,tid:a,tag:o.join(","),mission_id:f,topic_id:u,topic_detail:u!=null?{from_topic_id:u,from_source:"arc.web.recommend"}:null,desc_format_id:9999,desc:String(e.desc??""),dtime:x,recreate:-1,dynamic:String(e.dynamic??""),interactive:0,act_reserve_create:0,no_disturbance:0,porder:null,adorder_type:9,no_reprint:e["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:i===2?s:null,watermark:{state:0},cover:_,videos:[{title:r,desc:"",filename:p.filename,cid:p.cid}]},I=await g(t,W,{meta:w});if(!I||I.error)throw new y(`B站 ${I&&I.error||"add/v3 无返回"}`);const E=I.bvid?String(I.bvid):"";return{status:e.dtime?"✅ 定时投稿已提交":"✅ 投稿成功",title:r,bvid:E,url:E?`https://www.bilibili.com/video/${E}`:"(已提交,稍后在创作中心查看)"}}});
1
+ import*as B from"node:fs";import*as N from"node:path";import{cli as L,Strategy as F}from"@jackwener/opencli/registry";import{ArgumentError as w,AuthRequiredError as U,CommandExecutionError as v}from"@jackwener/opencli/errors";import{resolveVideoFile as D,resolveImageFile as z,videoTimeoutArg as J}from"../_shared/video-publish.js";import{setInputFiles as M}from"../_shared/file-inject.js";const O={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}},A="bda2",q=["SESSDATA","bili_jct","DedeUserID"],K="https://member.bilibili.com/platform/home",T="__pp_bili_video_input";export async function PP_RESOLVE_AI_STATEMENT(){try{const t=await fetch("https://member.bilibili.com/x/vupre/web/archive/pre?lang=cn",{credentials:"include"}),e=await t.json();if(!t.ok||e?.code!==0)return{error:`读取创作声明失败:HTTP ${t.status} / ${e?.message??e?.code}`};const n=e.data?.neutral_mark?.mark_list;if(!Array.isArray(n))return{error:"当前账号的投稿配置缺少创作声明列表"};const r=n.filter((o)=>o.content==="含AI生成内容");if(r.length!==1||!Number.isSafeInteger(r[0].id)||r[0].id<=0)return{error:"当前账号未返回唯一有效的“含AI生成内容”选项"};return{id:r[0].id,label:r[0].content}}catch(t){return{error:`读取创作声明失败:${t instanceof Error?t.message:String(t)}`}}}function V(t){var e=document.getElementById(t.inputId);if(!e||!e.files||!e.files[0])return{error:"文件未就绪(input 为空)"};var n=e.files[0],r=n.size,o=t.line;return async function(){try{var a=new URLSearchParams({profile:"ugcfx/bup",name:n.name,size:String(r),r:o.os,ssl:"0",version:"2.14.0",build:"2100400",upcdn:o.upcdn,probe_version:String(o.probe_version)}),i=await(await fetch("https://member.bilibili.com/preupload?"+a.toString(),{credentials:"include",headers:{Referer:"https://www.bilibili.com"}})).json();if(i.OK!==1)return{error:"preupload 失败:"+JSON.stringify(i)};var s="https:"+i.endpoint+"/"+String(i.upos_uri).replace(/^upos:\/\//,""),h=new URLSearchParams({uploads:"",output:"json",profile:"ugcfx/bup",filesize:String(r),partsize:String(i.chunk_size),biz_id:String(i.biz_id)}),m=await(await fetch(s+"?"+h.toString(),{method:"POST",headers:{"x-upos-auth":i.auth}})).json();if(m.OK!==1)return{error:"获取 upload_id 失败:"+JSON.stringify(m)};var g=Math.max(1,Math.ceil(r/i.chunk_size));return{url:s,auth:i.auth,uploadId:m.upload_id,chunkSize:i.chunk_size,bizId:i.biz_id,size:r,name:n.name,total:g}}catch(_){return{error:"preupload 异常:"+String(_&&_.message||_)}}}()}function H(t){var e=document.getElementById(t.inputId);if(!e||!e.files||!e.files[0])return{ok:!1,error:"文件丢失(页面可能已导航)"};var n=e.files[0],r=t.idx*t.chunkSize,o=n.slice(r,Math.min(r+t.chunkSize,t.size)),a=o.size,i=new URLSearchParams({partNumber:String(t.idx+1),uploadId:String(t.uploadId),chunk:String(t.idx),chunks:String(t.total),size:String(a),start:String(r),end:String(r+a),total:String(t.size)});return async function(){for(var s=0;s<5;s++){try{var h=await fetch(t.url+"?"+i.toString(),{method:"PUT",headers:{"x-upos-auth":t.auth},body:o});if(h.status<400){var m=await h.text();if(m==="MULTIPART_PUT_SUCCESS"||m==="")return{ok:!0}}}catch(g){}await new Promise(function(g){setTimeout(g,800*(s+1))})}return{ok:!1,error:"分块 "+t.idx+" 多次重试仍失败"}}()}function G(t){var e=[];for(var n=1;n<=t.total;n++)e.push({partNumber:n,eTag:"etag"});var r=new URLSearchParams({output:"json",name:t.name,profile:"ugcfx/bup",uploadId:String(t.uploadId),biz_id:String(t.bizId)});return async function(){try{var o=await(await fetch(t.url+"?"+r.toString(),{method:"POST",headers:{"x-upos-auth":t.auth,"content-type":"application/json; charset=UTF-8"},body:JSON.stringify({parts:e})})).json();if(o.OK!==1)return{error:"complete 失败:"+JSON.stringify(o)};var a=String(o.key).replace(/^\//,"").replace(/\.[^.]+$/,"");return{filename:a,cid:t.bizId}}catch(i){return{error:"complete 异常:"+String(i&&i.message||i)}}}()}function Q(t){return async function(){try{var e=await fetch("https://member.bilibili.com/x/vupre/web/topic/type?type_id="+encodeURIComponent(t.tid)+"&pn=0&ps=200",{credentials:"include"}),n=await e.json();if(!n||n.code!==0||!n.data||!Array.isArray(n.data.topics))return{error:"获取话题列表失败:"+(n&&n.message||"code="+(n&&n.code))};var r=null;for(var o=0;o<n.data.topics.length;o++)if(Number(n.data.topics[o].topic_id)===Number(t.topicId)){r=n.data.topics[o];break}if(!r)return{error:"topic_id "+t.topicId+" 不在分区 "+t.tid+" 的可用话题里(用 bilibili topics --tid "+t.tid+" 查合法值)"};return{topicId:Number(r.topic_id),missionId:r.mission_id!=null?Number(r.mission_id):null,name:r.topic_name||""}}catch(a){return{error:"解析话题异常:"+String(a&&a.message||a)}}}()}function W(t){return async function(){var e=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!e)return{error:"未找到 bili_jct CSRF token"};var n=decodeURIComponent(e[1]);try{var r=t.dataUri;if(typeof r==="string"&&r.indexOf("data:image/webp")===0){var o=r.indexOf(",");if(o===-1)return{error:"封面 data URI 格式非法"};var a=r.slice(5,o),i=r.slice(o+1),s;if(a.indexOf("base64")!==-1){var h=atob(i);s=new Uint8Array(h.length);for(var m=0;m<h.length;m++)s[m]=h.charCodeAt(m)}else s=new TextEncoder().encode(decodeURIComponent(i));var g=new Blob([s],{type:"image/webp"}),_=await createImageBitmap(g),y=document.createElement("canvas");y.width=_.width;y.height=_.height;var p=y.getContext("2d");p.fillStyle="#ffffff";p.fillRect(0,0,y.width,y.height);p.drawImage(_,0,0);var d=await new Promise(function(u){y.toBlob(u,"image/jpeg",0.92)});if(!d)return{error:"封面 webp 转码 JPEG 失败"};r=await new Promise(function(u,I){var P=new FileReader;P.onload=function(){u(String(P.result))};P.onerror=function(){I(Error("FileReader 读取转码结果失败"))};P.readAsDataURL(d)})}var b=new URLSearchParams;b.append("cover",r);b.append("csrf",n);var f=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(!f||f.code!==0||!f.data||!f.data.url)return{error:"封面上传失败:"+(f&&f.message?f.message:JSON.stringify(f))};return{url:f.data.url}}catch(u){return{error:"封面上传异常:"+String(u&&u.message||u)}}}()}function X(t){return async function(){var e=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!e)return{error:"未找到 bili_jct CSRF token"};var n=decodeURIComponent(e[1]),r=t.meta;for(var o in r)if(r[o]===null||r[o]===void 0)delete r[o];r.csrf=n;try{var a=await fetch("https://member.bilibili.com/x/vu/web/add/v3?csrf="+encodeURIComponent(n)+"&t="+Date.now(),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(r)}),i=await a.json();if(!i||i.code!==0)return{error:"add/v3 提交失败:"+(i&&i.message?i.message:"")+"(code="+(i&&i.code)+" status="+a.status+")"};return{bvid:i.data&&i.data.bvid,aid:i.data&&i.data.aid}}catch(s){return{error:"add/v3 异常:"+String(s&&s.message||s)}}}()}function Y(t){return async function(){var e=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!e)return{error:"未找到 bili_jct CSRF token"};var n=decodeURIComponent(e[1]),r=t.body;r.csrf=n;try{var o=await fetch("https://member.bilibili.com/x/vupre/web/draft/add?t="+Date.now()+"&csrf="+encodeURIComponent(n),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(r)}),a=await o.json();if(!a||a.code!==0)return{error:"draft/add 保存草稿失败:"+(a&&a.message?a.message:"")+"(code="+(a&&a.code)+" status="+o.status+")"};return{data:a.data}}catch(i){return{error:"draft/add 异常:"+String(i&&i.message||i)}}}()}const S=(t,e,n)=>t.evaluateWithArgs(`(${e.toString()})(a)`,{a:n});function Z(t){const e=z(t),n=N.extname(e).toLowerCase(),r=n===".png"?"image/png":n===".webp"?"image/webp":n===".bmp"?"image/bmp":"image/jpeg",o=B.readFileSync(e).toString("base64");return`data:${r};base64,${o}`}async function k(t){for(let e=0;e<8;e++){await t.goto(K);await t.wait({time:1});let n="";try{n=String(await t.evaluate("location.href")||"")}catch{n=""}if(/^https?:\/\/member\.bilibili\.com\//.test(n)){const r=await t.getCookies({url:"https://member.bilibili.com"}),o=new Map;for(const i of Array.isArray(r)?r:[])if(i&&typeof i.name==="string")o.set(i.name,String(i.value??""));const a=q.filter((i)=>!o.get(i));if(!a.length)return o;throw new U("member.bilibili.com",`缺少 B站登录 cookie(${a.join(", ")})。请先在客户端登录哔哩哔哩。`)}}throw new U("member.bilibili.com","无法进入 B站创作中心(可能未登录或被风控)。请先在客户端登录哔哩哔哩。")}L({site:"bilibili",name:"upload",access:"write",description:"投稿视频到 B站(在真实浏览器登录态里走 web 投稿:preupload→upos 分块→add/v3)。默认仅 dry-run 校验,加 --execute 才真正上传并提交。",domain:"member.bilibili.com",strategy:F.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:"ai-generated",type:"boolean",help:"声明含 AI 生成内容(发布和草稿均支持;按当前账号可用选项校验,不能与转载同时选择)"},{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(O).join("/")}(默认 ${A})`},{name:"concurrency",type:"number",help:"分块并发数(默认 3)"},{name:"draft",type:"boolean",help:"存草稿而非直接发布(上传视频后存到创作中心草稿箱,不公开)"},{name:"execute",type:"boolean",help:"真正发布投稿;不带(也不带 --draft)则只做 dry-run 校验"},J()],columns:["status","title","bvid","url"],func:async(t,e)=>{if(!t)throw new v("bilibili upload 需要浏览器会话");const n=D(String(e.file??"")),r=String(e.title??"").trim()||N.basename(n).replace(/\.[^.]+$/,""),o=String(e.tag??"").split(",").map((c)=>c.trim()).filter(Boolean);if(!o.length)throw new w("B站投稿至少需要 1 个标签,用 --tag 传(逗号分隔)");if(e.tid==null||e.tid===""||!Number.isFinite(Number(e.tid)))throw new w("必须用 --tid 指定分区 id(数字)。合法值用 `bilibili partitions` 列举,禁止臆造。");const a=Number(e.tid),i=e.copyright!=null&&e.copyright!==""?Number(e.copyright):1;if(i!==1&&i!==2)throw new w("--copyright 只能是 1(自制) 或 2(转载)");const s=String(e.source??"").trim();if(i===2&&!s)throw new w("转载(copyright=2)必须用 --source 提供转载来源");if(e["ai-generated"]&&i===2)throw new w("B站当前创作声明只能选一项,--ai-generated 不能与 --copyright 2 同时使用");if(e.cover)z(String(e.cover));const h=String(e.line??A),m=O[h];if(!m)throw new w(`未知上传线路「${h}」,可选:${Object.keys(O).join("/")}`);const g=e.concurrency!=null&&e.concurrency!==""?Number(e.concurrency):3,_=Boolean(e.draft),y=Boolean(e.execute)&&!_;await k(t);let p;if(e["ai-generated"]){p=await S(t,PP_RESOLVE_AI_STATEMENT,{});if(!p||p.error)throw new v(p?.error||"B站创作声明查询未返回结果")}await t.evaluate(`(() => { var id=${JSON.stringify(T)}; 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 M(t,`#${T}`,[n]);if(!_&&!y)return{status:"dry-run",title:r,bvid:"",url:`校验通过:登录态有效、视频已注入就绪、分区 ${a}、标签 [${o.join(", ")}]、版权 ${i===1?"自制":"转载"}${p?"、创作声明:"+p.label:""}。加 --execute 发布,或 --draft 存草稿。`};const d=await S(t,V,{inputId:T,line:m});if(!d||d.error)throw new v(`B站 preupload 失败:${d&&d.error||"无返回"}`);for(let c=0;c<d.total;c++){const l=await S(t,H,{inputId:T,url:d.url,auth:d.auth,uploadId:d.uploadId,idx:c,total:d.total,size:d.size,chunkSize:d.chunkSize});if(!l||!l.ok)throw new v(`B站分块上传失败(${c+1}/${d.total}):${l&&l.error||"无返回"}`)}const b=await S(t,G,{url:d.url,auth:d.auth,uploadId:d.uploadId,bizId:d.bizId,name:d.name,total:d.total});if(!b||b.error||!b.filename)throw new v(`B站 complete 失败:${b&&b.error||"未拿到 filename"}`);let f="";if(e.cover){const c=Z(String(e.cover)),l=await S(t,W,{dataUri:c});if(!l||l.error||!l.url)throw new v(`B站封面上传失败:${l&&l.error||"未拿到 url"}`);f=l.url}let u=null,I=null;if(e.topic!=null&&e.topic!==""){if(!Number.isFinite(Number(e.topic)))throw new w("--topic 必须是 topic_id 数字,用 `bilibili topics --tid <tid>` 取合法值");const c=await S(t,Q,{tid:a,topicId:Number(e.topic)});if(!c||c.error)throw new v(`B站话题解析失败:${c&&c.error||"无返回"}`);u=c.topicId;I=c.missionId}if(_){const c={videos:[{filename:b.filename,title:r,desc:"",cid:b.cid,is_4k:!1,is_8k:!1,is_hdr:!1}],cover:f,cover43:f,ai_cover:0,is_ab_cover:0,ab_cover_info:null,title:r,copyright:i,tid:a,tag:o.join(","),desc:String(e.desc??""),recreate:-1,dynamic:String(e.dynamic??""),is_only_self:0,space_hidden:2,watermark:{state:0},no_reprint:e["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(i===2)c.source=s;if(p)c.creation_statement_id=p.id;if(u!=null){c.topic_id=u;c.topic_detail={from_topic_id:u,from_source:"arc.web.search"};if(I!=null)c.mission_id=I}const l=await S(t,Y,{body:c});if(!l||l.error)throw new v(`B站 ${l&&l.error||"draft/add 无返回"}`);const R=l.data&&(l.data.aid!=null?l.data.aid:l.data.draft_id!=null?l.data.draft_id:l.data.id);return{status:"✅ 草稿已保存",title:r,bvid:R!=null?`draft:${R}`:"",url:"https://member.bilibili.com/platform/upload-manager/article?group=draft"}}const P=e.dtime!=null&&e.dtime!==""?Number(e.dtime):null,C={title:r,copyright:i,tid:a,tag:o.join(","),mission_id:I,topic_id:u,topic_detail:u!=null?{from_topic_id:u,from_source:"arc.web.recommend"}:null,desc_format_id:9999,desc:String(e.desc??""),dtime:P,recreate:-1,dynamic:String(e.dynamic??""),interactive:0,act_reserve_create:0,no_disturbance:0,porder:null,adorder_type:9,no_reprint:e["no-reprint"]?1:0,subtitle:{open:0,lan:""},creation_statement:p?{id:p.id}:null,dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1,web_os:1,source:i===2?s:null,watermark:{state:0},cover:f,videos:[{title:r,desc:"",filename:b.filename,cid:b.cid}]},E=await S(t,X,{meta:C});if(!E||E.error)throw new v(`B站 ${E&&E.error||"add/v3 无返回"}`);const x=E.bvid?String(E.bvid):"";return{status:e.dtime?"✅ 定时投稿已提交":"✅ 投稿成功",title:r,bvid:x,url:x?`https://www.bilibili.com/video/${x}`:"(已提交,稍后在创作中心查看)"}}});
@@ -0,0 +1,3 @@
1
+ import{readFileSync as S}from"node:fs";import{cli as T,Strategy as x}from"@jackwener/opencli/registry";import{ArgumentError as c,AuthRequiredError as E,CommandExecutionError as u}from"@jackwener/opencli/errors";const b='input[placeholder="请输入稿件标题"]',A='[editor_id="desc_at_editor"] .ql-editor',j='#tag-container input[placeholder="按回车键Enter创建标签"]',q="#tag-container .tag-pre-wrp .close";export function readEditForm(){const i=document.querySelector('input[placeholder="请输入稿件标题"]'),r=document.querySelector('[editor_id="desc_at_editor"] .ql-editor')?.parentElement.__quill,n=document.querySelectorAll("#tag-container");if(!i||!r||n.length!==1)return null;const e=r.getText();if(typeof e!=="string"||!e.endsWith(`
2
+ `))return null;return{title:i.value,desc:e.slice(0,-1),tags:[...n[0].querySelectorAll(".tag-pre-wrp .label-item-v2-container")].map((a)=>({name:a.querySelector(".label-item-v2-content")?.textContent,removable:Boolean(a.querySelector(".close"))}))}}export async function readArchive({bvid:i}){try{const r=await fetch("/x/vupre/web/archive/view?"+new URLSearchParams({bvid:i,topic_grey:"1"}),{credentials:"include"}),n=await r.json();if(r.status===401||n?.code===-101)return{auth:!0,error:"创作中心登录已失效"};if(!r.ok||n?.code!==0)return{error:`HTTP ${r.status} / ${n?.message??n?.code}`};const e=n.data?.archive,a=document.cookie.split(";").map((o)=>o.trim()).find((o)=>o.startsWith("DedeUserID="))?.slice(11);if(!a||!e||String(e.mid)!==a||e.bvid!==i)return{error:"稿件归属或 BV 号不匹配,停止编辑"};if(typeof e.title!=="string"||typeof e.desc!=="string"||typeof e.tag!=="string")return{error:"稿件缺少标题、简介或标签字段"};return{title:e.title,desc:e.desc,tags:e.tag.split(",").filter(Boolean),revision:JSON.stringify(e)}}catch(r){return{error:r instanceof Error?r.message:String(r)}}}const w=(i,r,n={})=>i.evaluateWithArgs(`(${r.toString()})(args)`,{args:n}),v=(i,r)=>i.length===r.length&&[...i].sort().every((n,e)=>n===[...r].sort()[e]),p=(i,r)=>i?.title===r.title&&i.desc===r.desc&&Array.isArray(i.tags)&&v(i.tags,r.tags);async function y(i,r){const n=await w(i,readArchive,{bvid:r});if(n?.auth)throw new E("member.bilibili.com",n.error);if(!n||n.error)throw new u(`读取稿件 ${r} 失败:${n?.error||"空响应"}`);return n}T({site:"bilibili",name:"video-edit",access:"write",description:"编辑自己的视频标题、简介和普通标签;未指定字段及活动话题保持原样。默认 dry-run,加 --execute 才保存并回查后台。AI 声明在官网编辑页不可修改。",domain:"member.bilibili.com",strategy:x.UI,browser:!0,args:[{name:"bvid",type:"string",required:!0,positional:!0,help:"已有视频的完整 BV 号"},{name:"title",type:"string",help:"新的完整标题,最多 80 字"},{name:"desc",type:"string",help:"替换简介(纯文本),可传空字符串清空"},{name:"desc-file",type:"string",help:"从本机 UTF-8 文件读取新的完整简介"},{name:"tag",type:"string",help:"替换全部普通标签,逗号分隔;活动标签保留,不要重复传入"},{name:"execute",type:"boolean",help:"保存修改;缺省仅读取并展示修改内容"},{name:"timeout",type:"int",default:120,help:"整段编辑流程的等待预算(秒);超时后先回查稿件,勿直接重提"}],columns:["bvid","status","field","before","after","url"],func:async(i,r)=>{const n=String(r.bvid||"");if(!/^BV[0-9A-Za-z]{10}$/.test(n))throw new c("请传入完整 BV 号");if(r.desc!==void 0&&r["desc-file"]!==void 0)throw new c("--desc 与 --desc-file 不能同时使用");const e={};if(r.title!==void 0){e.title=String(r.title);if(!e.title.trim()||e.title.length>80)throw new c("标题不能为空且不能超过 80 字")}if(r.desc!==void 0)e.desc=String(r.desc);if(r["desc-file"]!==void 0)try{e.desc=new TextDecoder("utf-8",{fatal:!0}).decode(S(String(r["desc-file"])))}catch(t){throw new c(`读取 UTF-8 简介文件失败:${t.message}`)}if(e.desc!==void 0){e.desc=e.desc.replace(/\r\n/g,`
3
+ `);if(e.desc.length>2000)throw new c("简介不能超过 2000 字")}if(r.tag!==void 0){e.tags=String(r.tag).split(",").map((t)=>t.trim());if(e.tags.some((t)=>!t||t.length>20)||new Set(e.tags).size!==e.tags.length)throw new c("标签不能为空、不能重复,每项最多 20 字")}if(!Object.keys(e).length)throw new c("至少指定 --title、--desc、--desc-file 或 --tag");if(!i)throw new u("稿件编辑需要浏览器会话");const a=`https://member.bilibili.com/platform/upload/video/frame?type=edit&bvid=${n}`;await i.goto(a);await i.wait({selector:b,timeout:20000});const o=await y(i,n),d=await w(i,readEditForm);if(!d||d.tags.some((t)=>typeof t.name!=="string")||!p({...d,tags:d.tags.map((t)=>t.name)},o))throw new u("编辑表单与后台稿件不一致或尚未加载完成,停止修改");const m=d.tags.filter((t)=>!t.removable).map((t)=>t.name);if(e.tags&&(e.tags.length+m.length>10||e.tags.some((t)=>m.includes(t))))throw new c(`普通标签与保留的活动标签合计不能超过 10 个,且不能重复活动标签:${m.join(",")}`);const s={...o,...e,tags:e.tags?[...m,...e.tags]:o.tags},f=Object.fromEntries(Object.keys(e).filter((t)=>t==="tags"?!v(o.tags,s.tags):o[t]!==s[t]).map((t)=>[t,{from:o[t],to:s[t]}])),g=(t)=>(Object.keys(f).length?Object.keys(f):Object.keys(e)).map((l)=>({bvid:n,status:t,field:l,before:l==="tags"?o.tags.join(","):o[l],after:l==="tags"?s.tags.join(","):s[l],url:a}));if(!Object.keys(f).length)return g("unchanged");if(!r.execute)return g("dry-run");if(f.title)await i.fillText(b,s.title);if(f.desc)await i.fillText(A,s.desc);if(f.tags){for(let t=0;t<d.tags.filter((l)=>l.removable).length;t++)await i.click(q,{nth:0});for(const t of e.tags){await i.fillText(j,t);await i.pressKey("Enter")}}const h=await w(i,readEditForm);if(!h||!p({...h,tags:h.tags.map((t)=>t.name)},s))throw new u("写入表单后的内容不匹配,尚未提交;请检查当前编辑页");if((await y(i,n)).revision!==o.revision)throw new u("后台稿件在编辑期间已变化,停止提交,请重新读取");try{await i.click(".submit-add");for(let t=0;t<15;t++){await i.wait({time:1});if(p(await y(i,n),s))return g("updated")}throw Error("后台尚未返回期望的修改内容")}catch(t){throw new u(`稿件 ${n} 已尝试提交,但保存结果未确认:${t.message}。请先回查该稿件,不要直接重复提交。`)}}});