publishport-opencli 1.8.5-pp.20 → 1.8.5-pp.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli-manifest.json CHANGED
@@ -3862,6 +3862,54 @@
3862
3862
  "sourceFile": "bilibili/categories.js",
3863
3863
  "navigateBefore": "https://member.bilibili.com"
3864
3864
  },
3865
+ {
3866
+ "site": "bilibili",
3867
+ "name": "coin",
3868
+ "description": "给 B站视频投币(官方 API,需登录;消耗硬币不可撤销,需 --execute)",
3869
+ "access": "write",
3870
+ "domain": "www.bilibili.com",
3871
+ "strategy": "cookie",
3872
+ "browser": true,
3873
+ "args": [
3874
+ {
3875
+ "name": "bvid",
3876
+ "type": "str",
3877
+ "required": true,
3878
+ "positional": true,
3879
+ "help": "Video BV ID / URL / b23.tv short link"
3880
+ },
3881
+ {
3882
+ "name": "count",
3883
+ "type": "int",
3884
+ "default": 1,
3885
+ "required": false,
3886
+ "help": "投币数量,1 或 2(单视频累计上限 2 枚)"
3887
+ },
3888
+ {
3889
+ "name": "with-like",
3890
+ "type": "boolean",
3891
+ "required": false,
3892
+ "help": "投币同时点赞"
3893
+ },
3894
+ {
3895
+ "name": "execute",
3896
+ "type": "boolean",
3897
+ "required": false,
3898
+ "help": "Actually spend coins. Without it the command refuses to write."
3899
+ }
3900
+ ],
3901
+ "columns": [
3902
+ "bvid",
3903
+ "coined",
3904
+ "total",
3905
+ "status",
3906
+ "url"
3907
+ ],
3908
+ "type": "js",
3909
+ "modulePath": "bilibili/coin.js",
3910
+ "sourceFile": "bilibili/coin.js",
3911
+ "navigateBefore": "https://www.bilibili.com"
3912
+ },
3865
3913
  {
3866
3914
  "site": "bilibili",
3867
3915
  "name": "comment",
@@ -3910,6 +3958,87 @@
3910
3958
  "sourceFile": "bilibili/comment.js",
3911
3959
  "navigateBefore": "https://www.bilibili.com"
3912
3960
  },
3961
+ {
3962
+ "site": "bilibili",
3963
+ "name": "comment-del",
3964
+ "description": "删除自己发的 B站视频评论(官方 API,需登录;不可恢复,需 --execute)",
3965
+ "access": "write",
3966
+ "domain": "www.bilibili.com",
3967
+ "strategy": "cookie",
3968
+ "browser": true,
3969
+ "args": [
3970
+ {
3971
+ "name": "bvid",
3972
+ "type": "str",
3973
+ "required": true,
3974
+ "positional": true,
3975
+ "help": "Video BV ID / URL / b23.tv short link"
3976
+ },
3977
+ {
3978
+ "name": "rpid",
3979
+ "type": "str",
3980
+ "required": true,
3981
+ "positional": true,
3982
+ "help": "要删除的评论 rpid(comment 命令发布时返回的 rpid)"
3983
+ },
3984
+ {
3985
+ "name": "execute",
3986
+ "type": "boolean",
3987
+ "required": false,
3988
+ "help": "Actually delete the comment. Without it the command refuses to write."
3989
+ }
3990
+ ],
3991
+ "columns": [
3992
+ "bvid",
3993
+ "rpid",
3994
+ "status"
3995
+ ],
3996
+ "type": "js",
3997
+ "modulePath": "bilibili/comment-del.js",
3998
+ "sourceFile": "bilibili/comment-del.js",
3999
+ "navigateBefore": "https://www.bilibili.com"
4000
+ },
4001
+ {
4002
+ "site": "bilibili",
4003
+ "name": "comment-like",
4004
+ "description": "给 B站视频评论点赞 / 取消点赞(官方 API,需登录;rpid 从 comments 命令取)",
4005
+ "access": "write",
4006
+ "domain": "www.bilibili.com",
4007
+ "strategy": "cookie",
4008
+ "browser": true,
4009
+ "args": [
4010
+ {
4011
+ "name": "bvid",
4012
+ "type": "str",
4013
+ "required": true,
4014
+ "positional": true,
4015
+ "help": "Video BV ID / URL / b23.tv short link"
4016
+ },
4017
+ {
4018
+ "name": "rpid",
4019
+ "type": "str",
4020
+ "required": true,
4021
+ "positional": true,
4022
+ "help": "评论 rpid(bilibili comments 输出里的 id)"
4023
+ },
4024
+ {
4025
+ "name": "undo",
4026
+ "type": "boolean",
4027
+ "required": false,
4028
+ "help": "取消点赞(默认是点赞)"
4029
+ }
4030
+ ],
4031
+ "columns": [
4032
+ "bvid",
4033
+ "rpid",
4034
+ "status",
4035
+ "url"
4036
+ ],
4037
+ "type": "js",
4038
+ "modulePath": "bilibili/comment-like.js",
4039
+ "sourceFile": "bilibili/comment-like.js",
4040
+ "navigateBefore": "https://www.bilibili.com"
4041
+ },
3913
4042
  {
3914
4043
  "site": "bilibili",
3915
4044
  "name": "comments",
@@ -3954,6 +4083,104 @@
3954
4083
  "sourceFile": "bilibili/comments.js",
3955
4084
  "navigateBefore": "https://www.bilibili.com"
3956
4085
  },
4086
+ {
4087
+ "site": "bilibili",
4088
+ "name": "dm-list",
4089
+ "description": "B站私信会话列表(talker_id 即对方 UID,供 dm-read / dm-send 用)",
4090
+ "access": "read",
4091
+ "domain": "www.bilibili.com",
4092
+ "strategy": "cookie",
4093
+ "browser": true,
4094
+ "args": [
4095
+ {
4096
+ "name": "limit",
4097
+ "type": "int",
4098
+ "default": 20,
4099
+ "required": false,
4100
+ "help": "返回会话数上限"
4101
+ }
4102
+ ],
4103
+ "columns": [
4104
+ "talker_id",
4105
+ "name",
4106
+ "last_content",
4107
+ "unread",
4108
+ "last_time"
4109
+ ],
4110
+ "type": "js",
4111
+ "modulePath": "bilibili/dm-list.js",
4112
+ "sourceFile": "bilibili/dm-list.js",
4113
+ "navigateBefore": "https://www.bilibili.com"
4114
+ },
4115
+ {
4116
+ "site": "bilibili",
4117
+ "name": "dm-read",
4118
+ "description": "读取与某 B站用户的私信记录(近 30 条;target 为 UID / 用户名)",
4119
+ "access": "read",
4120
+ "domain": "www.bilibili.com",
4121
+ "strategy": "cookie",
4122
+ "browser": true,
4123
+ "args": [
4124
+ {
4125
+ "name": "target",
4126
+ "type": "str",
4127
+ "required": true,
4128
+ "positional": true,
4129
+ "help": "对方 UID / 用户名(dm-list 的 talker_id)"
4130
+ }
4131
+ ],
4132
+ "columns": [
4133
+ "time",
4134
+ "direction",
4135
+ "sender_uid",
4136
+ "content",
4137
+ "msg_type"
4138
+ ],
4139
+ "type": "js",
4140
+ "modulePath": "bilibili/dm-read.js",
4141
+ "sourceFile": "bilibili/dm-read.js",
4142
+ "navigateBefore": "https://www.bilibili.com"
4143
+ },
4144
+ {
4145
+ "site": "bilibili",
4146
+ "name": "dm-send",
4147
+ "description": "给 B站用户发私信(纯文本,官方 API,需登录;不可撤回,需 --execute)",
4148
+ "access": "write",
4149
+ "domain": "www.bilibili.com",
4150
+ "strategy": "cookie",
4151
+ "browser": true,
4152
+ "args": [
4153
+ {
4154
+ "name": "target",
4155
+ "type": "str",
4156
+ "required": true,
4157
+ "positional": true,
4158
+ "help": "对方 UID / 用户名(dm-list 的 talker_id)"
4159
+ },
4160
+ {
4161
+ "name": "message",
4162
+ "type": "str",
4163
+ "required": true,
4164
+ "positional": true,
4165
+ "help": "私信内容(纯文本)"
4166
+ },
4167
+ {
4168
+ "name": "execute",
4169
+ "type": "boolean",
4170
+ "required": false,
4171
+ "help": "Actually send the message. Without it the command refuses to write."
4172
+ }
4173
+ ],
4174
+ "columns": [
4175
+ "receiver_id",
4176
+ "msg_key",
4177
+ "status"
4178
+ ],
4179
+ "type": "js",
4180
+ "modulePath": "bilibili/dm-send.js",
4181
+ "sourceFile": "bilibili/dm-send.js",
4182
+ "navigateBefore": "https://www.bilibili.com"
4183
+ },
3957
4184
  {
3958
4185
  "site": "bilibili",
3959
4186
  "name": "download",
@@ -4156,6 +4383,47 @@
4156
4383
  "sourceFile": "bilibili/favorite.js",
4157
4384
  "navigateBefore": "https://www.bilibili.com"
4158
4385
  },
4386
+ {
4387
+ "site": "bilibili",
4388
+ "name": "favorite-add",
4389
+ "description": "把 B站视频加入 / 移出收藏夹(官方 API,需登录;--folder 缺省用默认收藏夹)",
4390
+ "access": "write",
4391
+ "domain": "www.bilibili.com",
4392
+ "strategy": "cookie",
4393
+ "browser": true,
4394
+ "args": [
4395
+ {
4396
+ "name": "bvid",
4397
+ "type": "str",
4398
+ "required": true,
4399
+ "positional": true,
4400
+ "help": "Video BV ID / URL / b23.tv short link"
4401
+ },
4402
+ {
4403
+ "name": "folder",
4404
+ "type": "str",
4405
+ "required": false,
4406
+ "help": "目标收藏夹 ID 或标题(缺省用默认收藏夹)"
4407
+ },
4408
+ {
4409
+ "name": "remove",
4410
+ "type": "boolean",
4411
+ "required": false,
4412
+ "help": "从收藏夹移出(默认是加入)"
4413
+ }
4414
+ ],
4415
+ "columns": [
4416
+ "bvid",
4417
+ "folder_id",
4418
+ "folder",
4419
+ "status",
4420
+ "url"
4421
+ ],
4422
+ "type": "js",
4423
+ "modulePath": "bilibili/favorite-add.js",
4424
+ "sourceFile": "bilibili/favorite-add.js",
4425
+ "navigateBefore": "https://www.bilibili.com"
4426
+ },
4159
4427
  {
4160
4428
  "site": "bilibili",
4161
4429
  "name": "feed",
@@ -4365,6 +4633,40 @@
4365
4633
  "sourceFile": "bilibili/hot.js",
4366
4634
  "navigateBefore": "https://www.bilibili.com"
4367
4635
  },
4636
+ {
4637
+ "site": "bilibili",
4638
+ "name": "like",
4639
+ "description": "给 B站视频点赞 / 取消点赞(官方 API,需登录;幂等,已点赞再点不会报错)",
4640
+ "access": "write",
4641
+ "domain": "www.bilibili.com",
4642
+ "strategy": "cookie",
4643
+ "browser": true,
4644
+ "args": [
4645
+ {
4646
+ "name": "bvid",
4647
+ "type": "str",
4648
+ "required": true,
4649
+ "positional": true,
4650
+ "help": "Video BV ID / URL / b23.tv short link"
4651
+ },
4652
+ {
4653
+ "name": "undo",
4654
+ "type": "boolean",
4655
+ "required": false,
4656
+ "help": "取消点赞(默认是点赞)"
4657
+ }
4658
+ ],
4659
+ "columns": [
4660
+ "bvid",
4661
+ "action",
4662
+ "status",
4663
+ "url"
4664
+ ],
4665
+ "type": "js",
4666
+ "modulePath": "bilibili/like.js",
4667
+ "sourceFile": "bilibili/like.js",
4668
+ "navigateBefore": "https://www.bilibili.com"
4669
+ },
4368
4670
  {
4369
4671
  "site": "bilibili",
4370
4672
  "name": "login",
@@ -41,6 +41,29 @@ var PP = (function () {
41
41
  return new Blob([bytes], { type: mime });
42
42
  }
43
43
 
44
+ // ============ webp → JPEG 转码(统一图片格式支持)============
45
+ // 输入统一支持 webp:部分平台图床拒收 webp(百家号 errno 401100032、B 站 cover/up
46
+ // 均真机坐实),这些平台在上传前调本函数按需转码。非 webp 的 blob 原样返回。
47
+ // 检测按字节魔数(RIFF....WEBP),不信 blob.type(下载响应的 Content-Type 常缺/错)。
48
+ // JPEG 无透明通道,先铺白底再画。转码失败抛错,不静默回退原图。
49
+ async function webpToJpeg(blob) {
50
+ var head = new Uint8Array(await blob.slice(0, 12).arrayBuffer());
51
+ var isWebp = head[0] === 0x52 && head[1] === 0x49 && head[2] === 0x46 && head[3] === 0x46
52
+ && head[8] === 0x57 && head[9] === 0x45 && head[10] === 0x42 && head[11] === 0x50;
53
+ if (!isWebp) return blob;
54
+ var bmp = await createImageBitmap(blob);
55
+ var canvas = document.createElement('canvas');
56
+ canvas.width = bmp.width;
57
+ canvas.height = bmp.height;
58
+ var ctx = canvas.getContext('2d');
59
+ ctx.fillStyle = '#ffffff';
60
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
61
+ ctx.drawImage(bmp, 0, 0);
62
+ var jpeg = await new Promise(function (resolve) { canvas.toBlob(resolve, 'image/jpeg', 0.92); });
63
+ if (!jpeg) throw new Error('webp 转码 JPEG 失败');
64
+ return jpeg;
65
+ }
66
+
44
67
  // ============ MD5(字节级,页面内无依赖)============
45
68
  // Web Crypto 不提供 MD5,而部分平台的图片上传凭证要求图片字节的 md5
46
69
  //(如知乎 api.zhihu.com/images 的 image_hash)。移植自公有领域实现
@@ -676,6 +699,7 @@ var PP = (function () {
676
699
  pickPath: pickPath,
677
700
  md5: md5,
678
701
  dataUriToBlob: dataUriToBlob,
702
+ webpToJpeg: webpToJpeg,
679
703
  };
680
704
  })();
681
705
  `;export function evalPageRuntime(){return Function(PAGE_RUNTIME+`
@@ -1 +1 @@
1
- import{CliError as B,CommandExecutionError as I}from"@jackwener/opencli/errors";import{cli as R,Strategy as h}from"@jackwener/opencli/registry";import{publishArticle as m}from"../_shared/article/publish.js";import{readFile as v,stat as b}from"node:fs/promises";function w(G){if(!G.execute)throw new B("INVALID_INPUT","此百家号写操作需要 --execute 参数才能真正发布")}async function f(G){const D=typeof G.text==="string"?G.text:void 0,z=typeof G.file==="string"?G.file:void 0;if(D&&z)throw new B("INVALID_INPUT","正文和 --file 只能选一个");let K=D??"";if(z){let Q;try{Q=await b(z)}catch{throw new B("INVALID_INPUT",`找不到文件: ${z}`)}if(!Q.isFile())throw new B("INVALID_INPUT",`--file 必须是可读文本文件: ${z}`);let L;try{L=await v(z)}catch{throw new B("INVALID_INPUT",`文件无法读取: ${z}`)}try{K=new TextDecoder("utf-8",{fatal:!0}).decode(L)}catch{throw new B("INVALID_INPUT",`文件必须是 UTF-8 编码: ${z}`)}}if(!K.trim())throw new B("INVALID_INPUT","正文不能为空");return K}function n(G,D,z,K,Q={}){return[{status:"success",outcome:K,message:G,target_type:D,target:z,...Q}]}export const baijiahaoProfile={home:"https://baijiahao.baidu.com",outputFormat:"html",preprocessConfig:{},image:{skip:["baijiahao.baidu.com","bdstatic.com","bcebos.com"],uploadFn:async(G,D)=>{const z=await fetch(G,{credentials:"omit"});if(!z.ok)throw Error("图片下载失败: "+G+" HTTP "+z.status);let K=await z.blob();const Q=new Uint8Array(await K.slice(0,12).arrayBuffer());if(Q[0]===82&&Q[1]===73&&Q[2]===70&&Q[3]===70&&Q[8]===87&&Q[9]===69&&Q[10]===66&&Q[11]===80){const Z=await createImageBitmap(K),$=document.createElement("canvas");$.width=Z.width;$.height=Z.height;const F=$.getContext("2d");F.fillStyle="#ffffff";F.fillRect(0,0,$.width,$.height);F.drawImage(Z,0,0);K=await new Promise((O)=>$.toBlob(O,"image/jpeg",0.92));if(!K)throw Error("webp 转码 JPEG 失败: "+G)}const V=new FormData;V.append("media",K,"image.jpg");V.append("type","image");V.append("app_id","1589639493090963");V.append("is_waterlog","1");V.append("save_material","1");V.append("no_compress","0");V.append("is_events","");V.append("article_type","news");const X=await(await fetch("https://baijiahao.baidu.com/pcui/picture/uploadproxy",{method:"POST",credentials:"include",body:V})).json();if(X.errmsg!=="success"||!X.ret||!X.ret.https_url)throw Error(X.errmsg||"图片上传失败");return{url:X.ret.https_url}}},publish:async(G,D)=>{const z=G.params||{},K=await fetch("https://baijiahao.baidu.com/builder/rc/edit",{credentials:"include"}),L=(await K.text()).match(/window\.__BJH__INIT__AUTH__\s*=\s*['"]([^'"]+)['"]/);if(!L)return{ok:!1,stage:"auth",status:K.status,message:"登录失效,请重新登录百家号"};const V=L[1],Y=G.content;if(G.draftOnly){const _=new URLSearchParams({title:G.title,content:Y,feed_cat:"1",len:String(Y.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"}),M=await fetch("https://baijiahao.baidu.com/pcui/article/save?callback=bjhdraft",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",token:V},body:_}),A=(await M.text()).replace(/^bjhdraft\(/,"").replace(/\)$/,"");let q=null;try{q=JSON.parse(A)}catch(u){}if(!q||q.errmsg!=="success"||!q.ret||!q.ret.article_id)return{ok:!1,stage:"save",status:M.status,message:q&&q.errmsg||"保存草稿失败"};const H=String(q.ret.article_id),P="https://baijiahao.baidu.com/builder/rc/edit?type=news&article_id="+H;return{ok:!0,id:H,url:P,draft:!0}}if(!z.cover||!String(z.cover).trim())return{ok:!1,stage:"cover",message:"正式发布百家号文章必须提供封面图(--cover)"};const X=String(z.cover);let Z="";try{Z=new URL(X,location.href).hostname}catch(_){}const $=/(^|\.)baijiahao\.baidu\.com$|(^|\.)bdstatic\.com$|(^|\.)bcebos\.com$/.test(Z);let F="";if($)F=X;else try{const _=Z===location.hostname,M=await fetch(X,{credentials:_?"include":"omit"});if(!M.ok)return{ok:!1,stage:"cover",status:M.status,message:"封面图下载失败:"+z.cover};const T=await M.blob();if(/text\/html/i.test(T.type))return{ok:!1,stage:"cover",message:"封面图下载到的是 HTML 页面而非图片(防盗链/登录墙?):"+z.cover};const A=new FormData;A.append("media",T,"cover.jpg");A.append("type","image");A.append("app_id","1589639493090963");A.append("is_waterlog","1");A.append("save_material","1");A.append("no_compress","0");A.append("is_events","");A.append("article_type","news");const H=await(await fetch("https://baijiahao.baidu.com/pcui/picture/uploadproxy",{method:"POST",credentials:"include",body:A})).json();if(H.errmsg!=="success"||!H.ret||!H.ret.https_url)return{ok:!1,stage:"cover",message:"封面图上传失败:"+(H.errmsg||"未知错误")};F=H.ret.https_url}catch(_){return{ok:!1,stage:"cover",message:"封面图处理异常:"+String(_&&_.message||_)}}const O=Y+'<img src="'+F+'"><br>',j=[{src:F,cropData:{x:0,y:0,width:2048,height:1365},machine_chooseimg:0,isLegal:1}],x=[{src:F}],S={type:"news",title:G.title,author:z.author?String(z.author):"",abstract:z.abstract?String(z.abstract):"",content:O,auto_mount_goods:"1",len:String(O.length),vertical_cover:F,cover_images:JSON.stringify(j),_cover_images_map:JSON.stringify(x)},U="&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",y="&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",C=new URLSearchParams(S).toString()+U+y,N=await fetch("https://baijiahao.baidu.com/pcui/article/publish?callback=bjhpublish",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",token:V},body:C}),E=(await N.text()).replace(/^bjhpublish\(/,"").replace(/\)$/,"");let W=null;try{W=JSON.parse(E)}catch(_){}if(!W||W.errno!==0||!W.ret)return{ok:!1,stage:"publish",status:N.status,message:W&&(W.errmsg||W.errno)||"百家号发布失败"};const J=W.ret.url||"";return{ok:!0,id:J,url:J,draft:!1}},checkAuth:async(G)=>{try{const z=await(await fetch("https://baijiahao.baidu.com/builder/app/appinfo?_="+Date.now(),{credentials:"include"})).json();if(z.errmsg==="success"&&z.data&&z.data.user){const K=z.data.user;return{isAuthenticated:!0,userId:String(K.userid||""),username:K.name||"",avatar:K.avatar||""}}return{isAuthenticated:!1}}catch(D){return{isAuthenticated:!1,error:String(D&&D.message||D)}}}};R({site:"baijiahao",name:"article",access:"write",description:"发布百家号文章。默认正式发布(需 --cover 封面),加 --draft 仅存草稿(草稿不需要封面)。正文默认 Markdown,图片自动转存到百家号图床。",domain:"baijiahao.baidu.com",strategy:h.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(G,D)=>{if(!G)throw new I("百家号文章发布需要浏览器会话");w(D);const z=String(D.title??"").trim();if(!z)throw new B("INVALID_INPUT","文章标题不能为空");const K=await f(D),Q=Boolean(D.draft),L=typeof D.cover==="string"?D.cover.trim():"";if(!Q&&!L)throw new B("INVALID_INPUT","正式发布百家号文章必须提供封面图:请传 --cover <图片URL>(或加 --draft 只存草稿)");const V={cover:L,abstract:typeof D.abstract==="string"?D.abstract:"",author:typeof D.author==="string"?D.author:""},Y=await m(G,{title:z,body:K,format:D.html?"html":"markdown",draftOnly:Q,profile:baijiahaoProfile,publishParams:V}),X=Y.images.uploaded.length|0,Z=Y.images.failed.length|0;let $=Y.draft?"已保存百家号文章草稿":"已正式发布百家号文章";if(X||Z)$+=`(图片:${X} 张已转存${Z?`,${Z} 张失败`:""})`;return n($,"article","",Y.draft?"draft":"created",{created_target:"article:"+Y.id,created_url:Y.url})}});
1
+ import{CliError as F,CommandExecutionError as R}from"@jackwener/opencli/errors";import{cli as h,Strategy as P}from"@jackwener/opencli/registry";import{publishArticle as m}from"../_shared/article/publish.js";import{readFile as v,stat as n}from"node:fs/promises";function b(G){if(!G.execute)throw new F("INVALID_INPUT","此百家号写操作需要 --execute 参数才能真正发布")}async function w(G){const D=typeof G.text==="string"?G.text:void 0,z=typeof G.file==="string"?G.file:void 0;if(D&&z)throw new F("INVALID_INPUT","正文和 --file 只能选一个");let K=D??"";if(z){let Q;try{Q=await n(z)}catch{throw new F("INVALID_INPUT",`找不到文件: ${z}`)}if(!Q.isFile())throw new F("INVALID_INPUT",`--file 必须是可读文本文件: ${z}`);let Z;try{Z=await v(z)}catch{throw new F("INVALID_INPUT",`文件无法读取: ${z}`)}try{K=new TextDecoder("utf-8",{fatal:!0}).decode(Z)}catch{throw new F("INVALID_INPUT",`文件必须是 UTF-8 编码: ${z}`)}}if(!K.trim())throw new F("INVALID_INPUT","正文不能为空");return K}function f(G,D,z,K,Q={}){return[{status:"success",outcome:K,message:G,target_type:D,target:z,...Q}]}export const baijiahaoProfile={home:"https://baijiahao.baidu.com",outputFormat:"html",preprocessConfig:{},image:{skip:["baijiahao.baidu.com","bdstatic.com","bcebos.com"],uploadFn:async(G,D)=>{const z=await fetch(G,{credentials:"omit"});if(!z.ok)throw Error("图片下载失败: "+G+" HTTP "+z.status);let K=await z.blob();K=await D.webpToJpeg(K);const Q=new FormData;Q.append("media",K,"image.jpg");Q.append("type","image");Q.append("app_id","1589639493090963");Q.append("is_waterlog","1");Q.append("save_material","1");Q.append("no_compress","0");Q.append("is_events","");Q.append("article_type","news");const X=await(await fetch("https://baijiahao.baidu.com/pcui/picture/uploadproxy",{method:"POST",credentials:"include",body:Q})).json();if(X.errmsg!=="success"||!X.ret||!X.ret.https_url)throw Error(X.errmsg||"图片上传失败");return{url:X.ret.https_url}}},publish:async(G,D)=>{const z=G.params||{},K=await fetch("https://baijiahao.baidu.com/builder/rc/edit",{credentials:"include"}),Z=(await K.text()).match(/window\.__BJH__INIT__AUTH__\s*=\s*['"]([^'"]+)['"]/);if(!Z)return{ok:!1,stage:"auth",status:K.status,message:"登录失效,请重新登录百家号"};const X=Z[1],Y=G.content;if(G.draftOnly){const $=new URLSearchParams({title:G.title,content:Y,feed_cat:"1",len:String(Y.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"}),H=await fetch("https://baijiahao.baidu.com/pcui/article/save?callback=bjhdraft",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",token:X},body:$}),V=(await H.text()).replace(/^bjhdraft\(/,"").replace(/\)$/,"");let A=null;try{A=JSON.parse(V)}catch(d){}if(!A||A.errmsg!=="success"||!A.ret||!A.ret.article_id)return{ok:!1,stage:"save",status:H.status,message:A&&A.errmsg||"保存草稿失败"};const _=String(A.ret.article_id),I="https://baijiahao.baidu.com/builder/rc/edit?type=news&article_id="+_;return{ok:!0,id:_,url:I,draft:!0}}if(!z.cover||!String(z.cover).trim())return{ok:!1,stage:"cover",message:"正式发布百家号文章必须提供封面图(--cover)"};const B=String(z.cover);let q="";try{q=new URL(B,location.href).hostname}catch($){}const O=/(^|\.)baijiahao\.baidu\.com$|(^|\.)bdstatic\.com$|(^|\.)bcebos\.com$/.test(q);let W="";if(O)W=B;else try{const $=q===location.hostname,H=await fetch(B,{credentials:$?"include":"omit"});if(!H.ok)return{ok:!1,stage:"cover",status:H.status,message:"封面图下载失败:"+z.cover};let M=await H.blob();if(/text\/html/i.test(M.type))return{ok:!1,stage:"cover",message:"封面图下载到的是 HTML 页面而非图片(防盗链/登录墙?):"+z.cover};M=await D.webpToJpeg(M);const V=new FormData;V.append("media",M,"cover.jpg");V.append("type","image");V.append("app_id","1589639493090963");V.append("is_waterlog","1");V.append("save_material","1");V.append("no_compress","0");V.append("is_events","");V.append("article_type","news");const _=await(await fetch("https://baijiahao.baidu.com/pcui/picture/uploadproxy",{method:"POST",credentials:"include",body:V})).json();if(_.errmsg!=="success"||!_.ret||!_.ret.https_url)return{ok:!1,stage:"cover",message:"封面图上传失败:"+(_.errmsg||"未知错误")};W=_.ret.https_url}catch($){return{ok:!1,stage:"cover",message:"封面图处理异常:"+String($&&$.message||$)}}const T=Y+'<img src="'+W+'"><br>',J=[{src:W,cropData:{x:0,y:0,width:2048,height:1365},machine_chooseimg:0,isLegal:1}],j=[{src:W}],S={type:"news",title:G.title,author:z.author?String(z.author):"",abstract:z.abstract?String(z.abstract):"",content:T,auto_mount_goods:"1",len:String(T.length),vertical_cover:W,cover_images:JSON.stringify(J),_cover_images_map:JSON.stringify(j)},U="&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",y="&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",C=new URLSearchParams(S).toString()+U+y,N=await fetch("https://baijiahao.baidu.com/pcui/article/publish?callback=bjhpublish",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",token:X},body:C}),E=(await N.text()).replace(/^bjhpublish\(/,"").replace(/\)$/,"");let L=null;try{L=JSON.parse(E)}catch($){}if(!L||L.errno!==0||!L.ret)return{ok:!1,stage:"publish",status:N.status,message:L&&(L.errmsg||L.errno)||"百家号发布失败"};const x=L.ret.url||"";return{ok:!0,id:x,url:x,draft:!1}},checkAuth:async(G)=>{try{const z=await(await fetch("https://baijiahao.baidu.com/builder/app/appinfo?_="+Date.now(),{credentials:"include"})).json();if(z.errmsg==="success"&&z.data&&z.data.user){const K=z.data.user;return{isAuthenticated:!0,userId:String(K.userid||""),username:K.name||"",avatar:K.avatar||""}}return{isAuthenticated:!1}}catch(D){return{isAuthenticated:!1,error:String(D&&D.message||D)}}}};h({site:"baijiahao",name:"article",access:"write",description:"发布百家号文章。默认正式发布(需 --cover 封面),加 --draft 仅存草稿(草稿不需要封面)。正文默认 Markdown,图片自动转存到百家号图床。",domain:"baijiahao.baidu.com",strategy:P.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(G,D)=>{if(!G)throw new R("百家号文章发布需要浏览器会话");b(D);const z=String(D.title??"").trim();if(!z)throw new F("INVALID_INPUT","文章标题不能为空");const K=await w(D),Q=Boolean(D.draft),Z=typeof D.cover==="string"?D.cover.trim():"";if(!Q&&!Z)throw new F("INVALID_INPUT","正式发布百家号文章必须提供封面图:请传 --cover <图片URL>(或加 --draft 只存草稿)");const X={cover:Z,abstract:typeof D.abstract==="string"?D.abstract:"",author:typeof D.author==="string"?D.author:""},Y=await m(G,{title:z,body:K,format:D.html?"html":"markdown",draftOnly:Q,profile:baijiahaoProfile,publishParams:X}),B=Y.images.uploaded.length|0,q=Y.images.failed.length|0;let O=Y.draft?"已保存百家号文章草稿":"已正式发布百家号文章";if(B||q)O+=`(图片:${B} 张已转存${q?`,${q} 张失败`:""})`;return f(O,"article","",Y.draft?"draft":"created",{created_target:"article:"+Y.id,created_url:Y.url})}});
@@ -0,0 +1 @@
1
+ import{cli as W,Strategy as X}from"@jackwener/opencli/registry";import{ArgumentError as K,CommandExecutionError as N}from"@jackwener/opencli/errors";import{apiGet as Q,apiPost as Y,requireOkPayload as L,resolveVideoIds as Z}from"./utils.js";W({site:"bilibili",name:"coin",access:"write",description:"给 B站视频投币(官方 API,需登录;消耗硬币不可撤销,需 --execute)",domain:"www.bilibili.com",strategy:X.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(D,F)=>{if(!D)throw new N("Browser session required for bilibili coin");const h=Number(F.count??1);if(h!==1&&h!==2)throw new K("bilibili coin --count must be 1 or 2");if(!F.execute)throw new K("Refusing to spend coins: pass --execute to actually pay coins");const{bvid:z,aid:H}=await Z(D,F.bvid),M=`https://www.bilibili.com/video/${z}`,R=await Q(D,"/x/web-interface/archive/coins",{params:{aid:H,bvid:z}}),B=Number(L(R,"archive coins")?.multiply??0);if(B>=2)return[{bvid:z,coined:0,total:B,status:"already-maxed",url:M}];if(B+h>2)throw new K(`已投 ${B} 枚,再投 ${h} 枚会超过单视频 2 枚上限;改用 --count ${2-B}`);const T=await Y(D,"/x/web-interface/coin/add",{params:{aid:H,bvid:z,multiply:h,like:F["with-like"]?1:0}});L(T,"coin add");const U=await Q(D,"/x/web-interface/archive/coins",{params:{aid:H,bvid:z}}),J=Number(L(U,"archive coins")?.multiply??0);if(J<B+h)throw new N(`Bilibili coin did not verify: multiply=${J} after paying ${h}`);return[{bvid:z,coined:h,total:J,status:"coined",url:M}]}});
@@ -0,0 +1 @@
1
+ import{cli as H,Strategy as J}from"@jackwener/opencli/registry";import{ArgumentError as B,CommandExecutionError as K}from"@jackwener/opencli/errors";import{apiPost as L,requireOkPayload as M,resolveVideoIds as N}from"./utils.js";function Q(h){const f=Number(h);if(!Number.isSafeInteger(f)||f<=0)throw new B("bilibili comment-del rpid must be a positive integer");return f}H({site:"bilibili",name:"comment-del",access:"write",description:"删除自己发的 B站视频评论(官方 API,需登录;不可恢复,需 --execute)",domain:"www.bilibili.com",strategy:J.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID / URL / b23.tv short link"},{name:"rpid",required:!0,positional:!0,help:"要删除的评论 rpid(comment 命令发布时返回的 rpid)"},{name:"execute",type:"boolean",help:"Actually delete the comment. Without it the command refuses to write."}],columns:["bvid","rpid","status"],func:async(h,f)=>{if(!h)throw new K("Browser session required for bilibili comment-del");const z=Q(f.rpid);if(!f.execute)throw new B("Refusing to delete: pass --execute to actually delete this comment");const{bvid:D,aid:F}=await N(h,f.bvid),G=await L(h,"/x/v2/reply/del",{params:{oid:F,type:1,rpid:z}});M(G,"reply del");return[{bvid:D,rpid:String(z),status:"deleted"}]}});
@@ -0,0 +1 @@
1
+ import{cli as m,Strategy as d}from"@jackwener/opencli/registry";import{ArgumentError as a,CommandExecutionError as p}from"@jackwener/opencli/errors";import{apiPost as s,requireOkPayload as c,resolveVideoIds as b}from"./utils.js";function u(e){const i=Number(e);if(!Number.isSafeInteger(i)||i<=0)throw new a("bilibili comment-like rpid must be a positive integer");return i}m({site:"bilibili",name:"comment-like",access:"write",description:"给 B站视频评论点赞 / 取消点赞(官方 API,需登录;rpid 从 comments 命令取)",domain:"www.bilibili.com",strategy:d.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID / URL / b23.tv short link"},{name:"rpid",required:!0,positional:!0,help:"评论 rpid(bilibili comments 输出里的 id)"},{name:"undo",type:"boolean",help:"取消点赞(默认是点赞)"}],columns:["bvid","rpid","status","url"],func:async(e,i)=>{if(!e)throw new p("Browser session required for bilibili comment-like");const o=u(i.rpid),r=Boolean(i.undo),{bvid:t,aid:n}=await b(e,i.bvid),l=await s(e,"/x/v2/reply/action",{params:{oid:n,type:1,rpid:o,action:r?0:1}});c(l,"reply action");return[{bvid:t,rpid:String(o),status:r?"unliked":"liked",url:`https://www.bilibili.com/video/${t}#reply${o}`}]}});
@@ -0,0 +1 @@
1
+ import{cli as R,Strategy as T}from"@jackwener/opencli/registry";import{CommandExecutionError as U}from"@jackwener/opencli/errors";import{apiGet as V,fetchJson as W,requireOkPayload as X}from"./utils.js";export function parseMsgContent(v){const z=String(v??"");try{const b=JSON.parse(z);if(b&&typeof b==="object"){if(typeof b.content==="string")return b.content;if(typeof b.title==="string"||typeof b.text==="string")return[b.title,b.text].filter(Boolean).join(":");return z}}catch{}return z}R({site:"bilibili",name:"dm-list",access:"read",description:"B站私信会话列表(talker_id 即对方 UID,供 dm-read / dm-send 用)",domain:"www.bilibili.com",strategy:T.COOKIE,args:[{name:"limit",type:"int",default:20,help:"返回会话数上限"}],columns:["talker_id","name","last_content","unread","last_time"],func:async(v,z)=>{if(!v)throw new U("Browser session required for bilibili dm-list");const b=Math.max(1,Number(z.limit??20)),L=new URLSearchParams({session_type:"1",group_fold:"1",unfollow_fold:"0",sort_rule:"2",build:"0",mobi_app:"web"}).toString(),N=await W(v,`https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions?${L}`),Q=(X(N,"dm get_sessions")?.session_list??[]).slice(0,b),B=[];for(const A of Q){const D=String(A?.talker_id??"");let F="";try{F=(await V(v,"/x/web-interface/card",{params:{mid:D}}))?.data?.card?.name??""}catch{}const H=A?.last_msg??{},K=Number(H?.timestamp??0);B.push({talker_id:D,name:F,last_content:parseMsgContent(H?.content).replace(/\s+/g," ").slice(0,80),unread:Number(A?.unread_count??0),last_time:K?new Date(K*1000).toISOString():""})}return B}});
@@ -0,0 +1 @@
1
+ import{cli as T,Strategy as V}from"@jackwener/opencli/registry";import{CommandExecutionError as W,EmptyResultError as X}from"@jackwener/opencli/errors";import{fetchJson as Y,getSelfUid as Z,requireOkPayload as _,resolveUid as $,wbiSign as b}from"./utils.js";import{parseMsgContent as h}from"./dm-list.js";T({site:"bilibili",name:"dm-read",access:"read",description:"读取与某 B站用户的私信记录(近 30 条;target 为 UID / 用户名)",domain:"www.bilibili.com",strategy:V.COOKIE,args:[{name:"target",required:!0,positional:!0,help:"对方 UID / 用户名(dm-list 的 talker_id)"}],columns:["time","direction","sender_uid","content","msg_type"],func:async(z,H)=>{if(!z)throw new W("Browser session required for bilibili dm-read");const D=await $(z,String(H.target??"").trim()),K=await Z(z),L=await b(z,{talker_id:D,session_type:1,size:30,begin_seqno:0}),N=new URLSearchParams(L).toString().replace(/\+/g,"%20"),Q=await Y(z,`https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs?${N}`),F=_(Q,"dm fetch_session_msgs")?.messages??[];if(!F.length)throw new X(`bilibili dm-read: ${D}`,"与该用户还没有私信往来。");return F.slice().reverse().map((B)=>{const G=Number(B?.timestamp??0);return{time:G?new Date(G*1000).toISOString():"",direction:String(B?.sender_uid??"")===K?"sent":"received",sender_uid:String(B?.sender_uid??""),content:h(B?.content).replace(/\s+/g," ").slice(0,200),msg_type:Number(B?.msg_type??0)}})}});
@@ -0,0 +1 @@
1
+ import{cli as T,Strategy as W}from"@jackwener/opencli/registry";import{ArgumentError as J,CommandExecutionError as N}from"@jackwener/opencli/errors";import{apiPostAbs as X,getSelfUid as Y,requireOkPayload as Z,resolveUid as $,wbiSign as f}from"./utils.js";import{assertLiteralContent as h}from"../_shared/content-guard.js";const L="A6716E9A-7CE3-47AF-994B-F0B34178D28D";T({site:"bilibili",name:"dm-send",access:"write",description:"给 B站用户发私信(纯文本,官方 API,需登录;不可撤回,需 --execute)",domain:"www.bilibili.com",strategy:W.COOKIE,args:[{name:"target",required:!0,positional:!0,help:"对方 UID / 用户名(dm-list 的 talker_id)"},{name:"message",required:!0,positional:!0,help:"私信内容(纯文本)"},{name:"execute",type:"boolean",help:"Actually send the message. Without it the command refuses to write."}],columns:["receiver_id","msg_key","status"],func:async(z,F)=>{if(!z)throw new N("Browser session required for bilibili dm-send");const G=String(F.message??"").trim();h(G,{label:"私信内容"});if(!G)throw new J("bilibili dm-send message cannot be empty");if(!F.execute)throw new J("Refusing to send: pass --execute to actually send this message");const B=Number(await $(z,String(F.target??"").trim())),H=Number(await Y(z));if(B===H)throw new J("Cannot send a private message to yourself");const Q=await f(z,{w_sender_uid:H,w_receiver_id:B}),R=await X(z,"https://api.vc.bilibili.com/web_im/v1/web_im/send_msg",{query:Q,params:{"msg[sender_uid]":H,"msg[receiver_id]":B,"msg[receiver_type]":1,"msg[msg_type]":1,"msg[msg_status]":0,"msg[content]":JSON.stringify({content:G}),"msg[dev_id]":L,"msg[new_face_version]":0,"msg[timestamp]":Math.floor(Date.now()/1000),from_filework:0,build:0,mobi_app:"web"}}),M=Z(R,"dm send_msg")?.msg_key;if(!M)throw new N("Bilibili send_msg API did not return msg_key for the sent message");return[{receiver_id:String(B),msg_key:String(M),status:"sent"}]}});
@@ -0,0 +1 @@
1
+ import{cli as G,Strategy as U}from"@jackwener/opencli/registry";import{ArgumentError as X,CommandExecutionError as Q}from"@jackwener/opencli/errors";import{apiGet as V,apiPost as q,getSelfUid as A,requireOkPayload as Y,resolveVideoIds as O}from"./utils.js";async function Z(z,K){const h=await A(z),L=await V(z,"/x/v3/fav/folder/created/list-all",{params:{up_mid:h,type:2,rid:K},signed:!0}),M=Y(L,"fav folder list")?.list??[];if(!Array.isArray(M)||M.length===0)throw new Q("Bilibili fav folder list returned no folders for this account");return M}function _(z,K){const h=String(K??"").trim();if(!h)return z[0];const L=z.find((J)=>String(J.id)===h);if(L)return L;const H=z.filter((J)=>String(J.title??"")===h);if(H.length===1)return H[0];if(H.length>1)throw new X(`收藏夹标题「${h}」有 ${H.length} 个同名,请改用 ID:${H.map((J)=>J.id).join(", ")}`);const M=z.map((J)=>`${J.id}=${J.title}`).join(", ");throw new X(`找不到收藏夹「${h}」;现有收藏夹:${M}`)}G({site:"bilibili",name:"favorite-add",access:"write",description:"把 B站视频加入 / 移出收藏夹(官方 API,需登录;--folder 缺省用默认收藏夹)",domain:"www.bilibili.com",strategy:U.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(z,K)=>{if(!z)throw new Q("Browser session required for bilibili favorite-add");const h=Boolean(K.remove),{bvid:L,aid:H}=await O(z,K.bvid),M=`https://www.bilibili.com/video/${L}`,J=await Z(z,H),N=_(J,K.folder),$=Number(N.fav_state??0)===1,R={bvid:L,folder_id:String(N.id),folder:String(N.title??""),url:M};if($===!h)return[{...R,status:h?"not-in-folder":"already-favorited"}];const B=await q(z,"/x/v3/fav/resource/deal",{params:{rid:H,type:2,add_media_ids:h?"":String(N.id),del_media_ids:h?String(N.id):""}});Y(B,"fav resource deal");const W=(await Z(z,H)).find((D)=>String(D.id)===String(N.id));if(Number(W?.fav_state??0)!==(h?0:1))throw new Q(`Bilibili favorite did not verify: fav_state=${W?.fav_state} after ${h?"remove":"add"}`);return[{...R,status:h?"removed":"favorited"}]}});export const __test__={pickFolder:_};
@@ -0,0 +1 @@
1
+ import{cli as U,Strategy as q}from"@jackwener/opencli/registry";import{CommandExecutionError as X}from"@jackwener/opencli/errors";import{apiGet as A,apiPost as C,requireOkPayload as Z,resolveVideoIds as N}from"./utils.js";const Y=5000,h=500;async function Q(z,H,B){const D=await A(z,"/x/web-interface/archive/relation",{params:{aid:H,bvid:B}});return Z(D,"archive relation")}async function j(z,H,B,D){const K=Date.now()+Y;let J;while(Date.now()<=K){J=Boolean((await Q(z,H,B))?.like);if(J===D)return;if(typeof z.wait!=="function")break;await z.wait({time:h/1000})}throw new X(`Bilibili like did not verify: relation.like=${J}, expected ${D}`)}U({site:"bilibili",name:"like",access:"write",description:"给 B站视频点赞 / 取消点赞(官方 API,需登录;幂等,已点赞再点不会报错)",domain:"www.bilibili.com",strategy:q.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID / URL / b23.tv short link"},{name:"undo",type:"boolean",help:"取消点赞(默认是点赞)"}],columns:["bvid","action","status","url"],func:async(z,H)=>{if(!z)throw new X("Browser session required for bilibili like");const B=Boolean(H.undo),{bvid:D,aid:K}=await N(z,H.bvid),J=`https://www.bilibili.com/video/${D}`,W=B?"unlike":"like",$=await Q(z,K,D);if(Boolean($?.like)===!B)return[{bvid:D,action:W,status:B?"not-liked":"already-liked",url:J}];const G=await C(z,"/x/web-interface/archive/like",{params:{aid:K,like:B?2:1}});Z(G,"archive like");await j(z,K,D,!B);return[{bvid:D,action:W,status:B?"unliked":"liked",url:J}]}});export const __test__={fetchVideoRelation:Q};
@@ -1 +1 @@
1
- import*as u from"node:fs";import*as D from"node:path";import{cli as I,Strategy as E}from"@jackwener/opencli/registry";import{ArgumentError as L,AuthRequiredError as _,CommandExecutionError as B}from"@jackwener/opencli/errors";import{resolveVideoFile as l,resolveImageFile as y,throwIfFileAccessDenied as m}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}},b="bda2",n=["SESSDATA","bili_jct","DedeUserID"],w="https://member.bilibili.com/platform/home",C="__pp_bili_video_input";function d(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{error:"文件未就绪(input 为空)"};var $=J.files[0],Y=$.size,Z=W.line;return async function(){try{var G=new URLSearchParams({profile:"ugcfx/bup",name:$.name,size:String(Y),r:Z.os,ssl:"0",version:"2.14.0",build:"2100400",upcdn:Z.upcdn,probe_version:String(Z.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:\/\//,""),q=new URLSearchParams({uploads:"",output:"json",profile:"ugcfx/bup",filesize:String(Y),partsize:String(X.chunk_size),biz_id:String(X.biz_id)}),V=await(await fetch(K+"?"+q.toString(),{method:"POST",headers:{"x-upos-auth":X.auth}})).json();if(V.OK!==1)return{error:"获取 upload_id 失败:"+JSON.stringify(V)};var T=Math.max(1,Math.ceil(Y/X.chunk_size));return{url:K,auth:X.auth,uploadId:V.upload_id,chunkSize:X.chunk_size,bizId:X.biz_id,size:Y,name:$.name,total:T}}catch(F){return{error:"preupload 异常:"+String(F&&F.message||F)}}}()}function c(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{ok:!1,error:"文件丢失(页面可能已导航)"};var $=J.files[0],Y=W.idx*W.chunkSize,Z=$.slice(Y,Math.min(Y+W.chunkSize,W.size)),G=Z.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 q=await fetch(W.url+"?"+X.toString(),{method:"PUT",headers:{"x-upos-auth":W.auth},body:Z});if(q.status<400){var V=await q.text();if(V==="MULTIPART_PUT_SUCCESS"||V==="")return{ok:!0}}}catch(T){}await new Promise(function(T){setTimeout(T,800*(K+1))})}return{ok:!1,error:"分块 "+W.idx+" 多次重试仍失败"}}()}function k(W){var J=[];for(var $=1;$<=W.total;$++)J.push({partNumber:$,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 Z=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(Z.OK!==1)return{error:"complete 失败:"+JSON.stringify(Z)};var G=String(Z.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"}),$=await J.json();if(!$||$.code!==0||!$.data||!Array.isArray($.data.topics))return{error:"获取话题列表失败:"+($&&$.message||"code="+($&&$.code))};var Y=null;for(var Z=0;Z<$.data.topics.length;Z++)if(Number($.data.topics[Z].topic_id)===Number(W.topicId)){Y=$.data.topics[Z];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 $=decodeURIComponent(J[1]);try{var Y=new URLSearchParams;Y.append("cover",W.dataUri);Y.append("csrf",$);var Z=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:Y.toString()})).json();if(!Z||Z.code!==0||!Z.data||!Z.data.url)return{error:"封面上传失败:"+(Z&&Z.message?Z.message:JSON.stringify(Z))};return{url:Z.data.url}}catch(G){return{error:"封面上传异常:"+String(G&&G.message||G)}}}()}function p(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var $=decodeURIComponent(J[1]),Y=W.meta;for(var Z in Y)if(Y[Z]===null||Y[Z]===void 0)delete Y[Z];Y.csrf=$;try{var G=await fetch("https://member.bilibili.com/x/vu/web/add/v3?csrf="+encodeURIComponent($)+"&t="+Date.now(),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),X=await G.json();if(!X||X.code!==0)return{error:"add/v3 提交失败:"+(X&&X.message?X.message:"")+"(code="+(X&&X.code)+" status="+G.status+")"};return{bvid:X.data&&X.data.bvid,aid:X.data&&X.data.aid}}catch(K){return{error:"add/v3 异常:"+String(K&&K.message||K)}}}()}function s(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var $=decodeURIComponent(J[1]),Y=W.body;Y.csrf=$;try{var Z=await fetch("https://member.bilibili.com/x/vupre/web/draft/add?t="+Date.now()+"&csrf="+encodeURIComponent($),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),G=await Z.json();if(!G||G.code!==0)return{error:"draft/add 保存草稿失败:"+(G&&G.message?G.message:"")+"(code="+(G&&G.code)+" status="+Z.status+")"};return{data:G.data}}catch(X){return{error:"draft/add 异常:"+String(X&&X.message||X)}}}()}const R=(W,J,$)=>W.evaluateWithArgs(`(${J.toString()})(a)`,{a:$});function t(W){const J=y(W),$=D.extname(J).toLowerCase(),Y=$===".png"?"image/png":$===".webp"?"image/webp":$===".bmp"?"image/bmp":"image/jpeg",Z=u.readFileSync(J).toString("base64");return`data:${Y};base64,${Z}`}async function i(W){for(let J=0;J<8;J++){await W.goto(w);await W.wait({time:1});let $="";try{$=String(await W.evaluate("location.href")||"")}catch{$=""}if(/^https?:\/\/member\.bilibili\.com\//.test($)){const Y=await W.getCookies({url:"https://member.bilibili.com"}),Z=new Map;for(const X of Array.isArray(Y)?Y:[])if(X&&typeof X.name==="string")Z.set(X.name,String(X.value??""));const G=n.filter((X)=>!Z.get(X));if(!G.length)return Z;throw new _("member.bilibili.com",`缺少 B站登录 cookie(${G.join(", ")})。请先在客户端登录哔哩哔哩。`)}}throw new _("member.bilibili.com","无法进入 B站创作中心(可能未登录或被风控)。请先在客户端登录哔哩哔哩。")}I({site:"bilibili",name:"upload",access:"write",description:"投稿视频到 B站(在真实浏览器登录态里走 web 投稿:preupload→upos 分块→add/v3)。默认仅 dry-run 校验,加 --execute 才真正上传并提交。",domain:"member.bilibili.com",strategy:E.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("/")}(默认 ${b})`},{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 B("bilibili upload 需要浏览器会话");const $=l(String(J.file??"")),Y=String(J.title??"").trim()||D.basename($).replace(/\.[^.]+$/,""),Z=String(J.tag??"").split(",").map((Q)=>Q.trim()).filter(Boolean);if(!Z.length)throw new L("B站投稿至少需要 1 个标签,用 --tag 传(逗号分隔)");if(J.tid==null||J.tid===""||!Number.isFinite(Number(J.tid)))throw new L("必须用 --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 L("--copyright 只能是 1(自制) 或 2(转载)");const K=String(J.source??"").trim();if(X===2&&!K)throw new L("转载(copyright=2)必须用 --source 提供转载来源");if(J.cover)y(String(J.cover));const q=String(J.line??b),V=U[q];if(!V)throw new L(`未知上传线路「${q}」,可选:${Object.keys(U).join("/")}`);const T=J.concurrency!=null&&J.concurrency!==""?Number(J.concurrency):3,F=Boolean(J.draft),P=Boolean(J.execute)&&!F;await i(W);if(!W.setFileInput)throw new B("浏览器扩展不支持 CDP 文件上传(set-file-input),无法上传视频;请升级 PublishPort 客户端/扩展");await W.evaluate(`(() => { var id=${JSON.stringify(C)}; 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([$],`#${C}`)}catch(Q){m(Q);throw Q}if(!F&&!P)return{status:"dry-run",title:Y,bvid:"",url:`校验通过:登录态有效、视频已注入就绪、分区 ${G}、标签 [${Z.join(", ")}]、版权 ${X===1?"自制":"转载"}。加 --execute 发布,或 --draft 存草稿。`};const z=await R(W,d,{inputId:C,line:V});if(!z||z.error)throw new B(`B站 preupload 失败:${z&&z.error||"无返回"}`);for(let Q=0;Q<z.total;Q++){const M=await R(W,c,{inputId:C,url:z.url,auth:z.auth,uploadId:z.uploadId,idx:Q,total:z.total,size:z.size,chunkSize:z.chunkSize});if(!M||!M.ok)throw new B(`B站分块上传失败(${Q+1}/${z.total}):${M&&M.error||"无返回"}`)}const H=await R(W,k,{url:z.url,auth:z.auth,uploadId:z.uploadId,bizId:z.bizId,name:z.name,total:z.total});if(!H||H.error||!H.filename)throw new B(`B站 complete 失败:${H&&H.error||"未拿到 filename"}`);let N="";if(J.cover){const Q=t(String(J.cover)),M=await R(W,o,{dataUri:Q});if(!M||M.error||!M.url)throw new B(`B站封面上传失败:${M&&M.error||"未拿到 url"}`);N=M.url}let O=null,A=null;if(J.topic!=null&&J.topic!==""){if(!Number.isFinite(Number(J.topic)))throw new L("--topic 必须是 topic_id 数字,用 `bilibili topics --tid <tid>` 取合法值");const Q=await R(W,j,{tid:G,topicId:Number(J.topic)});if(!Q||Q.error)throw new B(`B站话题解析失败:${Q&&Q.error||"无返回"}`);O=Q.topicId;A=Q.missionId}if(F){const Q={videos:[{filename:H.filename,title:Y,desc:"",cid:H.cid,is_4k:!1,is_8k:!1,is_hdr:!1}],cover:N,cover43:N,ai_cover:0,is_ab_cover:0,ab_cover_info:null,title:Y,copyright:X,tid:G,tag:Z.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(O!=null){Q.topic_id=O;Q.topic_detail={from_topic_id:O,from_source:"arc.web.search"};if(A!=null)Q.mission_id=A}const M=await R(W,s,{body:Q});if(!M||M.error)throw new B(`B站 ${M&&M.error||"draft/add 无返回"}`);const v=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:v!=null?`draft:${v}`:"",url:"https://member.bilibili.com/platform/upload-manager/article?group=draft"}}const h=J.dtime!=null&&J.dtime!==""?Number(J.dtime):null,f={title:Y,copyright:X,tid:G,tag:Z.join(","),mission_id:A,topic_id:O,topic_detail:O!=null?{from_topic_id:O,from_source:"arc.web.recommend"}:null,desc_format_id:9999,desc:String(J.desc??""),dtime:h,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:N,videos:[{title:Y,desc:"",filename:H.filename,cid:H.cid}]},S=await R(W,p,{meta:f});if(!S||S.error)throw new B(`B站 ${S&&S.error||"add/v3 无返回"}`);const x=S.bvid?String(S.bvid):"";return{status:J.dtime?"✅ 定时投稿已提交":"✅ 投稿成功",title:Y,bvid:x,url:x?`https://www.bilibili.com/video/${x}`:"(已提交,稍后在创作中心查看)"}}});
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,19 +1,19 @@
1
- import f from"node:https";import{ArgumentError as X,AuthRequiredError as M,CommandExecutionError as L,EmptyResultError as B}from"@jackwener/opencli/errors";export function resolveBvid(z){const Q=String(z).trim();if(/^BV[A-Za-z0-9]+$/i.test(Q))return Promise.resolve(Q);try{const $=new URL(Q);if(/(\.|^)bilibili\.com$/i.test($.hostname)){const Z=$.pathname.match(/\/(?:video|bangumi\/play)\/(BV[A-Za-z0-9]+)/i);if(Z)return Promise.resolve(Z[1])}}catch{}const V=Q.replace(/^https?:\/\//,"").replace(/^(www\.)?b23\.tv\//,"");if(!/^[A-Za-z0-9]+$/.test(V))return Promise.reject(Error(`Cannot resolve BV ID from invalid b23.tv short code: ${Q}`));const F="https://b23.tv/"+V;return new Promise(($,Z)=>{const G=f.get(F,(H)=>{const O=H.headers.location;if(O){const W=O.match(/\/video\/(BV[A-Za-z0-9]+)/);if(W){H.resume();$(W[1]);return}}H.resume();Z(Error(`Cannot resolve BV ID from short URL: ${Q}`))});G.on("error",Z);G.setTimeout(4000,()=>{G.destroy();Z(Error(`Timeout resolving short URL: ${Q}`))})})}export function parsePageArg(z){if(z==null||z==="")return null;if(typeof z==="number"){if(Number.isSafeInteger(z)&&z>=1)return z;throw new X(`--page must be a positive decimal integer, got: ${z}`)}if(typeof z!=="string"||!/^[1-9]\d*$/.test(z))throw new X(`--page must be a positive decimal integer, got: ${String(z)}`);const Q=Number(z);if(!Number.isSafeInteger(Q))throw new X(`--page is too large: ${z}`);return Q}function T(z,Q){if(typeof z==="number"&&Number.isSafeInteger(z)&&z>=1)return z;if(typeof z==="string"&&/^[1-9]\d*$/.test(z)){const V=Number(z);if(Number.isSafeInteger(V))return V}throw new L(`Bilibili view API returned a malformed ${Q}`)}export function selectVideoPart(z,Q){const V=Array.isArray(z?.pages)?z.pages:null;if(!V||V.length===0)throw new L("Bilibili view API did not return pages[] for --page selection");const F=[];for(const Z of V){if(!Z||typeof Z!=="object"||Array.isArray(Z))throw new L("Bilibili view API returned a malformed pages[] entry");if(T(Z.page,"page number")===Q)F.push(Z)}if(F.length>1)throw new L(`Bilibili view API returned duplicate page entries for p=${Q}`);const $=F[0];if(!$){const Z=V.length||z?.videos||1;throw new L(`分P 序号超出范围:p=${Q}(该视频共 ${Z} 集)`)}T($.cid,`cid for p=${Q}`);return $}const J=[46,47,18,2,53,8,23,32,15,50,10,31,58,3,45,35,27,43,5,49,33,9,42,19,29,28,14,39,12,38,41,13,37,48,7,16,24,55,40,61,26,17,0,1,60,51,30,4,22,25,54,21,56,59,6,63,57,62,11,36,20,34,44,52];export function stripHtml(z){return z.replace(/<[^>]+>/g,"").replace(/&[a-z]+;/gi," ").trim()}export function payloadData(z){return z?.data??z}async function U(z){return z.evaluate(`
1
+ import B from"node:https";import{ArgumentError as G,AuthRequiredError as L,CommandExecutionError as W,EmptyResultError as _}from"@jackwener/opencli/errors";export function resolveBvid(z){const F=String(z).trim();if(/^BV[A-Za-z0-9]+$/i.test(F))return Promise.resolve(F);try{const Z=new URL(F);if(/(\.|^)bilibili\.com$/i.test(Z.hostname)){const V=Z.pathname.match(/\/(?:video|bangumi\/play)\/(BV[A-Za-z0-9]+)/i);if(V)return Promise.resolve(V[1])}}catch{}const Q=F.replace(/^https?:\/\//,"").replace(/^(www\.)?b23\.tv\//,"");if(!/^[A-Za-z0-9]+$/.test(Q))return Promise.reject(Error(`Cannot resolve BV ID from invalid b23.tv short code: ${F}`));const $="https://b23.tv/"+Q;return new Promise((Z,V)=>{const H=B.get($,(O)=>{const T=O.headers.location;if(T){const X=T.match(/\/video\/(BV[A-Za-z0-9]+)/);if(X){O.resume();Z(X[1]);return}}O.resume();V(Error(`Cannot resolve BV ID from short URL: ${F}`))});H.on("error",V);H.setTimeout(4000,()=>{H.destroy();V(Error(`Timeout resolving short URL: ${F}`))})})}export function parsePageArg(z){if(z==null||z==="")return null;if(typeof z==="number"){if(Number.isSafeInteger(z)&&z>=1)return z;throw new G(`--page must be a positive decimal integer, got: ${z}`)}if(typeof z!=="string"||!/^[1-9]\d*$/.test(z))throw new G(`--page must be a positive decimal integer, got: ${String(z)}`);const F=Number(z);if(!Number.isSafeInteger(F))throw new G(`--page is too large: ${z}`);return F}function M(z,F){if(typeof z==="number"&&Number.isSafeInteger(z)&&z>=1)return z;if(typeof z==="string"&&/^[1-9]\d*$/.test(z)){const Q=Number(z);if(Number.isSafeInteger(Q))return Q}throw new W(`Bilibili view API returned a malformed ${F}`)}export function selectVideoPart(z,F){const Q=Array.isArray(z?.pages)?z.pages:null;if(!Q||Q.length===0)throw new W("Bilibili view API did not return pages[] for --page selection");const $=[];for(const V of Q){if(!V||typeof V!=="object"||Array.isArray(V))throw new W("Bilibili view API returned a malformed pages[] entry");if(M(V.page,"page number")===F)$.push(V)}if($.length>1)throw new W(`Bilibili view API returned duplicate page entries for p=${F}`);const Z=$[0];if(!Z){const V=Q.length||z?.videos||1;throw new W(`分P 序号超出范围:p=${F}(该视频共 ${V} 集)`)}M(Z.cid,`cid for p=${F}`);return Z}const R=[46,47,18,2,53,8,23,32,15,50,10,31,58,3,45,35,27,43,5,49,33,9,42,19,29,28,14,39,12,38,41,13,37,48,7,16,24,55,40,61,26,17,0,1,60,51,30,4,22,25,54,21,56,59,6,63,57,62,11,36,20,34,44,52];export function stripHtml(z){return z.replace(/<[^>]+>/g,"").replace(/&[a-z]+;/gi," ").trim()}export function payloadData(z){return z?.data??z}async function S(z){return z.evaluate(`
2
2
  async () => {
3
3
  const res = await fetch('https://api.bilibili.com/x/web-interface/nav', { credentials: 'include' });
4
4
  return await res.json();
5
5
  }
6
- `)}async function S(z){const V=(await U(z))?.data?.wbi_img??{},F=V.img_url??"",$=V.sub_url??"",Z=F.split("/").pop()?.split(".")[0]??"",G=$.split("/").pop()?.split(".")[0]??"";return{imgKey:Z,subKey:G}}function _(z,Q){const V=z+Q;return J.map((F)=>V[F]||"").join("").slice(0,32)}async function P(z){const{createHash:Q}=await import("node:crypto");return Q("md5").update(z).digest("hex")}export async function wbiSign(z,Q){const{imgKey:V,subKey:F}=await S(z),$=_(V,F),Z=Math.floor(Date.now()/1000),G={},H={...Q,wts:String(Z)};for(const Y of Object.keys(H).sort())G[Y]=String(H[Y]).replace(/[!'()*]/g,"");const O=new URLSearchParams(G).toString().replace(/\+/g,"%20"),W=await P(O+$);G.w_rid=W;return G}export async function apiGet(z,Q,V={}){const F="https://api.bilibili.com";let $=V.params??{};if(V.signed)$=await wbiSign(z,$);const Z=new URLSearchParams(Object.fromEntries(Object.entries($).map(([H,O])=>[H,String(O)]))).toString().replace(/\+/g,"%20"),G=`${F}${Q}?${Z}`;return fetchJson(z,G)}export async function fetchJson(z,Q){const V=JSON.stringify(Q);return z.evaluate(`
6
+ `)}async function f(z){const Q=(await S(z))?.data?.wbi_img??{},$=Q.img_url??"",Z=Q.sub_url??"",V=$.split("/").pop()?.split(".")[0]??"",H=Z.split("/").pop()?.split(".")[0]??"";return{imgKey:V,subKey:H}}function J(z,F){const Q=z+F;return R.map(($)=>Q[$]||"").join("").slice(0,32)}async function P(z){const{createHash:F}=await import("node:crypto");return F("md5").update(z).digest("hex")}export async function wbiSign(z,F){const{imgKey:Q,subKey:$}=await f(z),Z=J(Q,$),V=Math.floor(Date.now()/1000),H={},O={...F,wts:String(V)};for(const Y of Object.keys(O).sort())H[Y]=String(O[Y]).replace(/[!'()*]/g,"");const T=new URLSearchParams(H).toString().replace(/\+/g,"%20"),X=await P(T+Z);H.w_rid=X;return H}export async function apiGet(z,F,Q={}){const $="https://api.bilibili.com";let Z=Q.params??{};if(Q.signed)Z=await wbiSign(z,Z);const V=new URLSearchParams(Object.fromEntries(Object.entries(Z).map(([O,T])=>[O,String(T)]))).toString().replace(/\+/g,"%20"),H=`${$}${F}?${V}`;return fetchJson(z,H)}export async function fetchJson(z,F){const Q=JSON.stringify(F);return z.evaluate(`
7
7
  async () => {
8
- const res = await fetch(${V}, { credentials: "include" });
8
+ const res = await fetch(${Q}, { credentials: "include" });
9
9
  return await res.json();
10
10
  }
11
- `)}export function isAuthLikeBilibiliError(z,Q){return z===-101||z===-111||z===-403||/csrf|登录|账号|权限|forbidden|permission|login/i.test(String(Q??""))}export function requireOkPayload(z,Q){if(!z||typeof z!=="object"||Array.isArray(z)||!Object.hasOwn(z,"code"))throw new L(`Bilibili ${Q} API returned a malformed payload`);if(z.code!==0){const V=z.message??"unknown error";if(isAuthLikeBilibiliError(z.code,V))throw new M("bilibili.com",`Bilibili ${Q} API requires login or permission: ${V} (${z.code})`);throw new L(`Bilibili ${Q} API failed: ${V} (${z.code})`)}return z.data}export async function apiPost(z,Q,V={}){const F=V.params??{},$=Object.fromEntries(Object.entries(F).map(([H,O])=>[H,String(O)])),Z=JSON.stringify($),G=JSON.stringify(`https://api.bilibili.com${Q}`);return z.evaluate(`
11
+ `)}export function isAuthLikeBilibiliError(z,F){return z===-101||z===-111||z===-403||/csrf|登录|账号|权限|forbidden|permission|login/i.test(String(F??""))}export function requireOkPayload(z,F){if(!z||typeof z!=="object"||Array.isArray(z)||!Object.hasOwn(z,"code"))throw new W(`Bilibili ${F} API returned a malformed payload`);if(z.code!==0){const Q=z.message??"unknown error";if(isAuthLikeBilibiliError(z.code,Q))throw new L("bilibili.com",`Bilibili ${F} API requires login or permission: ${Q} (${z.code})`);throw new W(`Bilibili ${F} API failed: ${Q} (${z.code})`)}return z.data}export async function apiPost(z,F,Q={}){const $=Q.params??{},Z=Object.fromEntries(Object.entries($).map(([O,T])=>[O,String(T)])),V=JSON.stringify(Z),H=JSON.stringify(`https://api.bilibili.com${F}`);return z.evaluate(`
12
12
  async () => {
13
13
  const csrf = (document.cookie.match(/bili_jct=([^;]+)/) || [])[1] || "";
14
- const body = new URLSearchParams(${Z});
14
+ const body = new URLSearchParams(${V});
15
15
  body.set("csrf", csrf);
16
- const res = await fetch(${G}, {
16
+ const res = await fetch(${H}, {
17
17
  method: "POST",
18
18
  credentials: "include",
19
19
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
@@ -28,4 +28,23 @@ import f from"node:https";import{ArgumentError as X,AuthRequiredError as M,Comma
28
28
  return { code: -1, message: "Non-JSON response (HTTP " + res.status + "): " + text.slice(0, 200) };
29
29
  }
30
30
  }
31
- `)}export async function getSelfUid(z){const V=(await U(z))?.data?.mid;if(!V)throw new M("bilibili.com");return String(V)}export async function resolveUid(z,Q){if(/^\d+$/.test(Q))return Q;const V=await apiGet(z,"/x/web-interface/wbi/search/type",{params:{search_type:"bili_user",keyword:Q},signed:!0});if(!V||typeof V!=="object"||Array.isArray(V)||!V.data||typeof V.data!=="object"||Array.isArray(V.data)||!Object.hasOwn(V.data,"result"))throw new L(`Bilibili user search returned malformed result for ${Q}`);const F=V.data.result;if(!Array.isArray(F))throw new L(`Bilibili user search returned malformed result for ${Q}`);if(F.length>0){const $=String(F[0]?.mid??"").trim();if(!$)throw new L(`Bilibili user search returned malformed mid for ${Q}`);return $}throw new B(`bilibili user search: ${Q}`,"User may not exist or username may have changed.")}
31
+ `)}export async function apiPostAbs(z,F,Q={}){const $=Q.params??{},Z=Object.fromEntries(Object.entries($).map(([T,X])=>[T,String(X)])),V=JSON.stringify(Z);let H=F;if(Q.query&&Object.keys(Q.query).length>0){const T=new URLSearchParams(Object.fromEntries(Object.entries(Q.query).map(([X,Y])=>[X,String(Y)]))).toString().replace(/\+/g,"%20");H=`${F}?${T}`}const O=JSON.stringify(H);return z.evaluate(`
32
+ async () => {
33
+ const csrf = (document.cookie.match(/bili_jct=([^;]+)/) || [])[1] || "";
34
+ const body = new URLSearchParams(${V});
35
+ body.set("csrf", csrf);
36
+ body.set("csrf_token", csrf);
37
+ const res = await fetch(${O}, {
38
+ method: "POST",
39
+ credentials: "include",
40
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
41
+ body: body.toString(),
42
+ });
43
+ const text = await res.text();
44
+ try {
45
+ return JSON.parse(text);
46
+ } catch {
47
+ return { code: -1, message: "Non-JSON response (HTTP " + res.status + "): " + text.slice(0, 200) };
48
+ }
49
+ }
50
+ `)}export async function resolveVideoIds(z,F){let Q;try{Q=await resolveBvid(F)}catch(H){throw new G(`Cannot resolve Bilibili BV ID from input: ${String(F??"")}`,H instanceof Error?H.message:String(H))}const $=await apiGet(z,"/x/web-interface/view",{params:{bvid:Q}}),V=requireOkPayload($,"view")?.aid;if(!V)throw new W(`Cannot resolve aid for bvid: ${Q}`);return{bvid:Q,aid:Number(V)}}export async function getSelfUid(z){const Q=(await S(z))?.data?.mid;if(!Q)throw new L("bilibili.com");return String(Q)}export async function resolveUid(z,F){if(/^\d+$/.test(F))return F;const Q=await apiGet(z,"/x/web-interface/wbi/search/type",{params:{search_type:"bili_user",keyword:F},signed:!0});if(!Q||typeof Q!=="object"||Array.isArray(Q)||!Q.data||typeof Q.data!=="object"||Array.isArray(Q.data)||!Object.hasOwn(Q.data,"result"))throw new W(`Bilibili user search returned malformed result for ${F}`);const $=Q.data.result;if(!Array.isArray($))throw new W(`Bilibili user search returned malformed result for ${F}`);if($.length>0){const Z=String($[0]?.mid??"").trim();if(!Z)throw new W(`Bilibili user search returned malformed mid for ${F}`);return Z}throw new _(`bilibili user search: ${F}`,"User may not exist or username may have changed.")}
@@ -1 +1 @@
1
- import{CliError as Z,CommandExecutionError as O}from"@jackwener/opencli/errors";import{cli as _,Strategy as j}from"@jackwener/opencli/registry";import{publishArticle as S}from"../_shared/article/publish.js";export const cnblogsProfile={home:"https://i.cnblogs.com/posts/edit",outputFormat:"markdown",image:{uploadFn:async(K,z)=>{const G=z.cookie("XSRF-TOKEN");if(!G)throw Error("未找到 XSRF-TOKEN cookie,请确认已登录博客园");const Q=await fetch(K,{credentials:"omit"});if(!Q.ok)throw Error("图片下载失败: "+K+"(HTTP "+Q.status+")");const Y=await Q.blob(),H=new FormData;H.append("image",Y,"image.png");H.append("app","blog");H.append("uploadType","Select");const X=await fetch("https://upload.cnblogs.com/v2/images/cors-upload",{method:"POST",credentials:"include",headers:{"x-xsrf-token":G},body:H}),W=await X.text();if(!X.ok)throw Error("图片上传失败: HTTP "+X.status+" "+W.slice(0,150));let V=null;try{V=JSON.parse(W)}catch(q){}if(!V)throw Error("图片上传失败: 响应不是 JSON - "+W.slice(0,100));const $=V.data||V.url||V.imageUrl||V.src;if(!$||typeof $!=="string")throw Error("图片上传失败: 无法解析图片 URL - "+JSON.stringify(V).slice(0,150));return{url:$}},skip:["cnblogs.com"]},publish:async(K,z)=>{const G=z.cookie("XSRF-TOKEN");if(!G)return{ok:!1,stage:"auth",status:401,message:"未找到 XSRF-TOKEN cookie,请确认已登录博客园并在 i.cnblogs.com 下访问过"};const Q={"Content-Type":"application/json","x-xsrf-token":G},Y={id:null,postType:2,accessPermission:0,title:K.title,url:null,postBody:K.content,categoryIds:null,categories:null,collectionIds:[],inSiteCandidate:!1,inSiteHome:!1,siteCategoryId:null,blogTeamIds:null,isPublished:!1,displayOnHomePage:!1,isAllowComments:!0,includeInMainSyndication:!1,isPinned:!1,showBodyWhenPinned:!1,isOnlyForRegisterUser:!1,isUpdateDateAdded:!1,entryName:null,description:null,featuredImage:null,tags:null,password:null,publishAt:null,datePublished:new Date().toISOString(),dateUpdated:null,isMarkdown:!0,isDraft:!0,autoDesc:null,changePostType:!1,blogId:0,author:null,removeScript:!1,clientInfo:null,changeCreatedTime:!1,canChangeCreatedTime:!1,isContributeToImpressiveBugActivity:!1,usingEditorId:5,sourceUrl:null},H=await fetch("https://i.cnblogs.com/api/posts",{method:"POST",credentials:"include",headers:Q,body:JSON.stringify(Y)}),X=await H.text();if(!H.ok)return{ok:!1,stage:"create",status:H.status,message:X.slice(0,300)};let W=null;try{W=JSON.parse(X)}catch(L){}if(!W||!W.id)return{ok:!1,stage:"create",status:H.status,message:"创建草稿失败: "+X.slice(0,300)};const V=String(W.id),$="https://i.cnblogs.com/articles/edit;postId="+V;if(K.draftOnly)return{ok:!0,draft:!0,id:V,url:$};const q=await fetch("https://i.cnblogs.com/api/posts/"+V,{method:"PATCH",credentials:"include",headers:Q,body:JSON.stringify({isPublished:!0,isDraft:!1,displayOnHomePage:!0})}),B=await q.text();if(!q.ok)return{ok:!1,stage:"publish",status:q.status,message:B.slice(0,300),id:V};let A=null;try{A=JSON.parse(B)}catch(L){}const F=A&&A.url||"https://www.cnblogs.com/?id="+V;return{ok:!0,draft:!1,id:V,url:F}}},cnblogsAuthProfile={home:"https://home.cnblogs.com",checkAuth:async(K)=>{try{const z=await fetch("https://home.cnblogs.com/user/CurrentUserInfo",{method:"GET",credentials:"include"});if(!z.ok)return{isAuthenticated:!1,error:"HTTP "+z.status};const G=await z.text(),Q=G.match(/href="\/u\/([^/]+)\/"/),Y=G.match(/<img[^>]+class="pfs"[^>]+src="([^"]+)"/);if(!Q)return{isAuthenticated:!1};const H=Q[1];return{isAuthenticated:!0,userId:H,username:H,avatar:Y?Y[1]:""}}catch(z){return{isAuthenticated:!1,error:String(z&&z.message||z)}}}};function M(K){if(!K.execute)throw new Z("INVALID_INPUT","此命令需要 --execute 参数才会实际写入博客园")}async function N(K){const z=typeof K.text==="string"?K.text:void 0,G=typeof K.file==="string"?K.file:void 0;if(z&&G)throw new Z("INVALID_INPUT","不能同时使用 <text> 和 --file,选其一即可");let Q=z??"";if(G){const{readFile:Y,stat:H}=await import("node:fs/promises");let X;try{X=await H(G)}catch{throw new Z("INVALID_INPUT","文件不存在: "+G)}if(!X.isFile())throw new Z("INVALID_INPUT","必须是可读文本文件: "+G);let W;try{W=await Y(G)}catch{throw new Z("INVALID_INPUT","文件读取失败: "+G)}try{Q=new TextDecoder("utf-8",{fatal:!0}).decode(W)}catch{throw new Z("INVALID_INPUT","文件不是有效 UTF-8 编码: "+G)}}if(!Q.trim())throw new Z("INVALID_INPUT","正文不能为空");return Q}function U(K,z,G,Q,Y={}){return[{status:"success",outcome:Q,message:K,target_type:z,target:G,...Y}]}_({site:"cnblogs",name:"article",access:"write",description:"发布博客园文章(Markdown)。正文默认 Markdown;图片自动转存到博客园图床。",domain:"cnblogs.com",strategy:j.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:"draft",type:"boolean",help:"仅保存草稿,不发布"},{name:"execute",type:"boolean",help:"实际执行写入。不加此参数命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(K,z)=>{if(!K)throw new O("博客园文章发布需要浏览器会话");M(z);const G=String(z.title??"").trim();if(!G)throw new Z("INVALID_INPUT","文章标题不能为空");const Q=await N(z),Y=Boolean(z.draft),H=await S(K,{title:G,body:Q,format:z.html?"html":"markdown",draftOnly:Y,profile:cnblogsProfile}),X=H.images.uploaded.length|0,W=H.images.failed.length|0;let V=H.draft?"已保存博客园草稿":"已发布博客园文章";if(X||W)V+="·图片: "+X+" 张已转存"+(W?","+W+" 张失败":"");return U(V,"article","",H.draft?"draft":"created",{created_target:"article:"+H.id,created_url:H.url})}});
1
+ import{CliError as Z,CommandExecutionError as M}from"@jackwener/opencli/errors";import{cli as N,Strategy as v}from"@jackwener/opencli/registry";import{publishArticle as U}from"../_shared/article/publish.js";export const cnblogsProfile={home:"https://i.cnblogs.com/posts/edit",outputFormat:"markdown",image:{uploadFn:async(K,z)=>{const G=z.cookie("XSRF-TOKEN");if(!G)throw Error("未找到 XSRF-TOKEN cookie,请确认已登录博客园");const Q=await fetch(K,{credentials:"omit"});if(!Q.ok)throw Error("图片下载失败: "+K+"(HTTP "+Q.status+")");const Y=await Q.blob(),H=new FormData;H.append("image",Y,"image.png");H.append("app","blog");H.append("uploadType","Select");const X=await fetch("https://upload.cnblogs.com/v2/images/cors-upload",{method:"POST",credentials:"include",headers:{"x-xsrf-token":G},body:H}),W=await X.text();if(!X.ok)throw Error("图片上传失败: HTTP "+X.status+" "+W.slice(0,150));let V=null;try{V=JSON.parse(W)}catch(q){}if(!V)throw Error("图片上传失败: 响应不是 JSON - "+W.slice(0,100));const $=V.data||V.url||V.imageUrl||V.src;if(!$||typeof $!=="string")throw Error("图片上传失败: 无法解析图片 URL - "+JSON.stringify(V).slice(0,150));return{url:$}},skip:["cnblogs.com"]},publish:async(K,z)=>{const G=z.cookie("XSRF-TOKEN");if(!G)return{ok:!1,stage:"auth",status:401,message:"未找到 XSRF-TOKEN cookie,请确认已登录博客园并在 i.cnblogs.com 下访问过"};const Q={"Content-Type":"application/json","x-xsrf-token":G},Y={id:null,postType:2,accessPermission:0,title:K.title,url:null,postBody:K.content,categoryIds:null,categories:null,collectionIds:[],inSiteCandidate:!1,inSiteHome:!1,siteCategoryId:null,blogTeamIds:null,isPublished:!1,displayOnHomePage:!1,isAllowComments:!0,includeInMainSyndication:!1,isPinned:!1,showBodyWhenPinned:!1,isOnlyForRegisterUser:!1,isUpdateDateAdded:!1,entryName:null,description:null,featuredImage:null,tags:null,password:null,publishAt:null,datePublished:new Date().toISOString(),dateUpdated:null,isMarkdown:!0,isDraft:!0,autoDesc:null,changePostType:!1,blogId:0,author:null,removeScript:!1,clientInfo:null,changeCreatedTime:!1,canChangeCreatedTime:!1,isContributeToImpressiveBugActivity:!1,usingEditorId:5,sourceUrl:null},H=await fetch("https://i.cnblogs.com/api/posts",{method:"POST",credentials:"include",headers:Q,body:JSON.stringify(Y)}),X=await H.text();if(!H.ok)return{ok:!1,stage:"create",status:H.status,message:X.slice(0,300)};let W=null;try{W=JSON.parse(X)}catch(j){}if(!W||!W.id)return{ok:!1,stage:"create",status:H.status,message:"创建草稿失败: "+X.slice(0,300)};const V=String(W.id),$="https://i.cnblogs.com/articles/edit;postId="+V;if(K.draftOnly)return{ok:!0,draft:!0,id:V,url:$};const q=await fetch("https://i.cnblogs.com/api/articles/"+V,{credentials:"include",headers:Q}),O=await q.text();let B=null;try{B=JSON.parse(O)}catch(j){}const A=B&&B.blogPost;if(!q.ok||!A)return{ok:!1,stage:"publish",status:q.status,message:"读取草稿完整对象失败: "+O.slice(0,300),id:V};A.isPublished=!0;A.isDraft=!1;const F=await fetch("https://i.cnblogs.com/api/posts",{method:"POST",credentials:"include",headers:Q,body:JSON.stringify(A)}),_=await F.text();if(!F.ok)return{ok:!1,stage:"publish",status:F.status,message:_.slice(0,300),id:V};let L=null;try{L=JSON.parse(_)}catch(j){}const S=L&&L.url||$;return{ok:!0,draft:!1,id:V,url:S}}},cnblogsAuthProfile={home:"https://home.cnblogs.com",checkAuth:async(K)=>{try{const z=await fetch("https://home.cnblogs.com/user/CurrentUserInfo",{method:"GET",credentials:"include"});if(!z.ok)return{isAuthenticated:!1,error:"HTTP "+z.status};const G=await z.text(),Q=G.match(/href="\/u\/([^/]+)\/"/),Y=G.match(/<img[^>]+class="pfs"[^>]+src="([^"]+)"/);if(!Q)return{isAuthenticated:!1};const H=Q[1];return{isAuthenticated:!0,userId:H,username:H,avatar:Y?Y[1]:""}}catch(z){return{isAuthenticated:!1,error:String(z&&z.message||z)}}}};function J(K){if(!K.execute)throw new Z("INVALID_INPUT","此命令需要 --execute 参数才会实际写入博客园")}async function y(K){const z=typeof K.text==="string"?K.text:void 0,G=typeof K.file==="string"?K.file:void 0;if(z&&G)throw new Z("INVALID_INPUT","不能同时使用 <text> 和 --file,选其一即可");let Q=z??"";if(G){const{readFile:Y,stat:H}=await import("node:fs/promises");let X;try{X=await H(G)}catch{throw new Z("INVALID_INPUT","文件不存在: "+G)}if(!X.isFile())throw new Z("INVALID_INPUT","必须是可读文本文件: "+G);let W;try{W=await Y(G)}catch{throw new Z("INVALID_INPUT","文件读取失败: "+G)}try{Q=new TextDecoder("utf-8",{fatal:!0}).decode(W)}catch{throw new Z("INVALID_INPUT","文件不是有效 UTF-8 编码: "+G)}}if(!Q.trim())throw new Z("INVALID_INPUT","正文不能为空");return Q}function C(K,z,G,Q,Y={}){return[{status:"success",outcome:Q,message:K,target_type:z,target:G,...Y}]}N({site:"cnblogs",name:"article",access:"write",description:"发布博客园文章(Markdown)。正文默认 Markdown;图片自动转存到博客园图床。",domain:"cnblogs.com",strategy:v.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:"draft",type:"boolean",help:"仅保存草稿,不发布"},{name:"execute",type:"boolean",help:"实际执行写入。不加此参数命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(K,z)=>{if(!K)throw new M("博客园文章发布需要浏览器会话");J(z);const G=String(z.title??"").trim();if(!G)throw new Z("INVALID_INPUT","文章标题不能为空");const Q=await y(z),Y=Boolean(z.draft),H=await U(K,{title:G,body:Q,format:z.html?"html":"markdown",draftOnly:Y,profile:cnblogsProfile}),X=H.images.uploaded.length|0,W=H.images.failed.length|0;let V=H.draft?"已保存博客园草稿":"已发布博客园文章";if(X||W)V+="·图片: "+X+" 张已转存"+(W?","+W+" 张失败":"");return C(V,"article","",H.draft?"draft":"created",{created_target:"article:"+H.id,created_url:H.url})}});
@@ -1,11 +1,11 @@
1
- import{cli as R,Strategy as Z}from"@jackwener/opencli/registry";import{ArgumentError as f,AuthRequiredError as U,CommandExecutionError as a,EmptyResultError as j}from"@jackwener/opencli/errors";import{stripHtml as y}from"./text.js";function g(t){const e=t.object||{};if(e.id!=null)return`${e.type||""}:${e.id}`;return null}function q(t){const e=t.id==null?"":String(t.id);if(t.type==="answer"){const r=t.question?.id==null?"":String(t.question.id);return r&&e?`https://www.zhihu.com/question/${r}/answer/${e}`:""}if(t.type==="article")return e?`https://zhuanlan.zhihu.com/p/${e}`:"";if(t.type==="question")return e?`https://www.zhihu.com/question/${e}`:"";return""}function _(t){if(typeof t!=="string"||!t)return"";try{const e=new URL(t);if(e.hostname==="api.zhihu.com"&&e.pathname==="/search_v3")return`https://www.zhihu.com/api/v4/search_v3${e.search}`;if(e.hostname==="www.zhihu.com"&&e.pathname==="/api/v4/search_v3")return e.toString()}catch{return""}return""}const z=1000,k=20,p=["all","answer","article","question"];function E(t){const e=Number(t??10);if(!Number.isInteger(e)||e<=0||e>z)throw new f(`zhihu search --limit must be a positive integer no greater than ${z}`,"Use a normal-sized limit to avoid slow requests or Zhihu risk controls");return e}function $(t){const e=String(t||"").trim();if(!e)throw new f("zhihu search query must not be empty","Example: opencli zhihu search codex");return e}function v(t){const e=String(t||"all");if(!p.includes(e))throw new f(`zhihu search --type must be one of: ${p.join(", ")}`,"Example: opencli zhihu search codex --type answer");return e}function S(t){if(t&&typeof t==="object"&&"data"in t&&"session"in t)return t.data;return t}function b(t,e){const r=S(t);if(!r||typeof r!=="object"||Array.isArray(r))throw new a("Zhihu search returned malformed payload");if(r.__httpError){const n=r.__httpError;if(n===401||n===403)throw new U("www.zhihu.com","Failed to fetch search results from Zhihu");throw new a(`Zhihu search request failed${n?` (HTTP ${n})`:""}`,"Try again later or rerun with -v for more detail")}if(r.__fetchError)throw new a("Zhihu search request failed",String(r.__fetchError));if(!Array.isArray(r.data))throw new a("Zhihu search returned malformed data list",`URL: ${e}`);if(!r.paging||typeof r.paging!=="object")throw new a("Zhihu search returned malformed paging data",`URL: ${e}`);return r}function x(t){if(!t||typeof t!=="object"||t.type!=="search_result"||!t.object||typeof t.object!=="object")return null;const e=t.object;if(e.type!=="answer"&&e.type!=="article"&&e.type!=="question")return null;const r=g(t),n=q(e),i=e.question||{},o=y(e.title||i.name||i.title||"");if(!r||!n||!o)throw new a("Zhihu search returned malformed result row identity");return{item:t,key:r,row:{title:o,type:e.type,author:e.author?.name||"",votes:e.voteup_count||0,url:n}}}R({site:"zhihu",name:"search",access:"read",description:"知乎搜索",domain:"www.zhihu.com",strategy:Z.COOKIE,args:[{name:"query",required:!0,positional:!0,help:"Search query"},{name:"limit",type:"int",default:10,help:"Number of results (max 1000; use normal-sized requests)"},{name:"type",default:"all",choices:p,help:"Result type: all, answer, article, or question"}],columns:["rank","title","type","author","votes","url"],func:async(t,e)=>{const r=$(e.query),n=E(e.limit),i=v(e.type);await t.goto("https://www.zhihu.com");let o=`https://www.zhihu.com/api/v4/search_v3?q=${encodeURIComponent(r)}&t=general&offset=0&limit=${k}`;const s=[],w=new Set,l=new Set;while(o&&s.length<n&&!l.has(o)){l.add(o);const u=b(await t.evaluate(`
1
+ import{cli as v,Strategy as h}from"@jackwener/opencli/registry";import{ArgumentError as K,AuthRequiredError as S,CommandExecutionError as $,EmptyResultError as q}from"@jackwener/opencli/errors";import{stripHtml as M}from"./text.js";function U(D){const B=D.object||{};if(B.id!=null)return`${B.type||""}:${B.id}`;return null}function A(D){const B=D.id==null?"":String(D.id);if(D.type==="answer"){const N=D.question?.id==null?"":String(D.question.id);return N&&B?`https://www.zhihu.com/question/${N}/answer/${B}`:""}if(D.type==="article")return B?`https://zhuanlan.zhihu.com/p/${B}`:"";if(D.type==="question")return B?`https://www.zhihu.com/question/${B}`:"";return""}function L(D){if(typeof D!=="string"||!D)return"";try{const B=new URL(D);if(B.hostname==="api.zhihu.com"&&B.pathname==="/search_v3")return`https://www.zhihu.com/api/v4/search_v3${B.search}`;if(B.hostname==="www.zhihu.com"&&B.pathname==="/api/v4/search_v3")return B.toString()}catch{return""}return""}const C=1000,w=20,Q=["all","answer","article","question"];function R(D){const B=Number(D??10);if(!Number.isInteger(B)||B<=0||B>C)throw new K(`zhihu search --limit must be a positive integer no greater than ${C}`,"Use a normal-sized limit to avoid slow requests or Zhihu risk controls");return B}function z(D){const B=String(D||"").trim();if(!B)throw new K("zhihu search query must not be empty","Example: opencli zhihu search codex");return B}function I(D){const B=String(D||"all");if(!Q.includes(B))throw new K(`zhihu search --type must be one of: ${Q.join(", ")}`,"Example: opencli zhihu search codex --type answer");return B}function H(D){if(D&&typeof D==="object"&&"data"in D&&"session"in D)return D.data;return D}function P(D,B){const N=H(D);if(!N||typeof N!=="object"||Array.isArray(N))throw new $("Zhihu search returned malformed payload");if(N.__httpError){const O=N.__httpError;if(O===401||O===403)throw new S("www.zhihu.com","Failed to fetch search results from Zhihu");throw new $(`Zhihu search request failed${O?` (HTTP ${O})`:""}`,"Try again later or rerun with -v for more detail")}if(N.__fetchError)throw new $("Zhihu search request failed",String(N.__fetchError));if(!Array.isArray(N.data))throw new $("Zhihu search returned malformed data list",`URL: ${B}`);if(!N.paging||typeof N.paging!=="object")throw new $("Zhihu search returned malformed paging data",`URL: ${B}`);return N}function T(D){return`
2
2
  (async () => {
3
3
  try {
4
- const r = await fetch(${JSON.stringify(o)}, { credentials: 'include' });
4
+ const r = await fetch(${JSON.stringify(D)}, { credentials: 'include' });
5
5
  if (!r.ok) return { __httpError: r.status };
6
6
  return await r.json();
7
7
  } catch (err) {
8
8
  return { __fetchError: err?.message || String(err) };
9
9
  }
10
10
  })()
11
- `),o);for(const m of u.data){const d=m?.object?.type;if(i!=="all"&&d&&d!==i)continue;const c=x(m);if(!c)continue;if(i!=="all"&&c.row.type!==i)continue;if(w.has(c.key))continue;w.add(c.key);s.push(c.row);if(s.length>=n)break}if(s.length>=n)break;if(u.paging?.is_end)break;const h=_(u.paging?.next);if(!h)throw new a("Zhihu search pagination returned malformed next URL");if(l.has(h))throw new a("Zhihu search pagination returned a repeated next URL");o=h}if(s.length===0)throw new j("zhihu search",`No ${i==="all"?"":`${i} `}results found for "${r}"`);return s.map((u,h)=>{return{rank:h+1,...u}})}});export const __test__={stripHtml:y,itemKey:g,itemUrl:q,normalizeSearchUrl:_,parseLimit:E,requireQuery:$,requireType:v,unwrapEvaluateResult:S,requireSearchPayload:b,normalizeResultItem:x};
11
+ `}async function k(D,B){let N=H(await D.evaluate(T(B)));if(N&&typeof N==="object"&&!Array.isArray(N)&&N.__fetchError){await D.goto("https://www.zhihu.com");N=H(await D.evaluate(T(B)))}return P(N,B)}function x(D){if(!D||typeof D!=="object"||D.type!=="search_result"||!D.object||typeof D.object!=="object")return null;const B=D.object;if(B.type!=="answer"&&B.type!=="article"&&B.type!=="question")return null;const N=U(D),O=A(B),V=B.question||{},W=M(B.title||V.name||V.title||"");if(!N||!O||!W)throw new $("Zhihu search returned malformed result row identity");return{item:D,key:N,row:{title:W,type:B.type,author:B.author?.name||"",votes:B.voteup_count||0,url:O}}}v({site:"zhihu",name:"search",access:"read",description:"知乎搜索",domain:"www.zhihu.com",strategy:h.COOKIE,args:[{name:"query",required:!0,positional:!0,help:"Search query"},{name:"limit",type:"int",default:10,help:"Number of results (max 1000; use normal-sized requests)"},{name:"type",default:"all",choices:Q,help:"Result type: all, answer, article, or question"}],columns:["rank","title","type","author","votes","url"],func:async(D,B)=>{const N=z(B.query),O=R(B.limit),V=I(B.type);await D.goto("https://www.zhihu.com");let W=`https://www.zhihu.com/api/v4/search_v3?q=${encodeURIComponent(N)}&t=general&offset=0&limit=${w}`;const G=[],X=new Set,J=new Set;while(W&&G.length<O&&!J.has(W)){J.add(W);const Z=await k(D,W);for(const Y of Z.data){const _=Y?.object?.type;if(V!=="all"&&_&&_!==V)continue;const F=x(Y);if(!F)continue;if(V!=="all"&&F.row.type!==V)continue;if(X.has(F.key))continue;X.add(F.key);G.push(F.row);if(G.length>=O)break}if(G.length>=O)break;if(Z.paging?.is_end)break;const f=L(Z.paging?.next);if(!f)throw new $("Zhihu search pagination returned malformed next URL");if(J.has(f))throw new $("Zhihu search pagination returned a repeated next URL");W=f}if(G.length===0)throw new q("zhihu search",`No ${V==="all"?"":`${V} `}results found for "${N}"`);return G.map((Z,f)=>{return{rank:f+1,...Z}})}});export const __test__={stripHtml:M,itemKey:U,itemUrl:A,normalizeSearchUrl:L,parseLimit:R,requireQuery:z,requireType:I,unwrapEvaluateResult:H,requireSearchPayload:P,fetchSearchPage:k,normalizeResultItem:x};
@@ -1 +1 @@
1
- import{WebSocket as I}from"ws";import{request as W}from"node:http";import{request as F}from"node:https";import{buildEvaluateExpression as _}from"./utils.js";import{generateStealthJs as z}from"./stealth.js";import{waitForDomStableJs as S}from"./dom-helpers.js";import{isRecord as H,saveBase64ToFile as M}from"../utils.js";import{getAllElectronApps as w}from"../electron-apps.js";import{BasePage as E}from"./base-page.js";const R=30000;export const CDP_RESPONSE_BODY_CAPTURE_LIMIT=8388608;export class CDPBridge{_ws=null;_idCounter=0;_pending=new Map;_eventListeners=new Map;async connect(G){if(this._ws)throw Error("CDPBridge is already connected. Call close() before reconnecting.");const V=G?.cdpEndpoint??process.env.OPENCLI_CDP_ENDPOINT;if(!V)throw Error("CDP endpoint not provided (pass cdpEndpoint or set OPENCLI_CDP_ENDPOINT)");let Q=V;if(V.startsWith("http")){const K=await j(`${V.replace(/\/$/,"")}/json`),X=U(K);if(!X||!X.webSocketDebuggerUrl)throw Error("No inspectable targets found at CDP endpoint");Q=X.webSocketDebuggerUrl}return new Promise((K,X)=>{const Z=new I(Q),$=(G?.timeout??10)*1000,J=setTimeout(()=>{this._ws=null;Z.close();X(Error("CDP connect timeout"))},$);Z.on("open",async()=>{clearTimeout(J);this._ws=Z;try{await this.send("Page.enable");await this.send("Page.addScriptToEvaluateOnNewDocument",{source:z()});if(G?.initScript)await this.send("Page.addScriptToEvaluateOnNewDocument",{source:G.initScript});if(G?.proxyAuth){this.on("Fetch.requestPaused",(L)=>{const Y=L;if(Y.requestId)this.send("Fetch.continueRequest",{requestId:Y.requestId}).catch(()=>{})});this.on("Fetch.authRequired",(L)=>{const Y=L;if(!Y.requestId)return;this.send("Fetch.continueWithAuth",{requestId:Y.requestId,authChallengeResponse:{response:"ProvideCredentials",username:G.proxyAuth.username,password:G.proxyAuth.password}}).catch(()=>{})});await this.send("Fetch.enable",{handleAuthRequests:!0})}}catch(L){Z.close();X(L instanceof Error?L:Error(String(L)));return}K(new O(this))});Z.on("error",(L)=>{clearTimeout(J);X(L)});Z.on("message",(L)=>{try{const Y=JSON.parse(L.toString());if(Y.id&&this._pending.has(Y.id)){const A=this._pending.get(Y.id);clearTimeout(A.timer);this._pending.delete(Y.id);if(Y.error)A.reject(Error(Y.error.message));else A.resolve(Y.result)}if(Y.method){const A=this._eventListeners.get(Y.method);if(A)for(const B of A)B(Y.params)}}catch(Y){if(process.env.OPENCLI_VERBOSE)console.error("[cdp] Failed to parse WebSocket message:",Y instanceof Error?Y.message:Y)}})})}async close(){if(this._ws){this._ws.close();this._ws=null}for(const G of this._pending.values()){clearTimeout(G.timer);G.reject(Error("CDP connection closed"))}this._pending.clear();this._eventListeners.clear()}async send(G,V={},Q=R){if(!this._ws||this._ws.readyState!==I.OPEN)throw Error("CDP connection is not open");const K=++this._idCounter;return new Promise((X,Z)=>{const $=setTimeout(()=>{this._pending.delete(K);Z(Error(`CDP command '${G}' timed out after ${Q/1000}s`))},Q);this._pending.set(K,{resolve:X,reject:Z,timer:$});this._ws.send(JSON.stringify({id:K,method:G,params:V}))})}on(G,V){let Q=this._eventListeners.get(G);if(!Q){Q=new Set;this._eventListeners.set(G,Q)}Q.add(V)}off(G,V){this._eventListeners.get(G)?.delete(V)}waitForEvent(G,V=15000){return new Promise((Q,K)=>{const X=setTimeout(()=>{this.off(G,Z);K(Error(`Timed out waiting for CDP event '${G}'`))},V),Z=($)=>{clearTimeout(X);this.off(G,Z);Q($)};this.on(G,Z)})}}class O extends E{bridge;_pageEnabled=!1;_networkCapturing=!1;_networkCapturePattern="";_networkEntries=[];_pendingRequests=new Map;_pendingBodyFetches=new Set;_consoleMessages=[];_consoleCapturing=!1;constructor(G){super();this.bridge=G}async goto(G,V){if(!this._pageEnabled){await this.bridge.send("Page.enable");this._pageEnabled=!0}const Q=this.bridge.waitForEvent("Page.loadEventFired",30000).catch(()=>{});await this.bridge.send("Page.navigate",{url:G});await Q;this._lastUrl=G;if(V?.waitUntil!=="none"){const K=V?.settleMs??1000;await this.evaluate(S(K,Math.min(500,K)))}}async evaluate(G,...V){const Q=_(G,V),K=await this.bridge.send("Runtime.evaluate",{expression:Q,returnByValue:!0,awaitPromise:!0});if(K.exceptionDetails)throw Error("Evaluate error: "+(K.exceptionDetails.exception?.description||"Unknown exception"));return K.result?.value}async getCookies(G={}){let V;try{const K=await this.bridge.send("Storage.getCookies");V=H(K)&&Array.isArray(K.cookies)?K.cookies:[]}catch{const K=await this.bridge.send("Network.getCookies",G.url?{urls:[G.url]}:{});V=H(K)&&Array.isArray(K.cookies)?K.cookies:[]}const Q=G.domain??(G.url?T(G.url):void 0);return Q?V.filter((K)=>N(K)&&C(K.domain,Q)):V.filter(N)}async screenshot(G={}){const V=G.fullPage===!0,Q=G.width&&G.width>0?Math.ceil(G.width):void 0,K=!V&&G.height&&G.height>0?Math.ceil(G.height):void 0,X=Q!==void 0||K!==void 0;if(X){if(Q!==void 0&&V)await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:Q,height:0,deviceScaleFactor:1});let Z=Q??0,$=K??0;if(V){const J=await this.bridge.send("Page.getLayoutMetrics"),L=H(J)?J:{},Y=H(L.cssContentSize)?L.cssContentSize:void 0,A=H(L.contentSize)?L.contentSize:void 0,B=Y??A;if(B&&typeof B.width==="number"&&typeof B.height==="number"){if(Z===0)Z=Math.ceil(B.width);$=Math.ceil(B.height)}}await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:Z,height:$,deviceScaleFactor:1})}try{const Z=await this.bridge.send("Page.captureScreenshot",{format:G.format??"png",quality:G.format==="jpeg"?G.quality??80:void 0,captureBeyondViewport:!X&&V}),$=H(Z)&&typeof Z.data==="string"?Z.data:"";if(G.path)await M($,G.path);return $}finally{if(X)await this.bridge.send("Emulation.clearDeviceMetricsOverride").catch(()=>{})}}async startNetworkCapture(G=""){this._networkCapturePattern=G;if(!this._networkCapturing){this._networkEntries=[];this._pendingRequests.clear();this._pendingBodyFetches.clear();await this.bridge.send("Network.enable");this.bridge.on("Network.requestWillBeSent",(V)=>{const Q=V;if(!this._networkCapturePattern||Q.request.url.includes(this._networkCapturePattern)){const K=this._networkEntries.push({url:Q.request.url,method:Q.request.method,timestamp:Date.now()})-1;this._pendingRequests.set(Q.requestId,K)}});this.bridge.on("Network.responseReceived",(V)=>{const Q=V,K=this._pendingRequests.get(Q.requestId);if(K!==void 0){this._networkEntries[K].responseStatus=Q.response.status;this._networkEntries[K].responseContentType=Q.response.mimeType||""}});this.bridge.on("Network.loadingFinished",(V)=>{const Q=V,K=this._pendingRequests.get(Q.requestId);if(K!==void 0){const X=this.bridge.send("Network.getResponseBody",{requestId:Q.requestId}).then((Z)=>{const $=Z;if(typeof $?.body==="string"){const J=$.body.length,L=J>CDP_RESPONSE_BODY_CAPTURE_LIMIT,Y=L?$.body.slice(0,CDP_RESPONSE_BODY_CAPTURE_LIMIT):$.body;this._networkEntries[K].responsePreview=$.base64Encoded?`base64:${Y}`:Y;this._networkEntries[K].responseBodyFullSize=J;this._networkEntries[K].responseBodyTruncated=L}}).catch((Z)=>{if(process.env.OPENCLI_VERBOSE)console.error(`[cdp] getResponseBody failed for ${Q.requestId}:`,Z instanceof Error?Z.message:Z)}).finally(()=>{this._pendingBodyFetches.delete(X)});this._pendingBodyFetches.add(X);this._pendingRequests.delete(Q.requestId)}});this._networkCapturing=!0}return!0}async readNetworkCapture(){if(this._pendingBodyFetches.size>0)await Promise.all([...this._pendingBodyFetches]);const G=[...this._networkEntries];this._networkEntries=[];return G}async consoleMessages(G="all"){if(!this._consoleCapturing){await this.bridge.send("Runtime.enable");this.bridge.on("Runtime.consoleAPICalled",(V)=>{const Q=V,K=(Q.args||[]).map((X)=>X.value!==void 0?String(X.value):X.description||"").join(" ");this._consoleMessages.push({type:Q.type,text:K,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this.bridge.on("Runtime.exceptionThrown",(V)=>{const Q=V,K=Q.exceptionDetails?.exception?.description||Q.exceptionDetails?.text||"Unknown exception";this._consoleMessages.push({type:"error",text:K,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this._consoleCapturing=!0}if(G==="all")return[...this._consoleMessages];if(G==="error")return this._consoleMessages.filter((V)=>V.type==="error"||V.type==="warning");return this._consoleMessages.filter((V)=>V.type===G)}async tabs(){return[]}async selectTab(G){}async cdp(G,V={}){return this.bridge.send(G,V)}async handleJavaScriptDialog(G,V){await this.cdp("Page.handleJavaScriptDialog",{accept:G,...V!==void 0&&{promptText:V}})}async nativeClick(G,V){await this.cdp("Input.dispatchMouseEvent",{type:"mouseMoved",x:G,y:V});await this.cdp("Input.dispatchMouseEvent",{type:"mousePressed",x:G,y:V,button:"left",clickCount:1});await this.cdp("Input.dispatchMouseEvent",{type:"mouseReleased",x:G,y:V,button:"left",clickCount:1})}async nativeType(G){await this.cdp("Input.insertText",{text:G})}async insertText(G){await this.nativeType(G)}async nativeKeyPress(G,V=[]){let Q=0;for(const K of V){if(K==="Alt")Q|=1;if(K==="Ctrl"||K==="Control")Q|=2;if(K==="Meta")Q|=4;if(K==="Shift")Q|=8}await this.cdp("Input.dispatchKeyEvent",{type:"keyDown",key:G,modifiers:Q});await this.cdp("Input.dispatchKeyEvent",{type:"keyUp",key:G,modifiers:Q})}}function T(G){try{return new URL(G).hostname}catch{return}}function N(G){return H(G)&&typeof G.name==="string"&&typeof G.value==="string"&&typeof G.domain==="string"}function C(G,V){const Q=G.replace(/^\./,"").toLowerCase(),K=V.replace(/^\./,"").toLowerCase();return K===Q||K.endsWith(`.${Q}`)}function U(G){const V=b(process.env.OPENCLI_CDP_TARGET);return G.map((K,X)=>({target:K,index:X,score:q(K,V)})).filter(({score:K})=>Number.isFinite(K)).sort((K,X)=>{if(X.score!==K.score)return X.score-K.score;return K.index-X.index})[0]?.target}function q(G,V){if(!G.webSocketDebuggerUrl)return Number.NEGATIVE_INFINITY;const Q=(G.type??"").toLowerCase(),K=(G.url??"").toLowerCase(),X=(G.title??"").toLowerCase(),Z=`${X} ${K}`;if(!Z.trim()&&!Q)return Number.NEGATIVE_INFINITY;if(Z.includes("devtools"))return Number.NEGATIVE_INFINITY;if(Q==="background_page"||Q==="service_worker")return Number.NEGATIVE_INFINITY;let $=0;if(V&&V.test(Z))$+=1000;if(Q==="app")$+=120;else if(Q==="webview")$+=100;else if(Q==="page")$+=80;else if(Q==="iframe")$+=20;if(K.startsWith("http://localhost")||K.startsWith("https://localhost"))$+=90;if(K.startsWith("file://"))$+=60;if(K.startsWith("http://127.0.0.1")||K.startsWith("https://127.0.0.1"))$+=50;if(K.startsWith("about:blank"))$-=120;if(K===""||K==="about:blank")$-=40;if(X&&X!=="devtools")$+=25;const J=Object.values(w()).map((L)=>(L.displayName??L.processName).toLowerCase());for(const L of J)if(X.includes(L)){$+=120;break}for(const L of J)if(K.includes(L)){$+=100;break}return $}function b(G){const V=G?.trim();if(!V)return;return new RegExp(D(V.toLowerCase()))}function D(G){return G.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export const __test__={selectCDPTarget:U,scoreCDPTarget:q};function j(G){return new Promise((V,Q)=>{const K=new URL(G),X=(K.protocol==="https:"?F:W)(K,(Z)=>{const $=Z.statusCode??0;if($<200||$>=300){Z.resume();Q(Error(`Failed to fetch CDP targets: HTTP ${$}`));return}const J=[];Z.on("data",(L)=>J.push(Buffer.isBuffer(L)?L:Buffer.from(L)));Z.on("end",()=>{try{V(JSON.parse(Buffer.concat(J).toString("utf8")))}catch(L){Q(L instanceof Error?L:Error(String(L)))}})});X.on("error",Q);X.setTimeout(1e4,()=>X.destroy(Error("Timed out fetching CDP targets")));X.end()})}
1
+ import{WebSocket as I}from"ws";import{request as W}from"node:http";import{request as F}from"node:https";import{buildEvaluateExpression as _}from"./utils.js";import{generateStealthJs as z}from"./stealth.js";import{waitForDomStableJs as S}from"./dom-helpers.js";import{isRecord as H,saveBase64ToFile as M}from"../utils.js";import{getAllElectronApps as w}from"../electron-apps.js";import{BasePage as E}from"./base-page.js";const R=30000;export const CDP_RESPONSE_BODY_CAPTURE_LIMIT=8388608;export class CDPBridge{_ws=null;_idCounter=0;_pending=new Map;_eventListeners=new Map;async connect(G){if(this._ws)throw Error("CDPBridge is already connected. Call close() before reconnecting.");const V=G?.cdpEndpoint??process.env.OPENCLI_CDP_ENDPOINT;if(!V)throw Error("CDP endpoint not provided (pass cdpEndpoint or set OPENCLI_CDP_ENDPOINT)");let Q=V;if(V.startsWith("http")){const K=await j(`${V.replace(/\/$/,"")}/json`),X=U(K);if(!X||!X.webSocketDebuggerUrl)throw Error("No inspectable targets found at CDP endpoint");Q=X.webSocketDebuggerUrl}return new Promise((K,X)=>{const Z=new I(Q),$=(G?.timeout??10)*1000,J=setTimeout(()=>{this._ws=null;Z.close();X(Error("CDP connect timeout"))},$);Z.on("open",async()=>{clearTimeout(J);this._ws=Z;try{await this.send("Page.enable");this.on("Page.javascriptDialogOpening",(L)=>{if(L?.type==="beforeunload")this.send("Page.handleJavaScriptDialog",{accept:!0}).catch(()=>{})});await this.send("Page.addScriptToEvaluateOnNewDocument",{source:z()});if(G?.initScript)await this.send("Page.addScriptToEvaluateOnNewDocument",{source:G.initScript});if(G?.proxyAuth){this.on("Fetch.requestPaused",(L)=>{const Y=L;if(Y.requestId)this.send("Fetch.continueRequest",{requestId:Y.requestId}).catch(()=>{})});this.on("Fetch.authRequired",(L)=>{const Y=L;if(!Y.requestId)return;this.send("Fetch.continueWithAuth",{requestId:Y.requestId,authChallengeResponse:{response:"ProvideCredentials",username:G.proxyAuth.username,password:G.proxyAuth.password}}).catch(()=>{})});await this.send("Fetch.enable",{handleAuthRequests:!0})}}catch(L){Z.close();X(L instanceof Error?L:Error(String(L)));return}K(new O(this))});Z.on("error",(L)=>{clearTimeout(J);X(L)});Z.on("message",(L)=>{try{const Y=JSON.parse(L.toString());if(Y.id&&this._pending.has(Y.id)){const A=this._pending.get(Y.id);clearTimeout(A.timer);this._pending.delete(Y.id);if(Y.error)A.reject(Error(Y.error.message));else A.resolve(Y.result)}if(Y.method){const A=this._eventListeners.get(Y.method);if(A)for(const B of A)B(Y.params)}}catch(Y){if(process.env.OPENCLI_VERBOSE)console.error("[cdp] Failed to parse WebSocket message:",Y instanceof Error?Y.message:Y)}})})}async close(){if(this._ws){this._ws.close();this._ws=null}for(const G of this._pending.values()){clearTimeout(G.timer);G.reject(Error("CDP connection closed"))}this._pending.clear();this._eventListeners.clear()}async send(G,V={},Q=R){if(!this._ws||this._ws.readyState!==I.OPEN)throw Error("CDP connection is not open");const K=++this._idCounter;return new Promise((X,Z)=>{const $=setTimeout(()=>{this._pending.delete(K);const J=G.startsWith("Page.")?"(页面可能被 JS 弹窗挡住:试 `opencli browser dialog accept`;托管 profile 可 `opencli profile stop <name>` 关闭后重跑命令自动重启会话)":"";Z(Error(`CDP command '${G}' timed out after ${Q/1000}s${J}`))},Q);this._pending.set(K,{resolve:X,reject:Z,timer:$});this._ws.send(JSON.stringify({id:K,method:G,params:V}))})}on(G,V){let Q=this._eventListeners.get(G);if(!Q){Q=new Set;this._eventListeners.set(G,Q)}Q.add(V)}off(G,V){this._eventListeners.get(G)?.delete(V)}waitForEvent(G,V=15000){return new Promise((Q,K)=>{const X=setTimeout(()=>{this.off(G,Z);K(Error(`Timed out waiting for CDP event '${G}'`))},V),Z=($)=>{clearTimeout(X);this.off(G,Z);Q($)};this.on(G,Z)})}}class O extends E{bridge;_pageEnabled=!1;_networkCapturing=!1;_networkCapturePattern="";_networkEntries=[];_pendingRequests=new Map;_pendingBodyFetches=new Set;_consoleMessages=[];_consoleCapturing=!1;constructor(G){super();this.bridge=G}async goto(G,V){if(!this._pageEnabled){await this.bridge.send("Page.enable");this._pageEnabled=!0}const Q=this.bridge.waitForEvent("Page.loadEventFired",30000).catch(()=>{});await this.bridge.send("Page.navigate",{url:G});await Q;this._lastUrl=G;if(V?.waitUntil!=="none"){const K=V?.settleMs??1000;await this.evaluate(S(K,Math.min(500,K)))}}async evaluate(G,...V){const Q=_(G,V),K=await this.bridge.send("Runtime.evaluate",{expression:Q,returnByValue:!0,awaitPromise:!0});if(K.exceptionDetails)throw Error("Evaluate error: "+(K.exceptionDetails.exception?.description||"Unknown exception"));return K.result?.value}async getCookies(G={}){let V;try{const K=await this.bridge.send("Storage.getCookies");V=H(K)&&Array.isArray(K.cookies)?K.cookies:[]}catch{const K=await this.bridge.send("Network.getCookies",G.url?{urls:[G.url]}:{});V=H(K)&&Array.isArray(K.cookies)?K.cookies:[]}const Q=G.domain??(G.url?T(G.url):void 0);return Q?V.filter((K)=>N(K)&&C(K.domain,Q)):V.filter(N)}async screenshot(G={}){const V=G.fullPage===!0,Q=G.width&&G.width>0?Math.ceil(G.width):void 0,K=!V&&G.height&&G.height>0?Math.ceil(G.height):void 0,X=Q!==void 0||K!==void 0;if(X){if(Q!==void 0&&V)await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:Q,height:0,deviceScaleFactor:1});let Z=Q??0,$=K??0;if(V){const J=await this.bridge.send("Page.getLayoutMetrics"),L=H(J)?J:{},Y=H(L.cssContentSize)?L.cssContentSize:void 0,A=H(L.contentSize)?L.contentSize:void 0,B=Y??A;if(B&&typeof B.width==="number"&&typeof B.height==="number"){if(Z===0)Z=Math.ceil(B.width);$=Math.ceil(B.height)}}await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:Z,height:$,deviceScaleFactor:1})}try{const Z=await this.bridge.send("Page.captureScreenshot",{format:G.format??"png",quality:G.format==="jpeg"?G.quality??80:void 0,captureBeyondViewport:!X&&V}),$=H(Z)&&typeof Z.data==="string"?Z.data:"";if(G.path)await M($,G.path);return $}finally{if(X)await this.bridge.send("Emulation.clearDeviceMetricsOverride").catch(()=>{})}}async startNetworkCapture(G=""){this._networkCapturePattern=G;if(!this._networkCapturing){this._networkEntries=[];this._pendingRequests.clear();this._pendingBodyFetches.clear();await this.bridge.send("Network.enable");this.bridge.on("Network.requestWillBeSent",(V)=>{const Q=V;if(!this._networkCapturePattern||Q.request.url.includes(this._networkCapturePattern)){const K=this._networkEntries.push({url:Q.request.url,method:Q.request.method,timestamp:Date.now()})-1;this._pendingRequests.set(Q.requestId,K)}});this.bridge.on("Network.responseReceived",(V)=>{const Q=V,K=this._pendingRequests.get(Q.requestId);if(K!==void 0){this._networkEntries[K].responseStatus=Q.response.status;this._networkEntries[K].responseContentType=Q.response.mimeType||""}});this.bridge.on("Network.loadingFinished",(V)=>{const Q=V,K=this._pendingRequests.get(Q.requestId);if(K!==void 0){const X=this.bridge.send("Network.getResponseBody",{requestId:Q.requestId}).then((Z)=>{const $=Z;if(typeof $?.body==="string"){const J=$.body.length,L=J>CDP_RESPONSE_BODY_CAPTURE_LIMIT,Y=L?$.body.slice(0,CDP_RESPONSE_BODY_CAPTURE_LIMIT):$.body;this._networkEntries[K].responsePreview=$.base64Encoded?`base64:${Y}`:Y;this._networkEntries[K].responseBodyFullSize=J;this._networkEntries[K].responseBodyTruncated=L}}).catch((Z)=>{if(process.env.OPENCLI_VERBOSE)console.error(`[cdp] getResponseBody failed for ${Q.requestId}:`,Z instanceof Error?Z.message:Z)}).finally(()=>{this._pendingBodyFetches.delete(X)});this._pendingBodyFetches.add(X);this._pendingRequests.delete(Q.requestId)}});this._networkCapturing=!0}return!0}async readNetworkCapture(){if(this._pendingBodyFetches.size>0)await Promise.all([...this._pendingBodyFetches]);const G=[...this._networkEntries];this._networkEntries=[];return G}async consoleMessages(G="all"){if(!this._consoleCapturing){await this.bridge.send("Runtime.enable");this.bridge.on("Runtime.consoleAPICalled",(V)=>{const Q=V,K=(Q.args||[]).map((X)=>X.value!==void 0?String(X.value):X.description||"").join(" ");this._consoleMessages.push({type:Q.type,text:K,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this.bridge.on("Runtime.exceptionThrown",(V)=>{const Q=V,K=Q.exceptionDetails?.exception?.description||Q.exceptionDetails?.text||"Unknown exception";this._consoleMessages.push({type:"error",text:K,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this._consoleCapturing=!0}if(G==="all")return[...this._consoleMessages];if(G==="error")return this._consoleMessages.filter((V)=>V.type==="error"||V.type==="warning");return this._consoleMessages.filter((V)=>V.type===G)}async tabs(){return[]}async selectTab(G){}async cdp(G,V={}){return this.bridge.send(G,V)}async handleJavaScriptDialog(G,V){await this.cdp("Page.handleJavaScriptDialog",{accept:G,...V!==void 0&&{promptText:V}})}async nativeClick(G,V){await this.cdp("Input.dispatchMouseEvent",{type:"mouseMoved",x:G,y:V});await this.cdp("Input.dispatchMouseEvent",{type:"mousePressed",x:G,y:V,button:"left",clickCount:1});await this.cdp("Input.dispatchMouseEvent",{type:"mouseReleased",x:G,y:V,button:"left",clickCount:1})}async nativeType(G){await this.cdp("Input.insertText",{text:G})}async insertText(G){await this.nativeType(G)}async nativeKeyPress(G,V=[]){let Q=0;for(const K of V){if(K==="Alt")Q|=1;if(K==="Ctrl"||K==="Control")Q|=2;if(K==="Meta")Q|=4;if(K==="Shift")Q|=8}await this.cdp("Input.dispatchKeyEvent",{type:"keyDown",key:G,modifiers:Q});await this.cdp("Input.dispatchKeyEvent",{type:"keyUp",key:G,modifiers:Q})}}function T(G){try{return new URL(G).hostname}catch{return}}function N(G){return H(G)&&typeof G.name==="string"&&typeof G.value==="string"&&typeof G.domain==="string"}function C(G,V){const Q=G.replace(/^\./,"").toLowerCase(),K=V.replace(/^\./,"").toLowerCase();return K===Q||K.endsWith(`.${Q}`)}function U(G){const V=b(process.env.OPENCLI_CDP_TARGET);return G.map((K,X)=>({target:K,index:X,score:q(K,V)})).filter(({score:K})=>Number.isFinite(K)).sort((K,X)=>{if(X.score!==K.score)return X.score-K.score;return K.index-X.index})[0]?.target}function q(G,V){if(!G.webSocketDebuggerUrl)return Number.NEGATIVE_INFINITY;const Q=(G.type??"").toLowerCase(),K=(G.url??"").toLowerCase(),X=(G.title??"").toLowerCase(),Z=`${X} ${K}`;if(!Z.trim()&&!Q)return Number.NEGATIVE_INFINITY;if(Z.includes("devtools"))return Number.NEGATIVE_INFINITY;if(Q==="background_page"||Q==="service_worker")return Number.NEGATIVE_INFINITY;let $=0;if(V&&V.test(Z))$+=1000;if(Q==="app")$+=120;else if(Q==="webview")$+=100;else if(Q==="page")$+=80;else if(Q==="iframe")$+=20;if(K.startsWith("http://localhost")||K.startsWith("https://localhost"))$+=90;if(K.startsWith("file://"))$+=60;if(K.startsWith("http://127.0.0.1")||K.startsWith("https://127.0.0.1"))$+=50;if(K.startsWith("about:blank"))$-=120;if(K===""||K==="about:blank")$-=40;if(X&&X!=="devtools")$+=25;const J=Object.values(w()).map((L)=>(L.displayName??L.processName).toLowerCase());for(const L of J)if(X.includes(L)){$+=120;break}for(const L of J)if(K.includes(L)){$+=100;break}return $}function b(G){const V=G?.trim();if(!V)return;return new RegExp(D(V.toLowerCase()))}function D(G){return G.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export const __test__={selectCDPTarget:U,scoreCDPTarget:q};function j(G){return new Promise((V,Q)=>{const K=new URL(G),X=(K.protocol==="https:"?F:W)(K,(Z)=>{const $=Z.statusCode??0;if($<200||$>=300){Z.resume();Q(Error(`Failed to fetch CDP targets: HTTP ${$}`));return}const J=[];Z.on("data",(L)=>J.push(Buffer.isBuffer(L)?L:Buffer.from(L)));Z.on("end",()=>{try{V(JSON.parse(Buffer.concat(J).toString("utf8")))}catch(L){Q(L instanceof Error?L:Error(String(L)))}})});X.on("error",Q);X.setTimeout(1e4,()=>X.destroy(Error("Timed out fetching CDP targets")));X.end()})}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "publishport-opencli",
3
- "version": "1.8.5-pp.20",
3
+ "version": "1.8.5-pp.22",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": false