publishport-opencli 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/cli-manifest.json +442 -1
  2. package/clis/_shared/file-inject.js +41 -0
  3. package/clis/_shared/video-publish.js +4 -4
  4. package/clis/bilibili/upload.js +1 -1
  5. package/clis/chatgpt/utils.js +5 -5
  6. package/clis/claude/utils.js +1 -1
  7. package/clis/deepseek/utils.js +7 -7
  8. package/clis/douyin/draft.js +6 -6
  9. package/clis/douyin/publish-image.js +10 -31
  10. package/clis/facebook/feed.js +78 -5
  11. package/clis/facebook/post.js +9 -36
  12. package/clis/facebook/search.js +83 -22
  13. package/clis/instagram/post.js +19 -66
  14. package/clis/instagram/reel.js +11 -11
  15. package/clis/threads/post.js +15 -34
  16. package/clis/twitter/post.js +15 -42
  17. package/clis/twitter/utils.js +4 -36
  18. package/clis/wechat-channels/publish.js +29 -57
  19. package/clis/weibo/publish.js +8 -8
  20. package/clis/weixin/create-draft.js +7 -7
  21. package/clis/xianyu/collect.js +1 -0
  22. package/clis/xianyu/location.js +1 -0
  23. package/clis/xianyu/manage.js +1 -0
  24. package/clis/xianyu/mtop.js +47 -0
  25. package/clis/xianyu/order.js +1 -0
  26. package/clis/xianyu/publish.js +2 -2
  27. package/clis/xianyu/rate.js +1 -0
  28. package/clis/xianyu/user.js +1 -0
  29. package/clis/xiaohongshu/publish-video.js +9 -27
  30. package/clis/xiaohongshu/publish.js +32 -72
  31. package/dist/src/browser/base-page.d.ts +19 -0
  32. package/dist/src/browser/base-page.js +1 -1
  33. package/dist/src/browser/bridge-readiness.d.ts +1 -1
  34. package/dist/src/browser/daemon-client.d.ts +72 -1
  35. package/dist/src/browser/daemon-client.js +1 -1
  36. package/dist/src/browser/daemon-version.d.ts +1 -1
  37. package/dist/src/browser/errors.d.ts +2 -0
  38. package/dist/src/browser/errors.js +3 -3
  39. package/dist/src/browser/network-interceptor.d.ts +11 -0
  40. package/dist/src/browser/network-interceptor.js +1 -0
  41. package/dist/src/browser/target-resolver.d.ts +1 -0
  42. package/dist/src/browser/target-resolver.js +100 -6
  43. package/dist/src/cli.js +24 -24
  44. package/dist/src/commands/daemon.js +1 -1
  45. package/dist/src/daemon-utils.d.ts +1 -0
  46. package/dist/src/daemon-utils.js +1 -1
  47. package/dist/src/daemon.js +1 -1
  48. package/dist/src/doctor.d.ts +1 -1
  49. package/dist/src/doctor.js +7 -7
  50. package/dist/src/errors.d.ts +8 -0
  51. package/dist/src/errors.js +1 -1
  52. package/dist/src/execution.js +2 -2
  53. package/dist/src/external.js +1 -1
  54. package/dist/src/session-lease.d.ts +135 -0
  55. package/dist/src/session-lease.js +1 -0
  56. package/dist/src/session-lease.test.d.ts +1 -0
  57. package/dist/src/types.d.ts +3 -0
  58. package/package.json +1 -1
package/cli-manifest.json CHANGED
@@ -15215,7 +15215,7 @@
15215
15215
  "type": "js",
15216
15216
  "modulePath": "facebook/search.js",
15217
15217
  "sourceFile": "facebook/search.js",
15218
- "navigateBefore": "https://www.facebook.com"
15218
+ "navigateBefore": false
15219
15219
  },
15220
15220
  {
15221
15221
  "site": "facebook",
@@ -47183,6 +47183,125 @@
47183
47183
  "sourceFile": "xianyu/chat.js",
47184
47184
  "navigateBefore": false
47185
47185
  },
47186
+ {
47187
+ "site": "xianyu",
47188
+ "name": "collect",
47189
+ "description": "列出我收藏的商品(分页)",
47190
+ "access": "read",
47191
+ "domain": "www.goofish.com",
47192
+ "strategy": "cookie",
47193
+ "browser": true,
47194
+ "args": [
47195
+ {
47196
+ "name": "page",
47197
+ "type": "int",
47198
+ "default": 1,
47199
+ "required": false,
47200
+ "help": "页码,从 1 开始"
47201
+ },
47202
+ {
47203
+ "name": "page_size",
47204
+ "type": "int",
47205
+ "default": 20,
47206
+ "required": false,
47207
+ "help": "每页条数"
47208
+ }
47209
+ ],
47210
+ "columns": [
47211
+ "item_id",
47212
+ "title",
47213
+ "price",
47214
+ "seller_nick",
47215
+ "want_cnt",
47216
+ "status_str"
47217
+ ],
47218
+ "type": "js",
47219
+ "modulePath": "xianyu/collect.js",
47220
+ "sourceFile": "xianyu/collect.js",
47221
+ "navigateBefore": false
47222
+ },
47223
+ {
47224
+ "site": "xianyu",
47225
+ "name": "delete",
47226
+ "description": "下架/删除商品",
47227
+ "access": "write",
47228
+ "domain": "www.goofish.com",
47229
+ "strategy": "cookie",
47230
+ "browser": true,
47231
+ "args": [
47232
+ {
47233
+ "name": "item_id",
47234
+ "type": "str",
47235
+ "required": true,
47236
+ "positional": true,
47237
+ "help": "商品 itemId"
47238
+ }
47239
+ ],
47240
+ "columns": [
47241
+ "item_id",
47242
+ "ok"
47243
+ ],
47244
+ "type": "js",
47245
+ "modulePath": "xianyu/manage.js",
47246
+ "sourceFile": "xianyu/manage.js",
47247
+ "navigateBefore": false
47248
+ },
47249
+ {
47250
+ "site": "xianyu",
47251
+ "name": "edit",
47252
+ "description": "编辑商品(标题/详情/价格/划线价/库存,按需传)",
47253
+ "access": "write",
47254
+ "domain": "www.goofish.com",
47255
+ "strategy": "cookie",
47256
+ "browser": true,
47257
+ "args": [
47258
+ {
47259
+ "name": "item_id",
47260
+ "type": "str",
47261
+ "required": true,
47262
+ "positional": true,
47263
+ "help": "商品 itemId"
47264
+ },
47265
+ {
47266
+ "name": "title",
47267
+ "type": "str",
47268
+ "required": false,
47269
+ "help": "新标题"
47270
+ },
47271
+ {
47272
+ "name": "desc",
47273
+ "type": "str",
47274
+ "required": false,
47275
+ "help": "新详情描述"
47276
+ },
47277
+ {
47278
+ "name": "price",
47279
+ "type": "float",
47280
+ "required": false,
47281
+ "help": "新价格(元)"
47282
+ },
47283
+ {
47284
+ "name": "original_price",
47285
+ "type": "float",
47286
+ "required": false,
47287
+ "help": "新划线价(元),传 0 清除"
47288
+ },
47289
+ {
47290
+ "name": "quantity",
47291
+ "type": "int",
47292
+ "required": false,
47293
+ "help": "新库存数量"
47294
+ }
47295
+ ],
47296
+ "columns": [
47297
+ "item_id",
47298
+ "ok"
47299
+ ],
47300
+ "type": "js",
47301
+ "modulePath": "xianyu/manage.js",
47302
+ "sourceFile": "xianyu/manage.js",
47303
+ "navigateBefore": false
47304
+ },
47186
47305
  {
47187
47306
  "site": "xianyu",
47188
47307
  "name": "inbox",
@@ -47263,6 +47382,42 @@
47263
47382
  "sourceFile": "xianyu/item.js",
47264
47383
  "navigateBefore": false
47265
47384
  },
47385
+ {
47386
+ "site": "xianyu",
47387
+ "name": "location",
47388
+ "description": "获取账号默认发布地址",
47389
+ "access": "read",
47390
+ "domain": "www.goofish.com",
47391
+ "strategy": "cookie",
47392
+ "browser": true,
47393
+ "args": [
47394
+ {
47395
+ "name": "longitude",
47396
+ "type": "float",
47397
+ "default": 121.4737,
47398
+ "required": false,
47399
+ "help": "经度(默认上海)"
47400
+ },
47401
+ {
47402
+ "name": "latitude",
47403
+ "type": "float",
47404
+ "default": 31.2304,
47405
+ "required": false,
47406
+ "help": "纬度(默认上海)"
47407
+ }
47408
+ ],
47409
+ "columns": [
47410
+ "prov",
47411
+ "city",
47412
+ "area",
47413
+ "poi",
47414
+ "division_id"
47415
+ ],
47416
+ "type": "js",
47417
+ "modulePath": "xianyu/location.js",
47418
+ "sourceFile": "xianyu/location.js",
47419
+ "navigateBefore": false
47420
+ },
47266
47421
  {
47267
47422
  "site": "xianyu",
47268
47423
  "name": "login",
@@ -47346,6 +47501,220 @@
47346
47501
  "sourceFile": "xianyu/messages.js",
47347
47502
  "navigateBefore": false
47348
47503
  },
47504
+ {
47505
+ "site": "xianyu",
47506
+ "name": "mine",
47507
+ "description": "列出当前账号在售/已发布商品(分页)",
47508
+ "access": "read",
47509
+ "domain": "www.goofish.com",
47510
+ "strategy": "cookie",
47511
+ "browser": true,
47512
+ "args": [
47513
+ {
47514
+ "name": "page",
47515
+ "type": "int",
47516
+ "default": 1,
47517
+ "required": false,
47518
+ "help": "页码,从 1 开始"
47519
+ },
47520
+ {
47521
+ "name": "page_size",
47522
+ "type": "int",
47523
+ "default": 20,
47524
+ "required": false,
47525
+ "help": "每页条数"
47526
+ }
47527
+ ],
47528
+ "columns": [
47529
+ "item_id",
47530
+ "title",
47531
+ "price",
47532
+ "status",
47533
+ "main_pic"
47534
+ ],
47535
+ "type": "js",
47536
+ "modulePath": "xianyu/manage.js",
47537
+ "sourceFile": "xianyu/manage.js",
47538
+ "navigateBefore": false
47539
+ },
47540
+ {
47541
+ "site": "xianyu",
47542
+ "name": "order",
47543
+ "description": "列出卖家已售订单(可按状态筛选)",
47544
+ "access": "read",
47545
+ "domain": "www.goofish.com",
47546
+ "strategy": "cookie",
47547
+ "browser": true,
47548
+ "args": [
47549
+ {
47550
+ "name": "page",
47551
+ "type": "int",
47552
+ "default": 1,
47553
+ "required": false,
47554
+ "help": "页码,从 1 开始"
47555
+ },
47556
+ {
47557
+ "name": "page_size",
47558
+ "type": "int",
47559
+ "default": 20,
47560
+ "required": false,
47561
+ "help": "每页条数"
47562
+ },
47563
+ {
47564
+ "name": "status",
47565
+ "type": "str",
47566
+ "default": "ALL",
47567
+ "required": false,
47568
+ "help": "订单状态筛选:ALL / WAITING_PAY / WAITING_SEND / WAITING_RECEIVE / WAITING_RATE / SUCCESS / REFUND"
47569
+ }
47570
+ ],
47571
+ "columns": [
47572
+ "order_id",
47573
+ "item_id",
47574
+ "buyer_nick",
47575
+ "status_str",
47576
+ "total_price",
47577
+ "create_time"
47578
+ ],
47579
+ "type": "js",
47580
+ "modulePath": "xianyu/order.js",
47581
+ "sourceFile": "xianyu/order.js",
47582
+ "navigateBefore": false
47583
+ },
47584
+ {
47585
+ "site": "xianyu",
47586
+ "name": "order-get",
47587
+ "description": "查询单个闲鱼订单详情",
47588
+ "access": "read",
47589
+ "domain": "www.goofish.com",
47590
+ "strategy": "cookie",
47591
+ "browser": true,
47592
+ "args": [
47593
+ {
47594
+ "name": "order_id",
47595
+ "type": "str",
47596
+ "required": true,
47597
+ "positional": true,
47598
+ "help": "订单号 orderId"
47599
+ },
47600
+ {
47601
+ "name": "role",
47602
+ "type": "str",
47603
+ "default": "seller",
47604
+ "required": false,
47605
+ "help": "视角:seller / buyer"
47606
+ }
47607
+ ],
47608
+ "columns": [
47609
+ "order_id",
47610
+ "item_id",
47611
+ "status_str",
47612
+ "total_price",
47613
+ "buyer_nick"
47614
+ ],
47615
+ "type": "js",
47616
+ "modulePath": "xianyu/order.js",
47617
+ "sourceFile": "xianyu/order.js",
47618
+ "navigateBefore": false
47619
+ },
47620
+ {
47621
+ "site": "xianyu",
47622
+ "name": "order-ship-dummy",
47623
+ "description": "虚拟发货(仅虚拟商品,无需物流单号)",
47624
+ "access": "write",
47625
+ "domain": "www.goofish.com",
47626
+ "strategy": "cookie",
47627
+ "browser": true,
47628
+ "args": [
47629
+ {
47630
+ "name": "order_id",
47631
+ "type": "str",
47632
+ "required": true,
47633
+ "positional": true,
47634
+ "help": "订单号 orderId"
47635
+ },
47636
+ {
47637
+ "name": "text",
47638
+ "type": "str",
47639
+ "default": "",
47640
+ "required": false,
47641
+ "help": "发货附言(可选)"
47642
+ }
47643
+ ],
47644
+ "columns": [
47645
+ "order_id",
47646
+ "ok"
47647
+ ],
47648
+ "type": "js",
47649
+ "modulePath": "xianyu/order.js",
47650
+ "sourceFile": "xianyu/order.js",
47651
+ "navigateBefore": false
47652
+ },
47653
+ {
47654
+ "site": "xianyu",
47655
+ "name": "order-ship-freeshipping",
47656
+ "description": "包邮发货(拼团活动专用)",
47657
+ "access": "write",
47658
+ "domain": "www.goofish.com",
47659
+ "strategy": "cookie",
47660
+ "browser": true,
47661
+ "args": [
47662
+ {
47663
+ "name": "order_id",
47664
+ "type": "str",
47665
+ "required": true,
47666
+ "positional": true,
47667
+ "help": "订单号 bizOrderId"
47668
+ },
47669
+ {
47670
+ "name": "item_id",
47671
+ "type": "str",
47672
+ "required": true,
47673
+ "help": "商品 itemId"
47674
+ },
47675
+ {
47676
+ "name": "buyer_id",
47677
+ "type": "str",
47678
+ "required": true,
47679
+ "help": "买家 userId"
47680
+ }
47681
+ ],
47682
+ "columns": [
47683
+ "order_id",
47684
+ "ok"
47685
+ ],
47686
+ "type": "js",
47687
+ "modulePath": "xianyu/order.js",
47688
+ "sourceFile": "xianyu/order.js",
47689
+ "navigateBefore": false
47690
+ },
47691
+ {
47692
+ "site": "xianyu",
47693
+ "name": "polish",
47694
+ "description": "擦亮商品(刷新曝光排序,不改内容)",
47695
+ "access": "write",
47696
+ "domain": "www.goofish.com",
47697
+ "strategy": "cookie",
47698
+ "browser": true,
47699
+ "args": [
47700
+ {
47701
+ "name": "item_id",
47702
+ "type": "str",
47703
+ "required": true,
47704
+ "positional": true,
47705
+ "help": "商品 itemId"
47706
+ }
47707
+ ],
47708
+ "columns": [
47709
+ "item_id",
47710
+ "ok",
47711
+ "already_polished"
47712
+ ],
47713
+ "type": "js",
47714
+ "modulePath": "xianyu/manage.js",
47715
+ "sourceFile": "xianyu/manage.js",
47716
+ "navigateBefore": false
47717
+ },
47349
47718
  {
47350
47719
  "site": "xianyu",
47351
47720
  "name": "publish",
@@ -47423,6 +47792,48 @@
47423
47792
  "sourceFile": "xianyu/publish.js",
47424
47793
  "navigateBefore": false
47425
47794
  },
47795
+ {
47796
+ "site": "xianyu",
47797
+ "name": "rate",
47798
+ "description": "拉某用户收到的信用评价列表",
47799
+ "access": "read",
47800
+ "domain": "www.goofish.com",
47801
+ "strategy": "cookie",
47802
+ "browser": true,
47803
+ "args": [
47804
+ {
47805
+ "name": "user_id",
47806
+ "type": "str",
47807
+ "required": true,
47808
+ "positional": true,
47809
+ "help": "被评价用户 userId(明文数字 uid)"
47810
+ },
47811
+ {
47812
+ "name": "page",
47813
+ "type": "int",
47814
+ "default": 1,
47815
+ "required": false,
47816
+ "help": "页码,从 1 开始"
47817
+ },
47818
+ {
47819
+ "name": "page_size",
47820
+ "type": "int",
47821
+ "default": 20,
47822
+ "required": false,
47823
+ "help": "每页条数"
47824
+ }
47825
+ ],
47826
+ "columns": [
47827
+ "rate_id",
47828
+ "rater_nick",
47829
+ "rate_content",
47830
+ "create_time"
47831
+ ],
47832
+ "type": "js",
47833
+ "modulePath": "xianyu/rate.js",
47834
+ "sourceFile": "xianyu/rate.js",
47835
+ "navigateBefore": false
47836
+ },
47426
47837
  {
47427
47838
  "site": "xianyu",
47428
47839
  "name": "reply",
@@ -47538,6 +47949,36 @@
47538
47949
  "sourceFile": "xianyu/search.js",
47539
47950
  "navigateBefore": false
47540
47951
  },
47952
+ {
47953
+ "site": "xianyu",
47954
+ "name": "user",
47955
+ "description": "查询闲鱼用户主页信息(不传 user_id 看自己)",
47956
+ "access": "read",
47957
+ "domain": "www.goofish.com",
47958
+ "strategy": "cookie",
47959
+ "browser": true,
47960
+ "args": [
47961
+ {
47962
+ "name": "user_id",
47963
+ "type": "str",
47964
+ "required": false,
47965
+ "positional": true,
47966
+ "help": "目标用户 userId;留空看自己"
47967
+ }
47968
+ ],
47969
+ "columns": [
47970
+ "user_id",
47971
+ "nick",
47972
+ "city",
47973
+ "credit_level",
47974
+ "fans_count",
47975
+ "follow_count"
47976
+ ],
47977
+ "type": "js",
47978
+ "modulePath": "xianyu/user.js",
47979
+ "sourceFile": "xianyu/user.js",
47980
+ "navigateBefore": false
47981
+ },
47541
47982
  {
47542
47983
  "site": "xianyu",
47543
47984
  "name": "whoami",
@@ -0,0 +1,41 @@
1
+ import*as X from"node:fs";import*as w from"node:path";import{CommandExecutionError as A}from"@jackwener/opencli/errors";const I=6291456,K={".mp4":"video/mp4",".mov":"video/quicktime",".avi":"video/x-msvideo",".mkv":"video/x-matroska",".m4v":"video/x-m4v",".webm":"video/webm",".flv":"video/x-flv",".wmv":"video/x-ms-wmv",".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp",".bmp":"image/bmp",".gif":"image/gif"};export function mimeForFile(q){return K[w.extname(q).toLowerCase()]??"application/octet-stream"}export function isFileInputCdpUnsupported(q){const O=q instanceof Error?q.message:String(q);return O.includes("Unknown action")||O.includes("not supported")||O.includes("set-file-input")}export function isFileAccessDenied(q){const O=q instanceof Error?q.message:String(q);return O.includes("Not allowed")||O.includes("File access not granted")}export function isStaleNodeError(q){const O=q instanceof Error?q.message:String(q);return O.includes("No element found matching selector")||O.includes("Could not find node with given id")||O.includes("No node with given id found")}function T(q){if(q&&typeof q==="object"&&"data"in q&&"session"in q)return q.data;return q}export async function injectInputFiles(q,O,z,{chunkBytes:j=I,onProgress:V,findInputJs:W}={}){const Z=`__ppInj_${Math.random().toString(36).slice(2,10)}`,G=JSON.stringify(Z),J=JSON.stringify(O),D=W??"((sel) => document.querySelector(sel))",L=T(await q.evaluate(`/*pp-file-inject:probe*/(() => ({ ok: !!(${D})(${J}) }))()`));if(!L||L.ok!==!0)throw new A(`未找到文件上传输入框: ${O}`);const R=z.reduce((Q,$)=>Q+X.statSync($).size,0);let U=0;try{for(const $ of z){const x=X.statSync($).size,_=X.openSync($,"r");let H=0;try{const N=Buffer.allocUnsafe(j);while(H<x){const M=X.readSync(_,N,0,j,H);if(M<=0)throw new A(`读取文件失败(提前 EOF): ${$}`);const F=JSON.stringify(N.subarray(0,M).toString("base64"));H+=M;U+=M;const Y=T(await q.evaluate(`
2
+ /*pp-file-inject:chunk*/(() => {
3
+ const S = window[${G}] ?? (window[${G}] = { parts: [], bytes: 0, files: [] });
4
+ const raw = atob(${F});
5
+ const len = raw.length;
6
+ const bytes = new Uint8Array(len);
7
+ for (let i = 0; i < len; i++) bytes[i] = raw.charCodeAt(i);
8
+ S.parts.push(new Blob([bytes]));
9
+ S.bytes += len;
10
+ return { ok: S.bytes === ${H}, bytes: S.bytes };
11
+ })()
12
+ `));if(!Y||Y.ok!==!0)throw new A(`文件注入中断:页面累计 ${Y?.bytes??"未知"} 字节 ≠ 已发送 ${H} 字节(标签页可能被刷新/导航走了)`,"保持发布页面前台稳定后重试本命令");if(V)V(U,R)}}finally{X.closeSync(_)}const v=T(await q.evaluate(`
13
+ /*pp-file-inject:seal*/(() => {
14
+ const S = window[${G}];
15
+ if (!S) return { ok: false };
16
+ S.files.push(new File(S.parts, ${JSON.stringify(w.basename($))}, { type: ${JSON.stringify(mimeForFile($))} }));
17
+ S.parts = [];
18
+ S.bytes = 0;
19
+ return { ok: true, files: S.files.length };
20
+ })()
21
+ `));if(!v||v.ok!==!0)throw new A("文件注入中断:页面注入状态丢失(标签页可能被刷新/导航走了)","保持发布页面前台稳定后重试本命令")}const Q=T(await q.evaluate(`
22
+ /*pp-file-inject:commit*/(() => {
23
+ const S = window[${G}];
24
+ delete window[${G}];
25
+ if (!S || !S.files.length) return { ok: false, error: '页面注入状态丢失(标签页可能被刷新/导航走了)' };
26
+ const input = (${D})(${J});
27
+ if (!input) return { ok: false, error: '提交时文件输入框已不在页面上: ' + ${J} };
28
+ const dt = new DataTransfer();
29
+ for (const f of S.files) dt.items.add(f);
30
+ try {
31
+ input.files = dt.files;
32
+ } catch {
33
+ // 个别站点用 Proxy 包过 input,直接赋值可能抛 —— defineProperty 兜底。
34
+ // configurable:true 让同一个 input 能被反复注入(重试/追加时 SPA 常复用同一元素)。
35
+ Object.defineProperty(input, 'files', { value: dt.files, writable: false, configurable: true });
36
+ }
37
+ input.dispatchEvent(new Event('input', { bubbles: true }));
38
+ input.dispatchEvent(new Event('change', { bubbles: true }));
39
+ return { ok: true, count: input.files.length };
40
+ })()
41
+ `));if(!Q||Q.ok!==!0)throw new A(`文件注入提交失败: ${Q?.error??"页面返回异常"}`,"保持发布页面前台稳定后重试本命令");return{count:Number(Q.count)||z.length,bytes:U}}catch(Q){await q.evaluate(`/*pp-file-inject:cleanup*/(() => { delete window[${G}]; })()`).catch(()=>{});throw Q}}export async function setInputFiles(q,O,z,j={}){const V=Array.isArray(O)?O:[O];let W;if(q.setFileInput)for(const Z of V)try{await q.setFileInput(z,Z);return{selector:Z,method:"cdp"}}catch(G){W=G;if(isFileAccessDenied(G)||isFileInputCdpUnsupported(G)){console.warn(`[file-inject] CDP 直读被拒(${G instanceof Error?G.message:G}),切无权限分块注入通道`);break}if(V.length===1&&isStaleNodeError(G))throw G}for(const Z of V)try{const G=await injectInputFiles(q,Z,z,j);return{selector:Z,method:"inject",...G}}catch(G){if(!(G instanceof Error?G.message:String(G)).includes("未找到文件上传输入框"))throw G;W=G}throw new A(`未找到可用的文件上传输入框(尝试过: ${V.join(" , ")}):${W instanceof Error?W.message:String(W)}`)}
@@ -1,4 +1,4 @@
1
- import*as Z from"node:fs";import*as L from"node:path";import{ArgumentError as J,CommandExecutionError as Y}from"@jackwener/opencli/errors";export const SUPPORTED_VIDEO_EXTENSIONS=new Set([".mp4",".mov",".avi",".mkv",".m4v",".webm",".flv",".wmv"]),SUPPORTED_IMAGE_EXTENSIONS=new Set([".jpg",".jpeg",".png",".webp",".bmp"]);export function parseScheduleDate(q){if(q==null||q==="")return null;const y=String(q),z=/^\d+$/.test(y)?new Date(Number(y)<1000000000000?Number(y)*1000:Number(y)):new Date(y);if(Number.isNaN(z.getTime()))throw new J(`无法解析定时时间: ${q}`);if(z.getTime()<=Date.now())throw new J("定时发布时间必须晚于当前时间");return z}export function unwrap(q){if(q&&typeof q==="object"&&"data"in q&&"session"in q)return q.data;return q}export async function evalPage(q,y){return unwrap(await q.evaluate(y))}export function sleep(q){return new Promise((y)=>setTimeout(y,q*1000))}export function resolveVideoFile(q){const y=L.resolve(String(q));let z;try{z=Z.statSync(y)}catch{throw new J(`视频文件不存在: ${y}`)}if(!z.isFile())throw new J(`视频路径不是文件: ${y}`);const B=L.extname(y).toLowerCase();if(!SUPPORTED_VIDEO_EXTENSIONS.has(B))throw new J(`不支持的视频格式: ${B}(支持 ${[...SUPPORTED_VIDEO_EXTENSIONS].join("/")})`);return y}export function resolveImageFile(q){const y=L.resolve(String(q));let z;try{z=Z.statSync(y)}catch{throw new J(`图片文件不存在: ${y}`)}if(!z.isFile())throw new J(`图片路径不是文件: ${y}`);const B=L.extname(y).toLowerCase();if(!SUPPORTED_IMAGE_EXTENSIONS.has(B))throw new J(`不支持的图片格式: ${B}(支持 ${[...SUPPORTED_IMAGE_EXTENSIONS].join("/")})`);return y}export function throwIfFileAccessDenied(q){if(!(q instanceof Error?q.message:String(q)).includes("Not allowed"))return;throw new Y("Chrome 拦截了扩展读取本地文件(DOM.setFileInputFiles → Not allowed):商店安装的扩展默认没有本地文件访问权限","请让用户在 Chrome 打开 chrome://extensions,找到「ppcli Browser Bridge」→「详情」→ 打开「允许访问文件网址」(Allow access to file URLs),开关即时生效,然后重试本命令")}export async function setVideoInput(q,y,z){if(!q.setFileInput)throw new Y("浏览器扩展不支持 CDP 文件上传(set-file-input),无法上传视频;请升级 PublishPort 客户端/扩展");let B;for(const H of y)try{await q.setFileInput(z,H);return H}catch(K){throwIfFileAccessDenied(K);B=K}throw new Y(`未找到可用的文件上传输入框(尝试过: ${y.join(" , ")}):${B instanceof Error?B.message:String(B)}`)}export async function currentUrl(q){try{if(q.getCurrentUrl){const y=await q.getCurrentUrl();if(y)return String(y)}}catch{}try{return String(await evalPage(q,"() => location.href")||"")}catch{return""}}export async function waitForUrlIncludes(q,y,z=60000,B=500){const H=Array.isArray(y)?y:[y],K=Date.now()+z;while(Date.now()<K){const Q=await currentUrl(q);if(Q&&H.some(($)=>Q.includes($)))return!0;await q.wait({time:B/1000})}return!1}export async function countByText(q,y){return Number(unwrap(await q.evaluate(`
1
+ import*as Y from"node:fs";import*as L from"node:path";import{ArgumentError as H,CommandExecutionError as $}from"@jackwener/opencli/errors";import{isFileAccessDenied as k,setInputFiles as C}from"./file-inject.js";export const SUPPORTED_VIDEO_EXTENSIONS=new Set([".mp4",".mov",".avi",".mkv",".m4v",".webm",".flv",".wmv"]),SUPPORTED_IMAGE_EXTENSIONS=new Set([".jpg",".jpeg",".png",".webp",".bmp"]);export function parseScheduleDate(q){if(q==null||q==="")return null;const y=String(q),z=/^\d+$/.test(y)?new Date(Number(y)<1000000000000?Number(y)*1000:Number(y)):new Date(y);if(Number.isNaN(z.getTime()))throw new H(`无法解析定时时间: ${q}`);if(z.getTime()<=Date.now())throw new H("定时发布时间必须晚于当前时间");return z}export function unwrap(q){if(q&&typeof q==="object"&&"data"in q&&"session"in q)return q.data;return q}export async function evalPage(q,y){return unwrap(await q.evaluate(y))}export function sleep(q){return new Promise((y)=>setTimeout(y,q*1000))}export function resolveVideoFile(q){const y=L.resolve(String(q));let z;try{z=Y.statSync(y)}catch{throw new H(`视频文件不存在: ${y}`)}if(!z.isFile())throw new H(`视频路径不是文件: ${y}`);const B=L.extname(y).toLowerCase();if(!SUPPORTED_VIDEO_EXTENSIONS.has(B))throw new H(`不支持的视频格式: ${B}(支持 ${[...SUPPORTED_VIDEO_EXTENSIONS].join("/")})`);return y}export function resolveImageFile(q){const y=L.resolve(String(q));let z;try{z=Y.statSync(y)}catch{throw new H(`图片文件不存在: ${y}`)}if(!z.isFile())throw new H(`图片路径不是文件: ${y}`);const B=L.extname(y).toLowerCase();if(!SUPPORTED_IMAGE_EXTENSIONS.has(B))throw new H(`不支持的图片格式: ${B}(支持 ${[...SUPPORTED_IMAGE_EXTENSIONS].join("/")})`);return y}export function throwIfFileAccessDenied(q){if(!k(q))return;throw new $("Chrome 拦截了扩展读取本地文件(DOM.setFileInputFiles → File access not granted):商店安装的扩展默认没有本地文件访问权限","请让用户在 Chrome 打开 chrome://extensions,找到「ppcli Browser Bridge」→「详情」→ 打开「允许访问文件网址」(Allow access to file URLs),开关即时生效,然后重试本命令")}export async function setVideoInput(q,y,z){return(await C(q,y,z)).selector}export async function currentUrl(q){try{if(q.getCurrentUrl){const y=await q.getCurrentUrl();if(y)return String(y)}}catch{}try{return String(await evalPage(q,"() => location.href")||"")}catch{return""}}export async function waitForUrlIncludes(q,y,z=60000,B=500){const J=Array.isArray(y)?y:[y],K=Date.now()+z;while(Date.now()<K){const Q=await currentUrl(q);if(Q&&J.some((Z)=>Q.includes(Z)))return!0;await q.wait({time:B/1000})}return!1}export async function countByText(q,y){return Number(unwrap(await q.evaluate(`
2
2
  ((want) => {
3
3
  let n = 0;
4
4
  const nodes = document.querySelectorAll('body *');
@@ -12,7 +12,7 @@ import*as Z from"node:fs";import*as L from"node:path";import{ArgumentError as J,
12
12
  }
13
13
  return n;
14
14
  })(${JSON.stringify(y)})
15
- `)))||0}export async function clickByText(q,y,{exact:z=!1,timeoutMs:B=0,pollMs:H=500}={}){const K=Date.now()+Math.max(0,B);for(;;){if(Boolean(unwrap(await q.evaluate(`
15
+ `)))||0}export async function clickByText(q,y,{exact:z=!1,timeoutMs:B=0,pollMs:J=500}={}){const K=Date.now()+Math.max(0,B);for(;;){if(Boolean(unwrap(await q.evaluate(`
16
16
  ((want, exact) => {
17
17
  const norm = (v) => (v || '').replace(/\\s+/g, ' ').trim();
18
18
  const isVisible = (el) => {
@@ -37,7 +37,7 @@ import*as Z from"node:fs";import*as L from"node:path";import{ArgumentError as J,
37
37
  }
38
38
  return false;
39
39
  })(${JSON.stringify(y)}, ${z?"true":"false"})
40
- `))))return!0;if(Date.now()>=K)return!1;await q.wait({time:H/1000})}}export async function existsByText(q,y,{exact:z=!1}={}){return Boolean(unwrap(await q.evaluate(`
40
+ `))))return!0;if(Date.now()>=K)return!1;await q.wait({time:J/1000})}}export async function existsByText(q,y,{exact:z=!1}={}){return Boolean(unwrap(await q.evaluate(`
41
41
  ((want, exact) => {
42
42
  const norm = (v) => (v || '').replace(/\\s+/g, ' ').trim();
43
43
  const isVisible = (el) => {
@@ -52,7 +52,7 @@ import*as Z from"node:fs";import*as L from"node:path";import{ArgumentError as J,
52
52
  }
53
53
  return false;
54
54
  })(${JSON.stringify(y)}, ${z?"true":"false"})
55
- `)))}export async function waitForSelector(q,y,z=30000,B=500){const H=Date.now()+z;while(Date.now()<H){if(Boolean(unwrap(await q.evaluate(`
55
+ `)))}export async function waitForSelector(q,y,z=30000,B=500){const J=Date.now()+z;while(Date.now()<J){if(Boolean(unwrap(await q.evaluate(`
56
56
  ((sel) => {
57
57
  const el = document.querySelector(sel);
58
58
  if (!el) return false;
@@ -1 +1 @@
1
- import*as I from"node:fs";import*as P from"node:path";import{cli as E,Strategy as b}from"@jackwener/opencli/registry";import{ArgumentError as D,AuthRequiredError as h,CommandExecutionError as S}from"@jackwener/opencli/errors";import{resolveVideoFile as l,resolveImageFile as u,throwIfFileAccessDenied as w}from"../_shared/video-publish.js";const U={bda2:{os:"upos",upcdn:"bda2",probe_version:20221109},bldsa:{os:"upos",upcdn:"bldsa",probe_version:20221109},qn:{os:"upos",upcdn:"qn",probe_version:20221109},ws:{os:"upos",upcdn:"ws",probe_version:20221109}},f="bda2",k=["SESSDATA","bili_jct","DedeUserID"],m="https://member.bilibili.com/platform/home",_="__pp_bili_video_input";function n(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{error:"文件未就绪(input 为空)"};var Z=J.files[0],Y=Z.size,$=W.line;return async function(){try{var G=new URLSearchParams({profile:"ugcfx/bup",name:Z.name,size:String(Y),r:$.os,ssl:"0",version:"2.14.0",build:"2100400",upcdn:$.upcdn,probe_version:String($.probe_version)}),X=await(await fetch("https://member.bilibili.com/preupload?"+G.toString(),{credentials:"include",headers:{Referer:"https://www.bilibili.com"}})).json();if(X.OK!==1)return{error:"preupload 失败:"+JSON.stringify(X)};var K="https:"+X.endpoint+"/"+String(X.upos_uri).replace(/^upos:\/\//,""),O=new URLSearchParams({uploads:"",output:"json",profile:"ugcfx/bup",filesize:String(Y),partsize:String(X.chunk_size),biz_id:String(X.biz_id)}),H=await(await fetch(K+"?"+O.toString(),{method:"POST",headers:{"x-upos-auth":X.auth}})).json();if(H.OK!==1)return{error:"获取 upload_id 失败:"+JSON.stringify(H)};var T=Math.max(1,Math.ceil(Y/X.chunk_size));return{url:K,auth:X.auth,uploadId:H.upload_id,chunkSize:X.chunk_size,bizId:X.biz_id,size:Y,name:Z.name,total:T}}catch(R){return{error:"preupload 异常:"+String(R&&R.message||R)}}}()}function d(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{ok:!1,error:"文件丢失(页面可能已导航)"};var Z=J.files[0],Y=W.idx*W.chunkSize,$=Z.slice(Y,Math.min(Y+W.chunkSize,W.size)),G=$.size,X=new URLSearchParams({partNumber:String(W.idx+1),uploadId:String(W.uploadId),chunk:String(W.idx),chunks:String(W.total),size:String(G),start:String(Y),end:String(Y+G),total:String(W.size)});return async function(){for(var K=0;K<5;K++){try{var O=await fetch(W.url+"?"+X.toString(),{method:"PUT",headers:{"x-upos-auth":W.auth},body:$});if(O.status<400){var H=await O.text();if(H==="MULTIPART_PUT_SUCCESS"||H==="")return{ok:!0}}}catch(T){}await new Promise(function(T){setTimeout(T,800*(K+1))})}return{ok:!1,error:"分块 "+W.idx+" 多次重试仍失败"}}()}function j(W){var J=[];for(var Z=1;Z<=W.total;Z++)J.push({partNumber:Z,eTag:"etag"});var Y=new URLSearchParams({output:"json",name:W.name,profile:"ugcfx/bup",uploadId:String(W.uploadId),biz_id:String(W.bizId)});return async function(){try{var $=await(await fetch(W.url+"?"+Y.toString(),{method:"POST",headers:{"x-upos-auth":W.auth,"content-type":"application/json; charset=UTF-8"},body:JSON.stringify({parts:J})})).json();if($.OK!==1)return{error:"complete 失败:"+JSON.stringify($)};var G=String($.key).replace(/^\//,"").replace(/\.[^.]+$/,"");return{filename:G,cid:W.bizId}}catch(X){return{error:"complete 异常:"+String(X&&X.message||X)}}}()}function c(W){return async function(){try{var J=await fetch("https://member.bilibili.com/x/vupre/web/topic/type?type_id="+encodeURIComponent(W.tid)+"&pn=0&ps=200",{credentials:"include"}),Z=await J.json();if(!Z||Z.code!==0||!Z.data||!Array.isArray(Z.data.topics))return{error:"获取话题列表失败:"+(Z&&Z.message||"code="+(Z&&Z.code))};var Y=null;for(var $=0;$<Z.data.topics.length;$++)if(Number(Z.data.topics[$].topic_id)===Number(W.topicId)){Y=Z.data.topics[$];break}if(!Y)return{error:"topic_id "+W.topicId+" 不在分区 "+W.tid+" 的可用话题里(用 bilibili topics --tid "+W.tid+" 查合法值)"};return{topicId:Number(Y.topic_id),missionId:Y.mission_id!=null?Number(Y.mission_id):null,name:Y.topic_name||""}}catch(G){return{error:"解析话题异常:"+String(G&&G.message||G)}}}()}function o(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]);try{var Y=W.dataUri;if(typeof Y==="string"&&Y.indexOf("data:image/webp")===0){var $=Y.indexOf(",");if($===-1)return{error:"封面 data URI 格式非法"};var G=Y.slice(5,$),X=Y.slice($+1),K;if(G.indexOf("base64")!==-1){var O=atob(X);K=new Uint8Array(O.length);for(var H=0;H<O.length;H++)K[H]=O.charCodeAt(H)}else K=new TextEncoder().encode(decodeURIComponent(X));var T=new Blob([K],{type:"image/webp"}),R=await createImageBitmap(T),B=document.createElement("canvas");B.width=R.width;B.height=R.height;var M=B.getContext("2d");M.fillStyle="#ffffff";M.fillRect(0,0,B.width,B.height);M.drawImage(R,0,0);var q=await new Promise(function(F){B.toBlob(F,"image/jpeg",0.92)});if(!q)return{error:"封面 webp 转码 JPEG 失败"};Y=await new Promise(function(F,x){var A=new FileReader;A.onload=function(){F(String(A.result))};A.onerror=function(){x(Error("FileReader 读取转码结果失败"))};A.readAsDataURL(q)})}var L=new URLSearchParams;L.append("cover",Y);L.append("csrf",Z);var V=await(await fetch("https://member.bilibili.com/x/vu/web/cover/up",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:L.toString()})).json();if(!V||V.code!==0||!V.data||!V.data.url)return{error:"封面上传失败:"+(V&&V.message?V.message:JSON.stringify(V))};return{url:V.data.url}}catch(F){return{error:"封面上传异常:"+String(F&&F.message||F)}}}()}function p(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.meta;for(var $ in Y)if(Y[$]===null||Y[$]===void 0)delete Y[$];Y.csrf=Z;try{var G=await fetch("https://member.bilibili.com/x/vu/web/add/v3?csrf="+encodeURIComponent(Z)+"&t="+Date.now(),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),X=await G.json();if(!X||X.code!==0)return{error:"add/v3 提交失败:"+(X&&X.message?X.message:"")+"(code="+(X&&X.code)+" status="+G.status+")"};return{bvid:X.data&&X.data.bvid,aid:X.data&&X.data.aid}}catch(K){return{error:"add/v3 异常:"+String(K&&K.message||K)}}}()}function g(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.body;Y.csrf=Z;try{var $=await fetch("https://member.bilibili.com/x/vupre/web/draft/add?t="+Date.now()+"&csrf="+encodeURIComponent(Z),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),G=await $.json();if(!G||G.code!==0)return{error:"draft/add 保存草稿失败:"+(G&&G.message?G.message:"")+"(code="+(G&&G.code)+" status="+$.status+")"};return{data:G.data}}catch(X){return{error:"draft/add 异常:"+String(X&&X.message||X)}}}()}const N=(W,J,Z)=>W.evaluateWithArgs(`(${J.toString()})(a)`,{a:Z});function s(W){const J=u(W),Z=P.extname(J).toLowerCase(),Y=Z===".png"?"image/png":Z===".webp"?"image/webp":Z===".bmp"?"image/bmp":"image/jpeg",$=I.readFileSync(J).toString("base64");return`data:${Y};base64,${$}`}async function t(W){for(let J=0;J<8;J++){await W.goto(m);await W.wait({time:1});let Z="";try{Z=String(await W.evaluate("location.href")||"")}catch{Z=""}if(/^https?:\/\/member\.bilibili\.com\//.test(Z)){const Y=await W.getCookies({url:"https://member.bilibili.com"}),$=new Map;for(const X of Array.isArray(Y)?Y:[])if(X&&typeof X.name==="string")$.set(X.name,String(X.value??""));const G=k.filter((X)=>!$.get(X));if(!G.length)return $;throw new h("member.bilibili.com",`缺少 B站登录 cookie(${G.join(", ")})。请先在客户端登录哔哩哔哩。`)}}throw new h("member.bilibili.com","无法进入 B站创作中心(可能未登录或被风控)。请先在客户端登录哔哩哔哩。")}E({site:"bilibili",name:"upload",access:"write",description:"投稿视频到 B站(在真实浏览器登录态里走 web 投稿:preupload→upos 分块→add/v3)。默认仅 dry-run 校验,加 --execute 才真正上传并提交。",domain:"member.bilibili.com",strategy:b.COOKIE,browser:!0,args:[{name:"file",required:!0,positional:!0,help:"视频文件路径"},{name:"title",help:"稿件标题(默认取文件名)"},{name:"tid",type:"number",required:!0,help:"分区 id(B站 typeid,必填,禁止臆造)。合法值用 `bilibili partitions` 列举后取。"},{name:"tag",required:!0,help:"标签,逗号分隔(B站要求至少 1 个)"},{name:"desc",help:"简介"},{name:"topic",type:"number",help:"参与话题的 topic_id(重要流量入口)。合法值用 `bilibili topics --tid <tid>` 列举,禁止臆造;mission_id 自动匹配。"},{name:"cover",help:"封面图片路径(不传由 B站自动截取)"},{name:"copyright",type:"number",help:"1=自制 2=转载(默认 1)"},{name:"source",help:"转载来源 URL(copyright=2 时必填)"},{name:"dynamic",help:"同步发布的动态文案(可空)"},{name:"no-reprint",type:"boolean",help:"禁止转载(默认允许)"},{name:"dtime",type:"number",help:"定时发布的 10 位 unix 时间戳(可空=立即)"},{name:"line",help:`上传线路:${Object.keys(U).join("/")}(默认 ${f})`},{name:"concurrency",type:"number",help:"分块并发数(默认 3)"},{name:"draft",type:"boolean",help:"存草稿而非直接发布(上传视频后存到创作中心草稿箱,不公开)"},{name:"execute",type:"boolean",help:"真正发布投稿;不带(也不带 --draft)则只做 dry-run 校验"}],columns:["status","title","bvid","url"],func:async(W,J)=>{if(!W)throw new S("bilibili upload 需要浏览器会话");const Z=l(String(J.file??"")),Y=String(J.title??"").trim()||P.basename(Z).replace(/\.[^.]+$/,""),$=String(J.tag??"").split(",").map((Q)=>Q.trim()).filter(Boolean);if(!$.length)throw new D("B站投稿至少需要 1 个标签,用 --tag 传(逗号分隔)");if(J.tid==null||J.tid===""||!Number.isFinite(Number(J.tid)))throw new D("必须用 --tid 指定分区 id(数字)。合法值用 `bilibili partitions` 列举,禁止臆造。");const G=Number(J.tid),X=J.copyright!=null&&J.copyright!==""?Number(J.copyright):1;if(X!==1&&X!==2)throw new D("--copyright 只能是 1(自制) 或 2(转载)");const K=String(J.source??"").trim();if(X===2&&!K)throw new D("转载(copyright=2)必须用 --source 提供转载来源");if(J.cover)u(String(J.cover));const O=String(J.line??f),H=U[O];if(!H)throw new D(`未知上传线路「${O}」,可选:${Object.keys(U).join("/")}`);const T=J.concurrency!=null&&J.concurrency!==""?Number(J.concurrency):3,R=Boolean(J.draft),B=Boolean(J.execute)&&!R;await t(W);if(!W.setFileInput)throw new S("浏览器扩展不支持 CDP 文件上传(set-file-input),无法上传视频;请升级 PublishPort 客户端/扩展");await W.evaluate(`(() => { var id=${JSON.stringify(_)}; var el=document.getElementById(id); if(!el){ el=document.createElement('input'); el.type='file'; el.id=id; el.style.cssText='position:fixed;left:-9999px;top:0;'; document.body.appendChild(el);} el.value=''; return true; })()`);try{await W.setFileInput([Z],`#${_}`)}catch(Q){w(Q);throw Q}if(!R&&!B)return{status:"dry-run",title:Y,bvid:"",url:`校验通过:登录态有效、视频已注入就绪、分区 ${G}、标签 [${$.join(", ")}]、版权 ${X===1?"自制":"转载"}。加 --execute 发布,或 --draft 存草稿。`};const M=await N(W,n,{inputId:_,line:H});if(!M||M.error)throw new S(`B站 preupload 失败:${M&&M.error||"无返回"}`);for(let Q=0;Q<M.total;Q++){const z=await N(W,d,{inputId:_,url:M.url,auth:M.auth,uploadId:M.uploadId,idx:Q,total:M.total,size:M.size,chunkSize:M.chunkSize});if(!z||!z.ok)throw new S(`B站分块上传失败(${Q+1}/${M.total}):${z&&z.error||"无返回"}`)}const q=await N(W,j,{url:M.url,auth:M.auth,uploadId:M.uploadId,bizId:M.bizId,name:M.name,total:M.total});if(!q||q.error||!q.filename)throw new S(`B站 complete 失败:${q&&q.error||"未拿到 filename"}`);let L="";if(J.cover){const Q=s(String(J.cover)),z=await N(W,o,{dataUri:Q});if(!z||z.error||!z.url)throw new S(`B站封面上传失败:${z&&z.error||"未拿到 url"}`);L=z.url}let V=null,F=null;if(J.topic!=null&&J.topic!==""){if(!Number.isFinite(Number(J.topic)))throw new D("--topic 必须是 topic_id 数字,用 `bilibili topics --tid <tid>` 取合法值");const Q=await N(W,c,{tid:G,topicId:Number(J.topic)});if(!Q||Q.error)throw new S(`B站话题解析失败:${Q&&Q.error||"无返回"}`);V=Q.topicId;F=Q.missionId}if(R){const Q={videos:[{filename:q.filename,title:Y,desc:"",cid:q.cid,is_4k:!1,is_8k:!1,is_hdr:!1}],cover:L,cover43:L,ai_cover:0,is_ab_cover:0,ab_cover_info:null,title:Y,copyright:X,tid:G,tag:$.join(","),desc:String(J.desc??""),recreate:-1,dynamic:String(J.dynamic??""),is_only_self:0,space_hidden:2,watermark:{state:0},no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1};if(X===2)Q.source=K;if(V!=null){Q.topic_id=V;Q.topic_detail={from_topic_id:V,from_source:"arc.web.search"};if(F!=null)Q.mission_id=F}const z=await N(W,g,{body:Q});if(!z||z.error)throw new S(`B站 ${z&&z.error||"draft/add 无返回"}`);const y=z.data&&(z.data.aid!=null?z.data.aid:z.data.draft_id!=null?z.data.draft_id:z.data.id);return{status:"✅ 草稿已保存",title:Y,bvid:y!=null?`draft:${y}`:"",url:"https://member.bilibili.com/platform/upload-manager/article?group=draft"}}const x=J.dtime!=null&&J.dtime!==""?Number(J.dtime):null,A={title:Y,copyright:X,tid:G,tag:$.join(","),mission_id:F,topic_id:V,topic_detail:V!=null?{from_topic_id:V,from_source:"arc.web.recommend"}:null,desc_format_id:9999,desc:String(J.desc??""),dtime:x,recreate:-1,dynamic:String(J.dynamic??""),interactive:0,act_reserve_create:0,no_disturbance:0,porder:null,adorder_type:9,no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},neutral_mark:null,dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1,web_os:1,source:X===2?K:null,watermark:{state:0},cover:L,videos:[{title:Y,desc:"",filename:q.filename,cid:q.cid}]},C=await N(W,p,{meta:A});if(!C||C.error)throw new S(`B站 ${C&&C.error||"add/v3 无返回"}`);const v=C.bvid?String(C.bvid):"";return{status:J.dtime?"✅ 定时投稿已提交":"✅ 投稿成功",title:Y,bvid:v,url:v?`https://www.bilibili.com/video/${v}`:"(已提交,稍后在创作中心查看)"}}});
1
+ import*as I from"node:fs";import*as P from"node:path";import{cli as E,Strategy as b}from"@jackwener/opencli/registry";import{ArgumentError as D,AuthRequiredError as h,CommandExecutionError as L}from"@jackwener/opencli/errors";import{resolveVideoFile as l,resolveImageFile as u}from"../_shared/video-publish.js";import{setInputFiles as w}from"../_shared/file-inject.js";const U={bda2:{os:"upos",upcdn:"bda2",probe_version:20221109},bldsa:{os:"upos",upcdn:"bldsa",probe_version:20221109},qn:{os:"upos",upcdn:"qn",probe_version:20221109},ws:{os:"upos",upcdn:"ws",probe_version:20221109}},f="bda2",k=["SESSDATA","bili_jct","DedeUserID"],m="https://member.bilibili.com/platform/home",x="__pp_bili_video_input";function n(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{error:"文件未就绪(input 为空)"};var Z=J.files[0],Y=Z.size,$=W.line;return async function(){try{var G=new URLSearchParams({profile:"ugcfx/bup",name:Z.name,size:String(Y),r:$.os,ssl:"0",version:"2.14.0",build:"2100400",upcdn:$.upcdn,probe_version:String($.probe_version)}),X=await(await fetch("https://member.bilibili.com/preupload?"+G.toString(),{credentials:"include",headers:{Referer:"https://www.bilibili.com"}})).json();if(X.OK!==1)return{error:"preupload 失败:"+JSON.stringify(X)};var K="https:"+X.endpoint+"/"+String(X.upos_uri).replace(/^upos:\/\//,""),O=new URLSearchParams({uploads:"",output:"json",profile:"ugcfx/bup",filesize:String(Y),partsize:String(X.chunk_size),biz_id:String(X.biz_id)}),H=await(await fetch(K+"?"+O.toString(),{method:"POST",headers:{"x-upos-auth":X.auth}})).json();if(H.OK!==1)return{error:"获取 upload_id 失败:"+JSON.stringify(H)};var T=Math.max(1,Math.ceil(Y/X.chunk_size));return{url:K,auth:X.auth,uploadId:H.upload_id,chunkSize:X.chunk_size,bizId:X.biz_id,size:Y,name:Z.name,total:T}}catch(R){return{error:"preupload 异常:"+String(R&&R.message||R)}}}()}function d(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{ok:!1,error:"文件丢失(页面可能已导航)"};var Z=J.files[0],Y=W.idx*W.chunkSize,$=Z.slice(Y,Math.min(Y+W.chunkSize,W.size)),G=$.size,X=new URLSearchParams({partNumber:String(W.idx+1),uploadId:String(W.uploadId),chunk:String(W.idx),chunks:String(W.total),size:String(G),start:String(Y),end:String(Y+G),total:String(W.size)});return async function(){for(var K=0;K<5;K++){try{var O=await fetch(W.url+"?"+X.toString(),{method:"PUT",headers:{"x-upos-auth":W.auth},body:$});if(O.status<400){var H=await O.text();if(H==="MULTIPART_PUT_SUCCESS"||H==="")return{ok:!0}}}catch(T){}await new Promise(function(T){setTimeout(T,800*(K+1))})}return{ok:!1,error:"分块 "+W.idx+" 多次重试仍失败"}}()}function c(W){var J=[];for(var Z=1;Z<=W.total;Z++)J.push({partNumber:Z,eTag:"etag"});var Y=new URLSearchParams({output:"json",name:W.name,profile:"ugcfx/bup",uploadId:String(W.uploadId),biz_id:String(W.bizId)});return async function(){try{var $=await(await fetch(W.url+"?"+Y.toString(),{method:"POST",headers:{"x-upos-auth":W.auth,"content-type":"application/json; charset=UTF-8"},body:JSON.stringify({parts:J})})).json();if($.OK!==1)return{error:"complete 失败:"+JSON.stringify($)};var G=String($.key).replace(/^\//,"").replace(/\.[^.]+$/,"");return{filename:G,cid:W.bizId}}catch(X){return{error:"complete 异常:"+String(X&&X.message||X)}}}()}function j(W){return async function(){try{var J=await fetch("https://member.bilibili.com/x/vupre/web/topic/type?type_id="+encodeURIComponent(W.tid)+"&pn=0&ps=200",{credentials:"include"}),Z=await J.json();if(!Z||Z.code!==0||!Z.data||!Array.isArray(Z.data.topics))return{error:"获取话题列表失败:"+(Z&&Z.message||"code="+(Z&&Z.code))};var Y=null;for(var $=0;$<Z.data.topics.length;$++)if(Number(Z.data.topics[$].topic_id)===Number(W.topicId)){Y=Z.data.topics[$];break}if(!Y)return{error:"topic_id "+W.topicId+" 不在分区 "+W.tid+" 的可用话题里(用 bilibili topics --tid "+W.tid+" 查合法值)"};return{topicId:Number(Y.topic_id),missionId:Y.mission_id!=null?Number(Y.mission_id):null,name:Y.topic_name||""}}catch(G){return{error:"解析话题异常:"+String(G&&G.message||G)}}}()}function o(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]);try{var Y=W.dataUri;if(typeof Y==="string"&&Y.indexOf("data:image/webp")===0){var $=Y.indexOf(",");if($===-1)return{error:"封面 data URI 格式非法"};var G=Y.slice(5,$),X=Y.slice($+1),K;if(G.indexOf("base64")!==-1){var O=atob(X);K=new Uint8Array(O.length);for(var H=0;H<O.length;H++)K[H]=O.charCodeAt(H)}else K=new TextEncoder().encode(decodeURIComponent(X));var T=new Blob([K],{type:"image/webp"}),R=await createImageBitmap(T),S=document.createElement("canvas");S.width=R.width;S.height=R.height;var Q=S.getContext("2d");Q.fillStyle="#ffffff";Q.fillRect(0,0,S.width,S.height);Q.drawImage(R,0,0);var q=await new Promise(function(F){S.toBlob(F,"image/jpeg",0.92)});if(!q)return{error:"封面 webp 转码 JPEG 失败"};Y=await new Promise(function(F,_){var A=new FileReader;A.onload=function(){F(String(A.result))};A.onerror=function(){_(Error("FileReader 读取转码结果失败"))};A.readAsDataURL(q)})}var B=new URLSearchParams;B.append("cover",Y);B.append("csrf",Z);var V=await(await fetch("https://member.bilibili.com/x/vu/web/cover/up",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:B.toString()})).json();if(!V||V.code!==0||!V.data||!V.data.url)return{error:"封面上传失败:"+(V&&V.message?V.message:JSON.stringify(V))};return{url:V.data.url}}catch(F){return{error:"封面上传异常:"+String(F&&F.message||F)}}}()}function p(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.meta;for(var $ in Y)if(Y[$]===null||Y[$]===void 0)delete Y[$];Y.csrf=Z;try{var G=await fetch("https://member.bilibili.com/x/vu/web/add/v3?csrf="+encodeURIComponent(Z)+"&t="+Date.now(),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),X=await G.json();if(!X||X.code!==0)return{error:"add/v3 提交失败:"+(X&&X.message?X.message:"")+"(code="+(X&&X.code)+" status="+G.status+")"};return{bvid:X.data&&X.data.bvid,aid:X.data&&X.data.aid}}catch(K){return{error:"add/v3 异常:"+String(K&&K.message||K)}}}()}function g(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.body;Y.csrf=Z;try{var $=await fetch("https://member.bilibili.com/x/vupre/web/draft/add?t="+Date.now()+"&csrf="+encodeURIComponent(Z),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),G=await $.json();if(!G||G.code!==0)return{error:"draft/add 保存草稿失败:"+(G&&G.message?G.message:"")+"(code="+(G&&G.code)+" status="+$.status+")"};return{data:G.data}}catch(X){return{error:"draft/add 异常:"+String(X&&X.message||X)}}}()}const N=(W,J,Z)=>W.evaluateWithArgs(`(${J.toString()})(a)`,{a:Z});function s(W){const J=u(W),Z=P.extname(J).toLowerCase(),Y=Z===".png"?"image/png":Z===".webp"?"image/webp":Z===".bmp"?"image/bmp":"image/jpeg",$=I.readFileSync(J).toString("base64");return`data:${Y};base64,${$}`}async function t(W){for(let J=0;J<8;J++){await W.goto(m);await W.wait({time:1});let Z="";try{Z=String(await W.evaluate("location.href")||"")}catch{Z=""}if(/^https?:\/\/member\.bilibili\.com\//.test(Z)){const Y=await W.getCookies({url:"https://member.bilibili.com"}),$=new Map;for(const X of Array.isArray(Y)?Y:[])if(X&&typeof X.name==="string")$.set(X.name,String(X.value??""));const G=k.filter((X)=>!$.get(X));if(!G.length)return $;throw new h("member.bilibili.com",`缺少 B站登录 cookie(${G.join(", ")})。请先在客户端登录哔哩哔哩。`)}}throw new h("member.bilibili.com","无法进入 B站创作中心(可能未登录或被风控)。请先在客户端登录哔哩哔哩。")}E({site:"bilibili",name:"upload",access:"write",description:"投稿视频到 B站(在真实浏览器登录态里走 web 投稿:preupload→upos 分块→add/v3)。默认仅 dry-run 校验,加 --execute 才真正上传并提交。",domain:"member.bilibili.com",strategy:b.COOKIE,browser:!0,args:[{name:"file",required:!0,positional:!0,help:"视频文件路径"},{name:"title",help:"稿件标题(默认取文件名)"},{name:"tid",type:"number",required:!0,help:"分区 id(B站 typeid,必填,禁止臆造)。合法值用 `bilibili partitions` 列举后取。"},{name:"tag",required:!0,help:"标签,逗号分隔(B站要求至少 1 个)"},{name:"desc",help:"简介"},{name:"topic",type:"number",help:"参与话题的 topic_id(重要流量入口)。合法值用 `bilibili topics --tid <tid>` 列举,禁止臆造;mission_id 自动匹配。"},{name:"cover",help:"封面图片路径(不传由 B站自动截取)"},{name:"copyright",type:"number",help:"1=自制 2=转载(默认 1)"},{name:"source",help:"转载来源 URL(copyright=2 时必填)"},{name:"dynamic",help:"同步发布的动态文案(可空)"},{name:"no-reprint",type:"boolean",help:"禁止转载(默认允许)"},{name:"dtime",type:"number",help:"定时发布的 10 位 unix 时间戳(可空=立即)"},{name:"line",help:`上传线路:${Object.keys(U).join("/")}(默认 ${f})`},{name:"concurrency",type:"number",help:"分块并发数(默认 3)"},{name:"draft",type:"boolean",help:"存草稿而非直接发布(上传视频后存到创作中心草稿箱,不公开)"},{name:"execute",type:"boolean",help:"真正发布投稿;不带(也不带 --draft)则只做 dry-run 校验"}],columns:["status","title","bvid","url"],func:async(W,J)=>{if(!W)throw new L("bilibili upload 需要浏览器会话");const Z=l(String(J.file??"")),Y=String(J.title??"").trim()||P.basename(Z).replace(/\.[^.]+$/,""),$=String(J.tag??"").split(",").map((z)=>z.trim()).filter(Boolean);if(!$.length)throw new D("B站投稿至少需要 1 个标签,用 --tag 传(逗号分隔)");if(J.tid==null||J.tid===""||!Number.isFinite(Number(J.tid)))throw new D("必须用 --tid 指定分区 id(数字)。合法值用 `bilibili partitions` 列举,禁止臆造。");const G=Number(J.tid),X=J.copyright!=null&&J.copyright!==""?Number(J.copyright):1;if(X!==1&&X!==2)throw new D("--copyright 只能是 1(自制) 或 2(转载)");const K=String(J.source??"").trim();if(X===2&&!K)throw new D("转载(copyright=2)必须用 --source 提供转载来源");if(J.cover)u(String(J.cover));const O=String(J.line??f),H=U[O];if(!H)throw new D(`未知上传线路「${O}」,可选:${Object.keys(U).join("/")}`);const T=J.concurrency!=null&&J.concurrency!==""?Number(J.concurrency):3,R=Boolean(J.draft),S=Boolean(J.execute)&&!R;await t(W);await W.evaluate(`(() => { var id=${JSON.stringify(x)}; var el=document.getElementById(id); if(!el){ el=document.createElement('input'); el.type='file'; el.id=id; el.style.cssText='position:fixed;left:-9999px;top:0;'; document.body.appendChild(el);} el.value=''; return true; })()`);await w(W,`#${x}`,[Z]);if(!R&&!S)return{status:"dry-run",title:Y,bvid:"",url:`校验通过:登录态有效、视频已注入就绪、分区 ${G}、标签 [${$.join(", ")}]、版权 ${X===1?"自制":"转载"}。加 --execute 发布,或 --draft 存草稿。`};const Q=await N(W,n,{inputId:x,line:H});if(!Q||Q.error)throw new L(`B站 preupload 失败:${Q&&Q.error||"无返回"}`);for(let z=0;z<Q.total;z++){const M=await N(W,d,{inputId:x,url:Q.url,auth:Q.auth,uploadId:Q.uploadId,idx:z,total:Q.total,size:Q.size,chunkSize:Q.chunkSize});if(!M||!M.ok)throw new L(`B站分块上传失败(${z+1}/${Q.total}):${M&&M.error||"无返回"}`)}const q=await N(W,c,{url:Q.url,auth:Q.auth,uploadId:Q.uploadId,bizId:Q.bizId,name:Q.name,total:Q.total});if(!q||q.error||!q.filename)throw new L(`B站 complete 失败:${q&&q.error||"未拿到 filename"}`);let B="";if(J.cover){const z=s(String(J.cover)),M=await N(W,o,{dataUri:z});if(!M||M.error||!M.url)throw new L(`B站封面上传失败:${M&&M.error||"未拿到 url"}`);B=M.url}let V=null,F=null;if(J.topic!=null&&J.topic!==""){if(!Number.isFinite(Number(J.topic)))throw new D("--topic 必须是 topic_id 数字,用 `bilibili topics --tid <tid>` 取合法值");const z=await N(W,j,{tid:G,topicId:Number(J.topic)});if(!z||z.error)throw new L(`B站话题解析失败:${z&&z.error||"无返回"}`);V=z.topicId;F=z.missionId}if(R){const z={videos:[{filename:q.filename,title:Y,desc:"",cid:q.cid,is_4k:!1,is_8k:!1,is_hdr:!1}],cover:B,cover43:B,ai_cover:0,is_ab_cover:0,ab_cover_info:null,title:Y,copyright:X,tid:G,tag:$.join(","),desc:String(J.desc??""),recreate:-1,dynamic:String(J.dynamic??""),is_only_self:0,space_hidden:2,watermark:{state:0},no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1};if(X===2)z.source=K;if(V!=null){z.topic_id=V;z.topic_detail={from_topic_id:V,from_source:"arc.web.search"};if(F!=null)z.mission_id=F}const M=await N(W,g,{body:z});if(!M||M.error)throw new L(`B站 ${M&&M.error||"draft/add 无返回"}`);const y=M.data&&(M.data.aid!=null?M.data.aid:M.data.draft_id!=null?M.data.draft_id:M.data.id);return{status:"✅ 草稿已保存",title:Y,bvid:y!=null?`draft:${y}`:"",url:"https://member.bilibili.com/platform/upload-manager/article?group=draft"}}const _=J.dtime!=null&&J.dtime!==""?Number(J.dtime):null,A={title:Y,copyright:X,tid:G,tag:$.join(","),mission_id:F,topic_id:V,topic_detail:V!=null?{from_topic_id:V,from_source:"arc.web.recommend"}:null,desc_format_id:9999,desc:String(J.desc??""),dtime:_,recreate:-1,dynamic:String(J.dynamic??""),interactive:0,act_reserve_create:0,no_disturbance:0,porder:null,adorder_type:9,no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},neutral_mark:null,dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1,web_os:1,source:X===2?K:null,watermark:{state:0},cover:B,videos:[{title:Y,desc:"",filename:q.filename,cid:q.cid}]},C=await N(W,p,{meta:A});if(!C||C.error)throw new L(`B站 ${C&&C.error||"add/v3 无返回"}`);const v=C.bvid?String(C.bvid):"";return{status:J.dtime?"✅ 定时投稿已提交":"✅ 投稿成功",title:Y,bvid:v,url:v?`https://www.bilibili.com/video/${v}`:"(已提交,稍后在创作中心查看)"}}});