publishport-opencli 1.0.26 → 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.
Files changed (40) hide show
  1. package/cli-manifest.json +308 -18
  2. package/clis/_shared/video-publish.js +2 -1
  3. package/clis/baijiahao/article.js +17 -1
  4. package/clis/bilibili/coin.js +1 -1
  5. package/clis/bilibili/danmaku.js +5 -0
  6. package/clis/bilibili/favorite-add.js +1 -1
  7. package/clis/bilibili/upload.js +1 -1
  8. package/clis/bilibili/video-edit.js +3 -0
  9. package/clis/bilibili/video.js +1 -1
  10. package/clis/jike/api.js +2 -2
  11. package/clis/jike/comments.js +1 -0
  12. package/clis/jike/delete.js +1 -0
  13. package/clis/jike/topic.js +1 -20
  14. package/clis/kuaishou/auth.js +8 -1
  15. package/clis/kuaishou/publish.js +6 -6
  16. package/clis/nowcoder/comment.js +1 -1
  17. package/clis/nowcoder/comments.js +1 -1
  18. package/clis/nowcoder/detail.js +4 -2
  19. package/clis/nowcoder/dm-list.js +1 -1
  20. package/clis/nowcoder/dm-read.js +1 -1
  21. package/clis/nowcoder/dm-send.js +1 -1
  22. package/clis/nowcoder/drafts.js +1 -1
  23. package/clis/nowcoder/experience.js +4 -2
  24. package/clis/nowcoder/inbox.js +1 -1
  25. package/clis/nowcoder/like.js +1 -1
  26. package/clis/nowcoder/my.js +1 -1
  27. package/clis/nowcoder/recommend.js +1 -1
  28. package/clis/nowcoder/search.js +3 -2
  29. package/clis/nowcoder/utils.js +14 -17
  30. package/clis/nowcoder/watch.js +1 -1
  31. package/clis/quora/create-answer.js +12 -8
  32. package/clis/reddit/comment.js +28 -7
  33. package/clis/reddit/upvote.js +21 -5
  34. package/clis/xiaohongshu/publish.js +1 -1
  35. package/clis/zhihu/comment.js +2 -2
  36. package/dist/src/cli.js +23 -23
  37. package/dist/src/commands/auth.js +3 -3
  38. package/dist/src/doctor.js +1 -1
  39. package/dist/src/pipeline/template.js +3 -1
  40. package/package.json +1 -1
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",
@@ -29530,7 +29789,7 @@
29530
29789
  {
29531
29790
  "site": "nowcoder",
29532
29791
  "name": "dm-read",
29533
- "description": "读某私信会话的消息(target 为 dm-list 的 target_user_id)",
29792
+ "description": "读某私信会话的消息(target 可填 dm-list 的 conversation_id 或 target_user_id)",
29534
29793
  "access": "read",
29535
29794
  "domain": "www.nowcoder.com",
29536
29795
  "strategy": "cookie",
@@ -29541,7 +29800,7 @@
29541
29800
  "type": "str",
29542
29801
  "required": true,
29543
29802
  "positional": true,
29544
- "help": "对方 userId(dm-list 的 target_user_id)"
29803
+ "help": "dm-list 的 conversation_id,或对方 userId"
29545
29804
  },
29546
29805
  {
29547
29806
  "name": "page",
@@ -29549,11 +29808,22 @@
29549
29808
  "default": 1,
29550
29809
  "required": false,
29551
29810
  "help": "页码"
29811
+ },
29812
+ {
29813
+ "name": "page-size",
29814
+ "type": "int",
29815
+ "default": 15,
29816
+ "required": false,
29817
+ "help": "每页条数"
29552
29818
  }
29553
29819
  ],
29554
29820
  "columns": [
29821
+ "message_id",
29822
+ "direction",
29555
29823
  "from_user_id",
29556
- "content"
29824
+ "type",
29825
+ "content",
29826
+ "created_at"
29557
29827
  ],
29558
29828
  "type": "js",
29559
29829
  "modulePath": "nowcoder/dm-read.js",
@@ -29563,18 +29833,18 @@
29563
29833
  {
29564
29834
  "site": "nowcoder",
29565
29835
  "name": "dm-send",
29566
- "description": "发私信(conversation 为 dm-list 的 conversation_id)",
29836
+ "description": "发私信(target 可填对方 userId 或 dm-list 的 conversation_id)",
29567
29837
  "access": "write",
29568
29838
  "domain": "www.nowcoder.com",
29569
29839
  "strategy": "cookie",
29570
29840
  "browser": true,
29571
29841
  "args": [
29572
29842
  {
29573
- "name": "conversation",
29843
+ "name": "target",
29574
29844
  "type": "str",
29575
29845
  "required": true,
29576
29846
  "positional": true,
29577
- "help": "会话 id(dm-list 的 conversation_id,格式 <我uid>_<对方uid>)"
29847
+ "help": "对方 userId(dm-list 的 target_user_id),也接受 conversation_id"
29578
29848
  },
29579
29849
  {
29580
29850
  "name": "content",
@@ -29594,7 +29864,8 @@
29594
29864
  }
29595
29865
  ],
29596
29866
  "columns": [
29597
- "conversation_id",
29867
+ "message_id",
29868
+ "receiver",
29598
29869
  "status"
29599
29870
  ],
29600
29871
  "type": "js",
@@ -29805,7 +30076,7 @@
29805
30076
  {
29806
30077
  "site": "nowcoder",
29807
30078
  "name": "my",
29808
- "description": "我发布的动态列表(含 momentId,可供 delete 使用)",
30079
+ "description": "我发布的内容列表(动态 + 长文,含可供 delete 使用的 id)",
29809
30080
  "access": "read",
29810
30081
  "domain": "www.nowcoder.com",
29811
30082
  "strategy": "cookie",
@@ -29820,9 +30091,15 @@
29820
30091
  }
29821
30092
  ],
29822
30093
  "columns": [
29823
- "moment_id",
30094
+ "content_id",
30095
+ "kind",
29824
30096
  "title",
29825
- "content"
30097
+ "content",
30098
+ "url",
30099
+ "views",
30100
+ "likes",
30101
+ "comments",
30102
+ "created_at"
29826
30103
  ],
29827
30104
  "type": "js",
29828
30105
  "modulePath": "nowcoder/my.js",
@@ -29999,7 +30276,11 @@
29999
30276
  "likes",
30000
30277
  "comments",
30001
30278
  "views",
30002
- "id"
30279
+ "content",
30280
+ "id",
30281
+ "momentId",
30282
+ "userId",
30283
+ "entityType"
30003
30284
  ],
30004
30285
  "type": "js",
30005
30286
  "modulePath": "nowcoder/recommend.js",
@@ -35078,7 +35359,9 @@
35078
35359
  ],
35079
35360
  "columns": [
35080
35361
  "status",
35081
- "message"
35362
+ "message",
35363
+ "comment_id",
35364
+ "url"
35082
35365
  ],
35083
35366
  "type": "js",
35084
35367
  "modulePath": "reddit/comment.js",
@@ -51171,6 +51454,13 @@
51171
51454
  "default": false,
51172
51455
  "required": false,
51173
51456
  "help": "保存为草稿,不直接发布"
51457
+ },
51458
+ {
51459
+ "name": "timeout",
51460
+ "type": "int",
51461
+ "default": 300,
51462
+ "required": false,
51463
+ "help": "整段发布流程的等待预算(秒);超时后先用 creator-notes 回查,勿直接重复发布"
51174
51464
  }
51175
51465
  ],
51176
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};