publishport-opencli 1.0.33 → 1.0.35

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
@@ -5526,6 +5526,7 @@
5526
5526
  "sourceFile": "bilibili/watch.js",
5527
5527
  "navigateBefore": false,
5528
5528
  "siteSession": "persistent",
5529
+ "sessionKey": "watch",
5529
5530
  "defaultWindowMode": "background"
5530
5531
  },
5531
5532
  {
@@ -18328,6 +18329,7 @@
18328
18329
  "sourceFile": "github/watch.js",
18329
18330
  "navigateBefore": false,
18330
18331
  "siteSession": "persistent",
18332
+ "sessionKey": "watch",
18331
18333
  "defaultWindowMode": "background"
18332
18334
  },
18333
18335
  {
@@ -31381,6 +31383,7 @@
31381
31383
  "sourceFile": "nowcoder/watch.js",
31382
31384
  "navigateBefore": false,
31383
31385
  "siteSession": "persistent",
31386
+ "sessionKey": "watch",
31384
31387
  "defaultWindowMode": "background"
31385
31388
  },
31386
31389
  {
@@ -37152,6 +37155,7 @@
37152
37155
  "sourceFile": "reddit/watch.js",
37153
37156
  "navigateBefore": false,
37154
37157
  "siteSession": "persistent",
37158
+ "sessionKey": "watch",
37155
37159
  "defaultWindowMode": "background"
37156
37160
  },
37157
37161
  {
@@ -41767,6 +41771,56 @@
41767
41771
  "sourceFile": "taobao/detail.js",
41768
41772
  "navigateBefore": false
41769
41773
  },
41774
+ {
41775
+ "site": "taobao",
41776
+ "name": "inbox",
41777
+ "description": "列出淘宝旺旺最近的会话(对方 userId、我方是卖家还是买家、最后一条消息、商品 id)",
41778
+ "access": "read",
41779
+ "domain": "market.m.taobao.com",
41780
+ "strategy": "cookie",
41781
+ "browser": true,
41782
+ "args": [
41783
+ {
41784
+ "name": "limit",
41785
+ "type": "int",
41786
+ "default": 20,
41787
+ "required": false,
41788
+ "help": "返回多少个会话(最多 100)"
41789
+ },
41790
+ {
41791
+ "name": "as-seller",
41792
+ "type": "bool",
41793
+ "default": false,
41794
+ "required": false,
41795
+ "help": "只看买家找店铺的会话(我方是卖家)"
41796
+ },
41797
+ {
41798
+ "name": "unread-only",
41799
+ "type": "bool",
41800
+ "default": false,
41801
+ "required": false,
41802
+ "help": "只看有未读的会话"
41803
+ }
41804
+ ],
41805
+ "columns": [
41806
+ "rank",
41807
+ "peer_nick",
41808
+ "peer_user_id",
41809
+ "self_role",
41810
+ "last_message",
41811
+ "last_from",
41812
+ "last_time",
41813
+ "unread",
41814
+ "item_id",
41815
+ "conversation_id"
41816
+ ],
41817
+ "type": "js",
41818
+ "modulePath": "taobao/inbox.js",
41819
+ "sourceFile": "taobao/inbox.js",
41820
+ "navigateBefore": false,
41821
+ "sessionKey": "watch",
41822
+ "defaultWindowMode": "background"
41823
+ },
41770
41824
  {
41771
41825
  "site": "taobao",
41772
41826
  "name": "login",
@@ -41798,6 +41852,46 @@
41798
41852
  "siteSession": "persistent",
41799
41853
  "defaultWindowMode": "foreground"
41800
41854
  },
41855
+ {
41856
+ "site": "taobao",
41857
+ "name": "messages",
41858
+ "description": "读取淘宝旺旺某个会话最近的聊天记录(按会话 id 或对方 userId)",
41859
+ "access": "read",
41860
+ "domain": "market.m.taobao.com",
41861
+ "strategy": "cookie",
41862
+ "browser": true,
41863
+ "args": [
41864
+ {
41865
+ "name": "target",
41866
+ "type": "str",
41867
+ "required": true,
41868
+ "positional": true,
41869
+ "help": "会话 id(…#11001@cntaobao)或对方 userId"
41870
+ },
41871
+ {
41872
+ "name": "limit",
41873
+ "type": "int",
41874
+ "default": 20,
41875
+ "required": false,
41876
+ "help": "最近多少条(最多 50)"
41877
+ }
41878
+ ],
41879
+ "columns": [
41880
+ "index",
41881
+ "time",
41882
+ "from",
41883
+ "sender_nick",
41884
+ "text",
41885
+ "item_id",
41886
+ "message_id"
41887
+ ],
41888
+ "type": "js",
41889
+ "modulePath": "taobao/messages.js",
41890
+ "sourceFile": "taobao/messages.js",
41891
+ "navigateBefore": false,
41892
+ "sessionKey": "watch",
41893
+ "defaultWindowMode": "background"
41894
+ },
41801
41895
  {
41802
41896
  "site": "taobao",
41803
41897
  "name": "reviews",
@@ -41885,6 +41979,153 @@
41885
41979
  "sourceFile": "taobao/search.js",
41886
41980
  "navigateBefore": false
41887
41981
  },
41982
+ {
41983
+ "site": "taobao",
41984
+ "name": "send",
41985
+ "description": "给淘宝旺旺里已有的会话发一条文字消息,发完回读确认送达(--dry-run 只核对会话和发送通道,不发)",
41986
+ "access": "write",
41987
+ "domain": "market.m.taobao.com",
41988
+ "strategy": "cookie",
41989
+ "browser": true,
41990
+ "args": [
41991
+ {
41992
+ "name": "target",
41993
+ "type": "str",
41994
+ "required": true,
41995
+ "positional": true,
41996
+ "help": "会话 id(…#11001@cntaobao)或对方 userId"
41997
+ },
41998
+ {
41999
+ "name": "text",
42000
+ "type": "str",
42001
+ "required": false,
42002
+ "help": "消息正文(或用 --file 指向正文文件)",
42003
+ "fromFile": true
42004
+ },
42005
+ {
42006
+ "name": "dry-run",
42007
+ "type": "bool",
42008
+ "default": false,
42009
+ "required": false,
42010
+ "help": "只核对会话、登录和发送通道,不发送"
42011
+ },
42012
+ {
42013
+ "name": "timeout",
42014
+ "type": "int",
42015
+ "default": 20,
42016
+ "required": false,
42017
+ "help": "等送达回读的秒数"
42018
+ },
42019
+ {
42020
+ "name": "file",
42021
+ "type": "str",
42022
+ "required": false,
42023
+ "help": "从本机文件读取text(UTF-8),与 --text 二选一。长正文建议走文件:命令行上只剩短参数,不受 shell 引号 / 换行 / 编码影响",
42024
+ "generatedFileFor": "text"
42025
+ }
42026
+ ],
42027
+ "columns": [
42028
+ "ok",
42029
+ "status",
42030
+ "reason",
42031
+ "peer_nick",
42032
+ "message_id",
42033
+ "tail"
42034
+ ],
42035
+ "type": "js",
42036
+ "modulePath": "taobao/send.js",
42037
+ "sourceFile": "taobao/send.js",
42038
+ "navigateBefore": false,
42039
+ "sessionKey": "watch",
42040
+ "defaultWindowMode": "background"
42041
+ },
42042
+ {
42043
+ "site": "taobao",
42044
+ "name": "watch",
42045
+ "description": "淘宝旺旺私信监听:旁听网页版旺旺自己的 WebSocket 推送,一有新消息就读已打开的会话,按 NDJSON 逐条输出(带对方 userId、商品 id、我方是买家还是卖家)",
42046
+ "access": "read",
42047
+ "domain": "market.m.taobao.com",
42048
+ "strategy": "cookie",
42049
+ "browser": true,
42050
+ "args": [
42051
+ {
42052
+ "name": "interval",
42053
+ "type": "int",
42054
+ "default": 1000,
42055
+ "required": false,
42056
+ "help": "本机取帧间隔毫秒(默认 1000)。纯本机 IPC,不发平台请求"
42057
+ },
42058
+ {
42059
+ "name": "reconcile-interval",
42060
+ "type": "int",
42061
+ "default": 0,
42062
+ "required": false,
42063
+ "help": "对账间隔秒(0 = 用该平台的默认值:本平台 60 秒);-1 = 彻底关闭对账"
42064
+ },
42065
+ {
42066
+ "name": "heartbeat",
42067
+ "type": "int",
42068
+ "default": 30,
42069
+ "required": false,
42070
+ "help": "心跳间隔秒(默认 30),无事件时输出一行 status 表示还活着"
42071
+ },
42072
+ {
42073
+ "name": "events",
42074
+ "type": "str",
42075
+ "required": false,
42076
+ "help": "只输出这些事件类型,逗号分隔(如 dm,comment);默认全部"
42077
+ },
42078
+ {
42079
+ "name": "once",
42080
+ "type": "bool",
42081
+ "default": false,
42082
+ "required": false,
42083
+ "help": "只跑一轮对账后退出,不长驻。用于验证解析器和补历史"
42084
+ },
42085
+ {
42086
+ "name": "replay-seen",
42087
+ "type": "bool",
42088
+ "default": false,
42089
+ "required": false,
42090
+ "help": "忽略去重表,把解析到的事件全部重新输出(调试用,会导致重复触发)"
42091
+ },
42092
+ {
42093
+ "name": "backfill",
42094
+ "type": "bool",
42095
+ "default": false,
42096
+ "required": false,
42097
+ "help": "首次运行时把已存在的历史消息也当成新事件输出(默认只静默登记,不触发)"
42098
+ },
42099
+ {
42100
+ "name": "dump-frames",
42101
+ "type": "bool",
42102
+ "default": false,
42103
+ "required": false,
42104
+ "help": "侦察模式:把收到的每一条原始帧(url / 通道 / 内容片段)也打出来,用来给新平台摸清事件通道"
42105
+ },
42106
+ {
42107
+ "name": "include-own",
42108
+ "type": "bool",
42109
+ "default": false,
42110
+ "required": false,
42111
+ "help": "连自己发出去的消息也当成事件(默认不算——否则自动回复会追着自己的回复自我触发)。调试解析用"
42112
+ }
42113
+ ],
42114
+ "columns": [
42115
+ "event",
42116
+ "id",
42117
+ "sender",
42118
+ "text",
42119
+ "ts"
42120
+ ],
42121
+ "type": "js",
42122
+ "modulePath": "taobao/watch.js",
42123
+ "sourceFile": "taobao/watch.js",
42124
+ "navigateBefore": false,
42125
+ "siteSession": "persistent",
42126
+ "sessionKey": "watch",
42127
+ "defaultWindowMode": "background"
42128
+ },
41888
42129
  {
41889
42130
  "site": "taobao",
41890
42131
  "name": "whoami",
@@ -46753,6 +46994,7 @@
46753
46994
  "sourceFile": "twitter/watch.js",
46754
46995
  "navigateBefore": false,
46755
46996
  "siteSession": "persistent",
46997
+ "sessionKey": "watch",
46756
46998
  "defaultWindowMode": "background"
46757
46999
  },
46758
47000
  {
@@ -47528,6 +47770,7 @@
47528
47770
  "sourceFile": "v2ex/watch.js",
47529
47771
  "navigateBefore": false,
47530
47772
  "siteSession": "persistent",
47773
+ "sessionKey": "watch",
47531
47774
  "defaultWindowMode": "background"
47532
47775
  },
47533
47776
  {
@@ -48452,6 +48695,7 @@
48452
48695
  "sourceFile": "weibo/watch.js",
48453
48696
  "navigateBefore": false,
48454
48697
  "siteSession": "persistent",
48698
+ "sessionKey": "watch",
48455
48699
  "defaultWindowMode": "background"
48456
48700
  },
48457
48701
  {
@@ -50833,7 +51077,14 @@
50833
51077
  "type": "str",
50834
51078
  "default": "ALL",
50835
51079
  "required": false,
50836
- "help": "订单状态筛选:ALL / WAITING_PAY / WAITING_SEND / WAITING_RECEIVE / WAITING_RATE / SUCCESS / REFUND"
51080
+ "help": "订单状态筛选:ALL / NOT_PAY / NOT_SHIP / SHIPPED / REFUND / TRADE_CLOSED / TRADE_SUCCESS"
51081
+ },
51082
+ {
51083
+ "name": "order_id",
51084
+ "type": "str",
51085
+ "default": "",
51086
+ "required": false,
51087
+ "help": "只查这一个订单号(order-get 的详情接口报「闲鱼太累了」时用它核实到账)"
50837
51088
  }
50838
51089
  ],
50839
51090
  "columns": [
@@ -50842,7 +51093,7 @@
50842
51093
  "buyer_nick",
50843
51094
  "status_str",
50844
51095
  "total_price",
50845
- "create_time"
51096
+ "pay_time"
50846
51097
  ],
50847
51098
  "type": "js",
50848
51099
  "modulePath": "xianyu/order.js",
@@ -51440,6 +51691,7 @@
51440
51691
  "sourceFile": "xianyu/watch.js",
51441
51692
  "navigateBefore": false,
51442
51693
  "siteSession": "persistent",
51694
+ "sessionKey": "watch",
51443
51695
  "defaultWindowMode": "background"
51444
51696
  },
51445
51697
  {
@@ -53321,6 +53573,7 @@
53321
53573
  "sourceFile": "xiaohongshu/watch.js",
53322
53574
  "navigateBefore": false,
53323
53575
  "siteSession": "persistent",
53576
+ "sessionKey": "watch",
53324
53577
  "defaultWindowMode": "background"
53325
53578
  },
53326
53579
  {
@@ -56950,6 +57203,7 @@
56950
57203
  "sourceFile": "zhihu/watch.js",
56951
57204
  "navigateBefore": false,
56952
57205
  "siteSession": "persistent",
57206
+ "sessionKey": "watch",
56953
57207
  "defaultWindowMode": "background"
56954
57208
  },
56955
57209
  {
@@ -56,4 +56,4 @@ import{existsSync as ee,mkdirSync as te,readFileSync as re,writeFileSync as ne}f
56
56
  WrappedES.prototype = NativeES.prototype;
57
57
  window.EventSource = WrappedES;
58
58
  }
59
- })()`}async function me(e){let t;try{t=await e.evaluate("(function(){ var o = window.__pp_stream || []; window.__pp_stream = []; return JSON.stringify(o); })()")}catch{return[]}let r;try{r=JSON.parse(typeof t==="string"?t:"[]")}catch{return[]}if(!Array.isArray(r))return[];return r.map((i)=>({kind:"page-hook",channel:i.channel,direction:i.direction,url:i.url,method:i.channel==="websocket"?"WS":"SSE",eventName:i.eventName||void 0,responseStatus:200,responseContentType:i.binary?"application/octet-stream":"text/plain",responsePreview:i.binary?"":String(i.data??""),timestamp:i.ts||Date.now()}))}export function registerWatch(e){if(!e?.site||!e?.domain||!e?.watchUrl||typeof e.parse!=="function")throw Error("registerWatch 需要 site、domain、watchUrl 和 parse");const t=e.name??"watch",r=e.site,i=Q(e.watchUrl);se({site:r,name:t,access:"read",description:e.description??`长驻监听${r}的新消息,旁听页面自身的实时通道并按 NDJSON 逐条输出(不对平台发轮询请求)`,domain:e.domain,strategy:oe.COOKIE,browser:!0,navigateBefore:!1,sessionKey:"watch",siteSession:"persistent",defaultWindowMode:"background",args:[{name:"interval",type:"int",required:!1,default:P,help:`本机取帧间隔毫秒(默认 ${P})。纯本机 IPC,不发平台请求`},{name:"reconcile-interval",type:"int",required:!1,default:0,help:`对账间隔秒(0 = 用该平台的默认值:可旁听实时通道的平台 ${B} 秒兜底,只能靠拉取的平台 ${j} 秒一轮);-1 = 彻底关闭对账`},{name:"heartbeat",type:"int",required:!1,default:D,help:`心跳间隔秒(默认 ${D}),无事件时输出一行 status 表示还活着`},{name:"events",type:"str",required:!1,help:"只输出这些事件类型,逗号分隔(如 dm,comment);默认全部"},{name:"once",type:"bool",required:!1,default:!1,help:"只跑一轮对账后退出,不长驻。用于验证解析器和补历史"},{name:"replay-seen",type:"bool",required:!1,default:!1,help:"忽略去重表,把解析到的事件全部重新输出(调试用,会导致重复触发)"},{name:"backfill",type:"bool",required:!1,default:!1,help:"首次运行时把已存在的历史消息也当成新事件输出(默认只静默登记,不触发)"},{name:"dump-frames",type:"bool",required:!1,default:!1,help:"侦察模式:把收到的每一条原始帧(url / 通道 / 内容片段)也打出来,用来给新平台摸清事件通道"},{name:"include-own",type:"bool",required:!1,default:!1,help:"连自己发出去的消息也当成事件(默认不算——否则自动回复会追着自己的回复自我触发)。调试解析用"}],columns:["event","id","sender","text","ts"],func:async(o,c)=>{const X=Math.max(200,Number(c.interval??P)),k=e.mode==="reconcile"?"reconcile":"stream",_=Number(c["reconcile-interval"]??0),b=_<0?0:_>0?_:k==="reconcile"?j:B,U=Math.max(0,Number(c.heartbeat??D)),x=c.once===!0||c.once==="true",S=c["replay-seen"]===!0||c["replay-seen"]==="true",T=c.backfill===!0||c.backfill==="true",Y=c["dump-frames"]===!0||c["dump-frames"]==="true",h=String(c.events??"").split(",").map((l)=>l.trim()).filter(Boolean);if(typeof o.startNetworkCapture!=="function"||typeof o.readNetworkCapture!=="function")throw new ce("当前浏览器通道不支持网络捕获,watch 无法工作(需要 CDP 或扩展路径)");const d=de(r,t),C={site:r,page:o,state:{},includeOwn:c["include-own"]===!0||c["include-own"]==="true"};let p=0,W=0,y=!1;const g=[];let m=d.firstRun&&!T&&!S,O=!1;const w=d.lastTs,F=(l)=>{const a=K(r,l);if(!a)return!1;if(h.length&&!h.includes(a.event))return!1;if(!S&&!fe(d,`${a.event}:${a.id}`))return!1;if(a.ts>d.lastTs)d.lastTs=a.ts;if(m){W++;return!1}if(!T&&!S&&w>0&&a.ts>0&&a.ts<=w){W++;return!1}if(!x)R(a);g.push(a);p++;return!0},Z=(l)=>{let a=0;for(const n of l){const s=K(r,n);if(!s)continue;if(h.length&&!h.includes(s.event))continue;if(d.set.has(`${s.event}:${s.id}`))continue;if(w>0&&s.ts>0&&s.ts<=w)continue;a++}return a},v=async(l,a=!1)=>{if(typeof e.reconcile!=="function")return;try{const n=V(await e.reconcile(o,C));if(a&&!m&&!T&&!S&&Z(n)>le){m=!0;O=!0}let s=0;for(const f of n)if(F(f))s++;u(r,"reconciled",{reason:l,scanned:n.length,new:s})}catch(n){if(n instanceof J)throw n;u(r,"reconcile_failed",{reason:l,error:n?.message??String(n)})}},I=async(l)=>{let a=!1;try{a=await o.startNetworkCapture(e.pattern??"")!==!1}catch{a=!1}const n=pe(e.pattern??"");y=!1;let s=!1;if(typeof o.cdp==="function")try{await o.cdp("Page.enable",{});await o.cdp("Page.addScriptToEvaluateOnNewDocument",{source:n});s=!0;y=!0}catch{}await o.goto(e.watchUrl,{waitUntil:s?"load":"none"});if(!s)try{await o.evaluate(n);y=!0;u(r,"page_hook_late",{note:"浏览器扩展版本较旧,旁听脚本只能在导航后注入;页面在此之前已建立的连接要等它重连才能被看到"})}catch(f){u(r,"page_hook_unavailable",{error:f?.message??String(f)})}if(!a){await o.startNetworkCapture(e.pattern??"");await o.goto(e.watchUrl,{waitUntil:"load"})}if(typeof e.onAttach==="function")await e.onAttach(o,C);u(r,"watching",{reason:l,mode:k,url:e.watchUrl,pattern:e.pattern??"",reconcile_seconds:b})};await I("start");await v("startup",!0);if(m){E(r,t,d);u(r,"seeded",{seeded:W,desynced:O,note:O?"去重表和收件箱对不上(上次没跑完 / 换了账号 / 表被裁剪),本轮把当前收件箱重新登记为「已见过」,不触发任何事件。从现在起只有新消息才会上报":"首次运行:已把当前收件箱登记为「已见过」,不触发任何事件。从现在起只有新消息才会上报(要补历史请加 --backfill)"});m=!1}if(x){E(r,t,d);return g}let A=!1;const N=()=>{A=!0};process.on("SIGINT",N);process.on("SIGTERM",N);let M=Date.now(),L=Date.now(),q=p;try{while(!A){await new Promise((n)=>setTimeout(n,X));if(A)break;let l=[];try{l=await o.readNetworkCapture()??[];if(y)l=l.concat(await me(o))}catch(n){u(r,"reattaching",{error:n?.message??String(n)});try{await I("capture_lost");await v("reattach")}catch(s){if(s instanceof J)throw s;u(r,"reattach_failed",{error:s?.message??String(s)})}continue}for(const n of l){if(Y)R({type:"frame",site:r,via:n?.kind==="page-hook"?"page-hook":"cdp",channel:n?.channel??"http",direction:n?.direction??"",method:n?.method??"",url:String(n?.url??"").slice(0,200),size:n?.responseBodyFullSize??String(n?.responsePreview??"").length,preview:String(n?.responsePreview??"").slice(0,600)});if(n?.direction==="sent")continue;let s;try{s=await e.parse(n,C)}catch(f){u(r,"parse_failed",{url:n?.url??"",error:f?.message??String(f)});continue}for(const f of V(s))F(f)}if(p!==q){E(r,t,d);q=p;L=Date.now()}if(i&&typeof o.getCurrentUrl==="function")try{const n=await o.getCurrentUrl();if(n&&Q(n)!==i){u(r,"page_drifted",{url:n});await I("drift");await v("drift")}}catch{}const a=Date.now();if(b&&a-M>=b*1000){M=a;await v("interval")}if(U&&a-L>=U*1000){L=a;u(r,"alive",{emitted:p})}}}finally{process.off("SIGINT",N);process.off("SIGTERM",N);E(r,t,d);u(r,"stopped",{emitted:p})}return g}})}export function frameJson(e){const t=e?.responsePreview;if(typeof t!=="string"||!t)return null;if(t.startsWith("base64:"))return null;try{return JSON.parse(t)}catch{return null}}
59
+ })()`}async function me(e){let t;try{t=await e.evaluate("(function(){ var o = window.__pp_stream || []; window.__pp_stream = []; return JSON.stringify(o); })()")}catch{return[]}let r;try{r=JSON.parse(typeof t==="string"?t:"[]")}catch{return[]}if(!Array.isArray(r))return[];return r.map((i)=>({kind:"page-hook",channel:i.channel,direction:i.direction,url:i.url,method:i.channel==="websocket"?"WS":"SSE",eventName:i.eventName||void 0,responseStatus:200,responseContentType:i.binary?"application/octet-stream":"text/plain",responsePreview:i.binary?"":String(i.data??""),timestamp:i.ts||Date.now()}))}export function registerWatch(e){if(!e?.site||!e?.domain||!e?.watchUrl||typeof e.parse!=="function")throw Error("registerWatch 需要 site、domain、watchUrl 和 parse");const t=e.name??"watch",r=e.site,i=Q(e.watchUrl);se({site:r,name:t,access:"read",description:e.description??`长驻监听${r}的新消息,旁听页面自身的实时通道并按 NDJSON 逐条输出(不对平台发轮询请求)`,domain:e.domain,strategy:oe.COOKIE,browser:!0,navigateBefore:!1,sessionKey:e.sessionKey??"watch",siteSession:"persistent",defaultWindowMode:"background",args:[{name:"interval",type:"int",required:!1,default:P,help:`本机取帧间隔毫秒(默认 ${P})。纯本机 IPC,不发平台请求`},{name:"reconcile-interval",type:"int",required:!1,default:0,help:`对账间隔秒(0 = 用该平台的默认值:${Number(e.reconcileSeconds)>0?`本平台 ${Number(e.reconcileSeconds)} 秒`:`可旁听实时通道的平台 ${B} 秒兜底,只能靠拉取的平台 ${j} 秒一轮`});-1 = 彻底关闭对账`},{name:"heartbeat",type:"int",required:!1,default:D,help:`心跳间隔秒(默认 ${D}),无事件时输出一行 status 表示还活着`},{name:"events",type:"str",required:!1,help:"只输出这些事件类型,逗号分隔(如 dm,comment);默认全部"},{name:"once",type:"bool",required:!1,default:!1,help:"只跑一轮对账后退出,不长驻。用于验证解析器和补历史"},{name:"replay-seen",type:"bool",required:!1,default:!1,help:"忽略去重表,把解析到的事件全部重新输出(调试用,会导致重复触发)"},{name:"backfill",type:"bool",required:!1,default:!1,help:"首次运行时把已存在的历史消息也当成新事件输出(默认只静默登记,不触发)"},{name:"dump-frames",type:"bool",required:!1,default:!1,help:"侦察模式:把收到的每一条原始帧(url / 通道 / 内容片段)也打出来,用来给新平台摸清事件通道"},{name:"include-own",type:"bool",required:!1,default:!1,help:"连自己发出去的消息也当成事件(默认不算——否则自动回复会追着自己的回复自我触发)。调试解析用"}],columns:["event","id","sender","text","ts"],func:async(o,c)=>{const X=Math.max(200,Number(c.interval??P)),k=e.mode==="reconcile"?"reconcile":"stream",b=Number(c["reconcile-interval"]??0),_=b<0?0:b>0?b:Number(e.reconcileSeconds)>0?Number(e.reconcileSeconds):k==="reconcile"?j:B,U=Math.max(0,Number(c.heartbeat??D)),x=c.once===!0||c.once==="true",S=c["replay-seen"]===!0||c["replay-seen"]==="true",T=c.backfill===!0||c.backfill==="true",Y=c["dump-frames"]===!0||c["dump-frames"]==="true",h=String(c.events??"").split(",").map((l)=>l.trim()).filter(Boolean);if(typeof o.startNetworkCapture!=="function"||typeof o.readNetworkCapture!=="function")throw new ce("当前浏览器通道不支持网络捕获,watch 无法工作(需要 CDP 或扩展路径)");const d=de(r,t),C={site:r,page:o,state:{},includeOwn:c["include-own"]===!0||c["include-own"]==="true"};let p=0,W=0,y=!1;const O=[];let m=d.firstRun&&!T&&!S,I=!1;const w=d.lastTs,F=(l)=>{const a=K(r,l);if(!a)return!1;if(h.length&&!h.includes(a.event))return!1;if(!S&&!fe(d,`${a.event}:${a.id}`))return!1;if(a.ts>d.lastTs)d.lastTs=a.ts;if(m){W++;return!1}if(!T&&!S&&w>0&&a.ts>0&&a.ts<=w){W++;return!1}if(!x)R(a);O.push(a);p++;return!0},Z=(l)=>{let a=0;for(const n of l){const s=K(r,n);if(!s)continue;if(h.length&&!h.includes(s.event))continue;if(d.set.has(`${s.event}:${s.id}`))continue;if(w>0&&s.ts>0&&s.ts<=w)continue;a++}return a},v=async(l,a=!1)=>{if(typeof e.reconcile!=="function")return;try{const n=V(await e.reconcile(o,C));if(a&&!m&&!T&&!S&&Z(n)>le){m=!0;I=!0}let s=0;for(const f of n)if(F(f))s++;u(r,"reconciled",{reason:l,scanned:n.length,new:s})}catch(n){if(n instanceof J)throw n;u(r,"reconcile_failed",{reason:l,error:n?.message??String(n)})}},g=async(l)=>{let a=!1;try{a=await o.startNetworkCapture(e.pattern??"")!==!1}catch{a=!1}const n=pe(e.pattern??"");y=!1;let s=!1;if(typeof o.cdp==="function")try{await o.cdp("Page.enable",{});await o.cdp("Page.addScriptToEvaluateOnNewDocument",{source:n});s=!0;y=!0}catch{}await o.goto(e.watchUrl,{waitUntil:s?"load":"none"});if(!s)try{await o.evaluate(n);y=!0;u(r,"page_hook_late",{note:"浏览器扩展版本较旧,旁听脚本只能在导航后注入;页面在此之前已建立的连接要等它重连才能被看到"})}catch(f){u(r,"page_hook_unavailable",{error:f?.message??String(f)})}if(!a){await o.startNetworkCapture(e.pattern??"");await o.goto(e.watchUrl,{waitUntil:"load"})}if(typeof e.onAttach==="function")await e.onAttach(o,C);u(r,"watching",{reason:l,mode:k,url:e.watchUrl,pattern:e.pattern??"",reconcile_seconds:_})};await g("start");await v("startup",!0);if(m){E(r,t,d);u(r,"seeded",{seeded:W,desynced:I,note:I?"去重表和收件箱对不上(上次没跑完 / 换了账号 / 表被裁剪),本轮把当前收件箱重新登记为「已见过」,不触发任何事件。从现在起只有新消息才会上报":"首次运行:已把当前收件箱登记为「已见过」,不触发任何事件。从现在起只有新消息才会上报(要补历史请加 --backfill)"});m=!1}if(x){E(r,t,d);return O}let A=!1;const N=()=>{A=!0};process.on("SIGINT",N);process.on("SIGTERM",N);let M=Date.now(),L=Date.now(),q=p;try{while(!A){await new Promise((n)=>setTimeout(n,X));if(A)break;let l=[];try{l=await o.readNetworkCapture()??[];if(y)l=l.concat(await me(o))}catch(n){u(r,"reattaching",{error:n?.message??String(n)});try{await g("capture_lost");await v("reattach")}catch(s){if(s instanceof J)throw s;u(r,"reattach_failed",{error:s?.message??String(s)})}continue}for(const n of l){if(Y)R({type:"frame",site:r,via:n?.kind==="page-hook"?"page-hook":"cdp",channel:n?.channel??"http",direction:n?.direction??"",method:n?.method??"",url:String(n?.url??"").slice(0,200),size:n?.responseBodyFullSize??String(n?.responsePreview??"").length,preview:String(n?.responsePreview??"").slice(0,600)});if(n?.direction==="sent")continue;let s;try{s=await e.parse(n,C)}catch(f){u(r,"parse_failed",{url:n?.url??"",error:f?.message??String(f)});continue}for(const f of V(s))F(f)}if(p!==q){E(r,t,d);q=p;L=Date.now()}if(i&&typeof o.getCurrentUrl==="function")try{const n=await o.getCurrentUrl();if(n&&Q(n)!==i){u(r,"page_drifted",{url:n});await g("drift");await v("drift")}}catch{}const a=Date.now();if(_&&a-M>=_*1000){M=a;await v("interval")}if(U&&a-L>=U*1000){L=a;u(r,"alive",{emitted:p})}}}finally{process.off("SIGINT",N);process.off("SIGTERM",N);E(r,t,d);u(r,"stopped",{emitted:p})}return O}})}export function frameJson(e){const t=e?.responsePreview;if(typeof t!=="string"||!t)return null;if(t.startsWith("base64:"))return null;try{return JSON.parse(t)}catch{return null}}
@@ -0,0 +1,13 @@
1
+ import{ArgumentError as C,AuthRequiredError as E,CommandExecutionError as b}from"@jackwener/opencli/errors";export const IM_URL="https://market.m.taobao.com/app/im/chat/index.html",IM_DOMAIN="market.m.taobao.com",IM_SESSION_KEY="watch";const L=(t)=>new Promise((a)=>setTimeout(a,t));function v(){const t=(e)=>{if(e&&typeof e==="object")return e;if(typeof e!=="string"||!e)return null;try{return JSON.parse(e)}catch(m){return null}},a=Array.from(document.querySelectorAll("iframe")).find((e)=>/chat-core/.test(e.src||""));let s=null;try{s=a?a.contentWindow:null;if(s)s.document}catch(e){s=null}const c=s?s.document:null,f=s?s._imsdk:null,_=()=>{if(!c)return null;const e=(o)=>{const g=o&&Object.keys(o).find((n)=>n.startsWith("__reactFiber"));let i=g?o[g]:null;for(let n=0;i&&n<40;n++,i=i.return){const d=i.memoizedProps;if(d&&d.dataManager&&d.dataManager.store)return d.dataManager}return null},m=c.querySelector(".conversation-item"),r=m?e(m):null;if(r)return r;const u=Array.from(c.querySelectorAll("body > div"));for(const o of u){const g=Object.keys(o).find((n)=>n.startsWith("__reactContainer"));if(!g)continue;const i=[o[g]];for(let n=0;n<i.length&&n<20000;n++){const d=i[n],y=d&&d.memoizedProps;if(y&&y.dataManager&&y.dataManager.store)return y.dataManager;if(d&&d.child)i.push(d.child);if(d&&d.sibling)i.push(d.sibling)}}return null},p=()=>{try{return String(f.getAuthService().getLoginUid()||"")}catch(e){return""}},l=(e)=>{const m={},r=/"?(\d+)"?\s*:\s*"(\w+)"/g;let u;while(u=r.exec(String(e||"")))m[u[1]]=u[2];return m},x=(e,m)=>{const r=e&&e.message?e.message:e;if(!r||!r.messageId)return null;const u=r.extension||{},o=r.content||{},g=o.contentType,i=o.custom||null;let n="";if(o.text&&typeof o.text.content==="string")n=o.text.content;else if(i)n=String(i.summary||i.title||"");else if(o.image||g===2)n="[图片]";else if(o.audio||g===3)n="[语音]";else if(o.video)n="[视频]";n=String(n).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g,"").trim();if(!n)n=String(r.searchableContent&&r.searchableContent.summary||"")||`[消息类型 ${g}]`;const d=String(r.sender&&r.sender.uid||"").split("@")[0],y=o.text&&o.text.extension||{},w=t(u.bizDataExt)||t(y.bizDataExt)||{},N=t(w.template_material_summary)||{},A=t(w.enter_shop_info)||{};let k=String(N.itemId||A.itemId||""),D=k?"card":"";if(!k){const O=JSON.stringify(o).match(/(?:item\.taobao\.com|detail\.tmall\.com|item\.htm)[^"\s]*?[?&]id=(\d{6,})/);if(O){k=O[1];D="link"}}return{message_id:String(r.messageId),ts:Number(r.createAt||0),cid:String(r.cid||""),sender_id:d,sender_nick:String(u.sender_nick||"").replace(/^cntaobao/,""),from_self:!!m&&d===m,content_type:g==null?null:Number(g),custom_type:i&&i.type!=null?Number(i.type):null,text:n,item_id:k,item_source:D,order_id:String(N.orderId||A.orderId||""),status:e&&e.msgStatus!=null?Number(e.msgStatus):null}},S=(e,m)=>{const r=e.conversationContent||{},u=r.lastMessageSummary||{},o=u.messageData&&u.messageData.originPaasMessageData;let g=o?x(o,m):null;if(!g&&u.code&&u.code.messageId){const w=String(u.sender&&u.sender.targetId||"");g={message_id:String(u.code.messageId),ts:Number(u.sendTime||0),cid:String(e.conversationCode||""),sender_id:w,sender_nick:"",from_self:!!m&&w===m,content_type:null,custom_type:null,text:String(u.content||""),item_id:"",item_source:"",order_id:"",status:null}}const i=e.ext||{},n=t(i.target)||{},d=l(i.accountRoleMap),y=String(e.conversationIdentifier&&e.conversationIdentifier.target&&e.conversationIdentifier.target.targetId||n.id||"");return{cid:String(e.conversationCode||""),peer_id:y,peer_nick:String(r.conversationName||n.dnick||""),self_role:d[m]||"",peer_role:d[y]||"",unread:Number(r.unReadNumber||0),modify_time:Number(e.modifyTime||0),last:g}};return{win:s,doc:c,sdk:f,parseJson:t,normMsg:x,normConv:S,readState:()=>{const e=String(location.href||""),m=c&&c.body?c.body.innerText:"",r={href:e,login_page:/login\.taobao\.com|login\.tmall\.com/.test(e),frame:!!c,sdk:!!f,login:!1,connection:null,self_id:"",self_nick:"",disconnected:/连接断开|连接中断/.test(m),store:!1,conversations:[]};if(!f)return r;try{const n=f.getAuthService();r.login=!!n.isLogin();r.connection=n.connectionStatus;r.self_nick=String(n.getLoginNick()||"")}catch(n){}r.self_id=p();const u=_();if(!u)return r;r.store=!0;const o=u.store,g=Array.isArray(o.conversationIdList)?o.conversationIdList:[],i=o.conversationMap&&o.conversationMap.store;for(const n of g){const d=i&&typeof i.get==="function"?i.get(n):null;if(d)r.conversations.push(S(d,r.self_id))}return r},selfId:p,findDataManager:_}}function I(t,a={}){return`(() => { const L = (${v.toString()})(); return (${t.toString()})(L, ${JSON.stringify(a)}); })()`}function M(t,a){if(!t||typeof t!=="object"||Array.isArray(t))throw new b(`淘宝旺旺 ${a}:页面返回的数据不对(${JSON.stringify(t)?.slice(0,200)})`);return t}export async function readState(t){return M(await t.evaluate(I((a)=>a.readState())),"readState")}export async function fetchMessages(t,a,s=10){const c=`(async () => { const L = (${v.toString()})(); const a = ${JSON.stringify({cid:a,count:s})};
2
+ if (!L.sdk) return { error: 'IM SDK 不在(页面没加载完或不是旺旺页)' };
3
+ const me = L.selfId();
4
+ try {
5
+ const r = await L.sdk.getOriginSdk().getMsgService().listPrevMsgs(a.cid, String(Number.MAX_SAFE_INTEGER), a.count);
6
+ const list = ((r && r.userMessageModels) || []).map((um) => L.normMsg(um, me)).filter(Boolean);
7
+ list.sort((x, y) => x.ts - y.ts);
8
+ return { messages: list, has_more: !!(r && r.hasMore) };
9
+ } catch (e) {
10
+ let detail = '';
11
+ try { detail = JSON.stringify(e); } catch (x) { detail = String(e); }
12
+ return { error: (e && e.message) || detail.slice(0, 500) || String(e) };
13
+ } })()`;return M(await t.evaluate(c),"listPrevMsgs")}export async function ensureImPage(t,{timeoutMs:a=20000,allowReload:s=!0}={}){let c="";try{c=String(await t.getCurrentUrl?.()||"")}catch{c=""}if(!c.startsWith(IM_URL))await t.goto(IM_URL);let f=!1;const _=()=>Date.now()+a;let p=_(),l=await readState(t);for(;;){if(l.login_page)throw new E("taobao.com","淘宝网页旺旺跳到了登录页:浏览器里的淘宝登录态失效了");if(l.sdk&&l.login&&l.store&&!l.disconnected)return l;if(l.disconnected&&s&&!f){f=!0;await t.goto(IM_URL);p=_()}else if(Date.now()>p)break;await L(500);l=await readState(t)}if(l.disconnected)throw new b("淘宝网页旺旺「连接断开」:同一个账号在别处又开了一个网页旺旺页面(只能有一个在线),刷新后仍然连不上");throw new b(`淘宝网页旺旺 ${Math.round(a/1000)} 秒内没有就绪:${JSON.stringify({frame:l.frame,sdk:l.sdk,login:l.login,connection:l.connection,store:l.store,href:l.href})}`)}export function resolveConversation(t,a){const s=String(a??"").trim();if(!s)throw new C("要给会话 id(如 2208502750464.1-123.1#11001@cntaobao)或对方 userId");if(s.includes("#")){const f=t.conversations.find((_)=>_.cid===s);if(!f)throw new b(`会话 ${s} 不在当前旺旺的会话列表里`);return f}if(!/^\d+$/.test(s))throw new C(`「${s}」既不是会话 id 也不是数字 userId`);const c=t.conversations.find((f)=>f.peer_id===s);if(!c)throw new b(`旺旺会话列表里没有和 userId ${s} 的会话(只回复已有会话,不新开会话)`);return c}export async function startSend(t,a,s,c){const f=I((_,p)=>{if(!_.sdk)return{ok:!1,reason:"IM SDK 不在"};const l=_.win.__pp_tb_send=_.win.__pp_tb_send||{};l[p.token]={state:"sending",at:Date.now()};const x=(S)=>{try{return JSON.parse(JSON.stringify(S))}catch(h){return String(S)}};try{_.sdk.getMessageService().sendMessages({sendMessages:[{conversationCode:p.cid,msgType:101,originalData:{text:p.text},summary:"",ext:{},localExt:{}}],dataCallback:(S)=>{const h=Array.isArray(S)?S[0]:S;l[p.token]={state:"sent",message_id:String(h&&h.code&&h.code.messageId||""),at:Date.now()}},errorCallBack:(S)=>{l[p.token]={state:"failed",error:x(S),at:Date.now()}}})}catch(S){l[p.token]={state:"failed",error:String(S&&S.message||S),at:Date.now()}}return{ok:!0}},{cid:a,text:s,token:c});return M(await t.evaluate(f),"send")}export async function readSendResult(t,a){const s=I((c,f)=>{return{result:(c.win&&c.win.__pp_tb_send||{})[f.token]||null}},{token:a});return M(await t.evaluate(s),"send-result")}export function fmtTime(t){if(!t)return"";return new Date(Number(t)+28800000).toISOString().replace("T"," ").slice(0,19)}export const __test__={pageLib:v,pageCall:I};
@@ -0,0 +1 @@
1
+ import{cli as i,Strategy as n}from"@jackwener/opencli/registry";import{clampInt as o}from"../_shared/common.js";import{IM_DOMAIN as m,IM_SESSION_KEY as d,ensureImPage as _,fmtTime as f}from"./im.js";i({site:"taobao",name:"inbox",access:"read",description:"列出淘宝旺旺最近的会话(对方 userId、我方是卖家还是买家、最后一条消息、商品 id)",domain:m,strategy:n.COOKIE,browser:!0,navigateBefore:!1,sessionKey:d,defaultWindowMode:"background",args:[{name:"limit",type:"int",default:20,help:"返回多少个会话(最多 100)"},{name:"as-seller",type:"bool",default:!1,help:"只看买家找店铺的会话(我方是卖家)"},{name:"unread-only",type:"bool",default:!1,help:"只看有未读的会话"}],columns:["rank","peer_nick","peer_user_id","self_role","last_message","last_from","last_time","unread","item_id","conversation_id"],func:async(r,t)=>{const l=o(t.limit,20,1,100);let a=(await _(r)).conversations;if(t["as-seller"]===!0||t["as-seller"]==="true")a=a.filter((e)=>e.self_role==="seller");if(t["unread-only"]===!0||t["unread-only"]==="true")a=a.filter((e)=>e.unread>0);return a.slice(0,l).map((e,s)=>({rank:s+1,peer_nick:e.peer_nick,peer_user_id:e.peer_id,self_role:e.self_role,last_message:e.last?.text??"",last_from:e.last?e.last.from_self?"me":"peer":"",last_time:f(e.last?.ts),unread:e.unread,item_id:e.last?.item_id??"",conversation_id:e.cid}))}});
@@ -0,0 +1 @@
1
+ import{CommandExecutionError as m}from"@jackwener/opencli/errors";import{cli as d,Strategy as c}from"@jackwener/opencli/registry";import{clampInt as _}from"../_shared/common.js";import{IM_DOMAIN as f,IM_SESSION_KEY as l,ensureImPage as u,fetchMessages as g,fmtTime as p,resolveConversation as I}from"./im.js";d({site:"taobao",name:"messages",access:"read",description:"读取淘宝旺旺某个会话最近的聊天记录(按会话 id 或对方 userId)",domain:f,strategy:c.COOKIE,browser:!0,navigateBefore:!1,sessionKey:l,defaultWindowMode:"background",args:[{name:"target",positional:!0,required:!0,help:"会话 id(…#11001@cntaobao)或对方 userId"},{name:"limit",type:"int",default:20,help:"最近多少条(最多 50)"}],columns:["index","time","from","sender_nick","text","item_id","message_id"],func:async(i,s)=>{const o=_(s.limit,20,1,50),a=await u(i),t=I(a,s.target),r=await g(i,t.cid,o);if(!Array.isArray(r.messages))throw new m(`读会话 ${t.cid} 的消息失败:${r.error||"页面没返回消息列表"}`);return r.messages.map((e,n)=>({index:n+1,time:p(e.ts),from:e.from_self?"me":"peer",sender_nick:e.sender_nick,text:e.text,item_id:e.item_id,message_id:e.message_id,conversation_id:t.cid,peer_user_id:t.peer_id,self_role:t.self_role}))}});
@@ -0,0 +1,3 @@
1
+ import{ArgumentError as h,CommandExecutionError as k}from"@jackwener/opencli/errors";import{cli as E,Strategy as I}from"@jackwener/opencli/registry";import{IM_DOMAIN as O,IM_SESSION_KEY as v,ensureImPage as N,fetchMessages as c,fmtTime as D,readSendResult as C,resolveConversation as K,startSend as P}from"./im.js";const b=500,x=(t)=>new Promise((o)=>setTimeout(o,t)),A=(t)=>String(t??"").replace(/\s+/g," ").trim();E({site:"taobao",name:"send",access:"write",description:"给淘宝旺旺里已有的会话发一条文字消息,发完回读确认送达(--dry-run 只核对会话和发送通道,不发)",domain:O,strategy:I.COOKIE,browser:!0,navigateBefore:!1,sessionKey:v,defaultWindowMode:"background",args:[{name:"target",positional:!0,required:!0,help:"会话 id(…#11001@cntaobao)或对方 userId"},{name:"text",fromFile:!0,help:"消息正文(或用 --file 指向正文文件)"},{name:"dry-run",type:"bool",default:!1,help:"只核对会话、登录和发送通道,不发送"},{name:"timeout",type:"int",default:20,help:"等送达回读的秒数"}],columns:["ok","status","reason","peer_nick","message_id","tail"],func:async(t,o)=>{const d=String(o.text??"").replace(/\r\n/g,`
2
+ `).trim(),f=o["dry-run"]===!0||o["dry-run"]==="true";if(!d&&!f)throw new h("正文是空的(--text 或 --file)");if(d.length>b)throw new h(`正文 ${d.length} 字,超过旺旺单条上限 ${b} 字,拆成几条发`);const S=await N(t),r=K(S,o.target),l={peer_nick:r.peer_nick,peer_user_id:r.peer_id,self_role:r.self_role,conversation_id:r.cid},m=(e)=>e.slice(-4).map((n)=>`${D(n.ts)} ${n.from_self?"我":"对方"}: ${n.text}`).join(`
3
+ `);if(f){const e=await c(t,r.cid,4);if(!Array.isArray(e.messages))throw new k(`读会话失败:${e.error}`);return[{ok:!0,status:"dry-run",reason:"会话、登录、发送通道都就绪,未发送",message_id:"",tail:m(e.messages),...l}]}const g=`pp${Date.now()}${Math.random().toString(36).slice(2,8)}`,u=Date.now(),_=await P(t,r.cid,d,g);if(!_.ok)throw new k(`发送没发起来:${_.reason}`);const w=u+Math.max(5,Number(o.timeout)||20)*1000;let i=null,s=null,a=[];while(Date.now()<w){await x(1000);i=(await C(t,g)).result||i;if(i?.state==="failed")return[{ok:!1,status:"failed",reason:`平台回发送失败:${JSON.stringify(i.error).slice(0,400)}`,message_id:"",tail:"",...l}];const e=await c(t,r.cid,6);if(!Array.isArray(e.messages))continue;a=e.messages;s=a.find((n)=>n.from_self&&(i?.message_id&&n.message_id===i.message_id||A(n.text)===A(d)&&n.ts>=u-60000));if(s)break}if(!s)return[{ok:!1,status:"unconfirmed",reason:`${Math.round((w-u)/1000)} 秒内没回读到这条消息(发送回调:${i?.state??"无"})`,message_id:i?.message_id||"",tail:m(a),...l}];await x(1500);const y=await c(t,r.cid,6);if(Array.isArray(y.messages))a=y.messages;const M=a.find((e)=>e.message_id===s.message_id)||s,p=a.filter((e)=>e.ts>=s.ts&&e.message_id!==s.message_id).find((e)=>/违规|违禁|发送失败|未发送|不当|拦截|无法发送/.test(e.text));if(M.status===-1)return[{ok:!1,status:"failed",reason:"平台把这条消息标成发送失败",message_id:s.message_id,tail:m(a),...l}];if(p)return[{ok:!1,status:"blocked",reason:`发出后平台提示:${p.text}`,message_id:s.message_id,tail:m(a),...l}];return[{ok:!0,status:"sent",reason:"已送达(回读到这条消息)",message_id:s.message_id,tail:m(a),...l}]}});
@@ -0,0 +1 @@
1
+ import{CommandExecutionError as y}from"@jackwener/opencli/errors";import{registerWatch as g}from"../_shared/watch.js";import{IM_DOMAIN as C,IM_SESSION_KEY as S,IM_URL as u,ensureImPage as E,fetchMessages as v,readState as I}from"./im.js";const h=/"lwp"\s*:\s*"\/s\//,k=[300,700,1000,1500],M=10,N=10,T=60;function l(t){return t.map((e)=>`${e.cid}:${e.last?.message_id??""}`).join("|")}function A(t,e,i,n){const s=!!e.sender_id&&e.sender_id!==i.self_id,_=n.state.itemByCid.get(t.cid),a=e.item_id||_?.item_id||"";return{event:"dm",id:e.message_id,ts:e.ts,conversation_id:t.cid,sender:{id:e.sender_id,name:s?e.sender_nick||t.peer_nick:e.sender_nick||i.self_nick},text:e.text,url:u,raw:{cid:t.cid,peer_user_id:t.peer_id,peer_nick:t.peer_nick,self_user_id:i.self_id,self_role:t.self_role,peer_role:t.peer_role,from_peer:s,sender_nick:e.sender_nick,item_id:a,item_id_source:e.item_id?`message:${e.item_source||"card"}`:a?"conversation":"",order_id:e.order_id||"",content_type:e.content_type,custom_type:e.custom_type,unread_count:t.unread}}}function B(t,e,i){for(const n of i){if(!n.item_id)continue;const s=t.state.itemByCid.get(e);if(!s||n.ts>=s.ts)t.state.itemByCid.set(e,{item_id:n.item_id,ts:n.ts})}}async function p(t,e,{fetch:i}){const n=t.state.lastByCid,s=[];let _=0;const a=[];for(const r of e.conversations){const d=r.last;if(!d||!d.message_id)continue;const c=n.get(r.cid);if(c&&c.message_id===d.message_id)continue;let f=[d];if(i&&_<N){_++;const o=await v(t.page,r.cid,M);if(Array.isArray(o.messages)&&o.messages.length)f=o.messages;else if(o.error)a.push(`${r.cid}: ${o.error}`)}B(t,r.cid,f);const m=c?c.ts:t.state.viewTs;for(const o of f){if(i&&m&&o.ts<m)continue;if(!i&&o.message_id!==d.message_id)continue;s.push(A(r,o,e,t))}n.set(r.cid,{message_id:d.message_id,ts:d.ts})}for(const r of e.conversations)if(r.last?.ts&&r.last.ts>t.state.viewTs)t.state.viewTs=r.last.ts;if(a.length&&!s.length)throw new y(`读会话消息失败:${a.join("; ").slice(0,500)}`);return s}function w(t){if(!t.state.lastByCid){t.state.lastByCid=new Map;t.state.itemByCid=new Map;t.state.viewTs=0}}g({site:"taobao",name:"watch",domain:C,description:"淘宝旺旺私信监听:旁听网页版旺旺自己的 WebSocket 推送,一有新消息就读已打开的会话,按 NDJSON 逐条输出(带对方 userId、商品 id、我方是买家还是卖家)",watchUrl:u,pattern:"wss-cntaobao.dingtalk.com",sessionKey:S,reconcileSeconds:T,parse:async(t,e)=>{if(!h.test(String(t?.responsePreview??"")))return null;w(e);const i=Number(t?.timestamp??0);if(i&&e.state.readStartedAt&&i<e.state.readStartedAt)return null;e.state.readStartedAt=Date.now();const n=e.state.fingerprint;let s=null;for(const _ of k){await new Promise((a)=>setTimeout(a,_));s=await I(e.page);if(s.store&&l(s.conversations)!==n)break}if(!s.store||!s.login)return null;e.state.fingerprint=l(s.conversations);return p(e,s,{fetch:e.state.lastByCid.size>0})},reconcile:async(t,e)=>{w(e);const i=await E(t);e.state.fingerprint=l(i.conversations);return p(e,i,{fetch:e.state.lastByCid.size>0})}});
@@ -1 +1 @@
1
- import{ArgumentError as w}from"@jackwener/opencli/errors";import{cli as l,Strategy as _}from"@jackwener/opencli/registry";import{HOME_URL as p,mtopRequest as g}from"./mtop.js";import{normalizeNumericId as c}from"./utils.js";const y=["ALL","WAITING_PAY","WAITING_SEND","WAITING_RECEIVE","WAITING_RATE","SUCCESS","REFUND"];function S(e){const r=String(e||"ALL").trim().toUpperCase();if(!y.includes(r))throw new w(`xianyu order --status must be one of: ${y.join(", ")}`);return r}function b(e,r){const t=String(e??"").trim();if(!t)return 1;if(!/^\d+$/.test(t)||Number(t)<1)throw new w(`xianyu order ${r} must be a positive integer`);return Number(t)}l({site:"xianyu",name:"order",access:"read",description:"列出卖家已售订单(可按状态筛选)",domain:"www.goofish.com",strategy:_.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"page",type:"int",default:1,help:"页码,从 1 开始"},{name:"page_size",type:"int",default:20,help:"每页条数"},{name:"status",default:"ALL",help:`订单状态筛选:${y.join(" / ")}`}],columns:["order_id","item_id","buyer_nick","status_str","total_price","create_time"],func:async(e,r)=>{const t=b(r.page,"page"),s=b(r.page_size,"page_size"),o=S(r.status);await e.goto(p);await e.wait(1);const u=(await g(e,"order list",{api:"mtop.taobao.idle.trade.merchant.sold.get",data:{pageNumber:t,rowsPerPage:s,orderIds:"",queryCode:o,orderSearchParam:"{}"},version:"1.0"})).module||{},d=(Array.isArray(u.items)?u.items:[]).map((a)=>{const i=a.order||a.orderInfo||a,m=a.buyer||a.buyerInfo||{},I=a.item||a.itemInfo||{};return{order_id:String(i.orderId||i.bizOrderId||i.id||""),item_id:String(I.itemId||I.id||""),item_title:I.title||"",buyer_id:String(m.userId||m.buyerId||""),buyer_nick:m.nick||m.userNick||"",status:i.status??i.orderStatus??"",status_str:i.statusStr||i.orderStatusStr||"",total_price:String(i.totalPrice||i.actualPrice||""),create_time:String(i.createTime||i.gmtCreate||"")}});return d.length?d:[{order_id:"",status_str:"(无已售订单)",total_price:"",buyer_nick:"",item_id:"",create_time:""}]}});l({site:"xianyu",name:"order-get",access:"read",description:"查询单个闲鱼订单详情",domain:"www.goofish.com",strategy:_.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"order_id",required:!0,positional:!0,help:"订单号 orderId"},{name:"role",default:"seller",help:"视角:seller / buyer"}],columns:["order_id","item_id","status_str","total_price","buyer_nick"],func:async(e,r)=>{const t=c(r.order_id,"order_id","2800000000000000000"),s=String(r.role||"seller").trim()==="buyer"?"buyer":"seller";await e.goto(p);await e.wait(1);const o=await g(e,"order get",{api:"mtop.idle.web.trade.order.detail",data:{orderId:t,role:s},version:"1.0"}),n=o.itemInfoDTO||{},u=o.buyerInfoDTO||{},f=o.sellerInfoDTO||{},d=o.priceInfoDTO||{},a=o.logisticsInfoDTO||{},i=o.statusInfoDTO||{};return[{order_id:t,role:s,item_id:String(n.itemId||""),title:n.title||"",main_pic:n.picUrl||"",quantity:Number(n.quantity||1),buyer_id:String(u.buyerId||""),buyer_nick:u.userNick||"",seller_id:String(f.sellerId||""),seller_nick:f.userNick||"",total_price:String(d.totalPrice||""),auction_price:String(d.auctionPrice||""),post_fee:String(d.postFee||""),status:i.orderStatus,status_str:i.orderStatusStr||"",logistic_no:a.logisticsNo||"",logistic_company:a.logisticsCompany||""}]}});l({site:"xianyu",name:"order-ship-dummy",access:"write",description:"虚拟发货(仅虚拟商品,无需物流单号)",domain:"www.goofish.com",strategy:_.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"order_id",required:!0,positional:!0,help:"订单号 orderId"},{name:"text",fromFile:!0,default:"",help:"发货附言(可选)"}],columns:["order_id","ok"],func:async(e,r)=>{const t=c(r.order_id,"order_id","2800000000000000000");await e.goto(p);await e.wait(1);const s=await g(e,"order ship-dummy",{api:"mtop.taobao.idle.logistic.consign.dummy",data:{orderId:t,tradeText:String(r.text||""),picList:[],newUnconsign:!0},version:"1.0"});return[{order_id:t,ok:!0,data:s}]}});l({site:"xianyu",name:"order-ship-freeshipping",access:"write",description:"包邮发货(拼团活动专用)",domain:"www.goofish.com",strategy:_.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"order_id",required:!0,positional:!0,help:"订单号 bizOrderId"},{name:"item_id",required:!0,help:"商品 itemId"},{name:"buyer_id",required:!0,help:"买家 userId"}],columns:["order_id","ok"],func:async(e,r)=>{const t=c(r.order_id,"order_id","2800000000000000000"),s=c(r.item_id,"item_id","1017530128619"),o=c(r.buyer_id,"buyer_id","2208502750464");await e.goto(p);await e.wait(1);const n=await g(e,"order ship-freeshipping",{api:"mtop.idle.groupon.activity.seller.freeshipping",data:{bizOrderId:t,itemId:Number(s),buyerId:Number(o)},version:"1.0"});return[{order_id:t,ok:!0,data:n}]}});export const __test__={normalizeStatus:S,normalizePage:b,ORDER_STATUS:y};
1
+ import{ArgumentError as b}from"@jackwener/opencli/errors";import{cli as l,Strategy as _}from"@jackwener/opencli/registry";import{HOME_URL as p,mtopRequest as g}from"./mtop.js";import{normalizeNumericId as c}from"./utils.js";const f=["ALL","NOT_PAY","NOT_SHIP","SHIPPED","REFUND","TRADE_CLOSED","TRADE_SUCCESS"];function h(e){const r=String(e||"ALL").trim().toUpperCase();if(!f.includes(r))throw new b(`xianyu order --status must be one of: ${f.join(", ")}`);return r}function w(e,r){const t=String(e??"").trim();if(!t)return 1;if(!/^\d+$/.test(t)||Number(t)<1)throw new b(`xianyu order ${r} must be a positive integer`);return Number(t)}function O(e){const r=JSON.stringify(e.rightVO||{}),t=r.match(/"orderId":"(\d{10,})"/)||r.match(/orderId=(\d{10,})/);if(t)return t[1];const i=(e.commonData||{}).orderId;return Number.isSafeInteger(i)?String(i):""}l({site:"xianyu",name:"order",access:"read",description:"列出卖家已售订单(可按状态筛选)",domain:"www.goofish.com",strategy:_.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"page",type:"int",default:1,help:"页码,从 1 开始"},{name:"page_size",type:"int",default:20,help:"每页条数"},{name:"status",default:"ALL",help:`订单状态筛选:${f.join(" / ")}`},{name:"order_id",default:"",help:"只查这一个订单号(order-get 的详情接口报「闲鱼太累了」时用它核实到账)"}],columns:["order_id","item_id","buyer_nick","status_str","total_price","pay_time"],func:async(e,r)=>{const t=w(r.page,"page"),i=w(r.page_size,"page_size"),o=h(r.status),n=String(r.order_id||"").trim();if(n&&!/^\d+$/.test(n))throw new b("xianyu order --order_id must be digits");await e.goto(p);await e.wait(1);const d=(await g(e,"order list",{api:"mtop.taobao.idle.trade.merchant.sold.get",data:{pageNumber:t,rowsPerPage:i,orderIds:n,queryCode:o,orderSearchParam:"{}"},version:"1.0"})).module||{},u=(Array.isArray(d.items)?d.items:[]).map((a)=>{const s=a.commonData||{},I=a.buyerInfoVO||{},S=a.priceVO||{};return{order_id:O(a),item_id:String(s.itemId||""),item_title:(a.itemVO||{}).title||"",buyer_id:String(I.buyerId||""),buyer_nick:I.userNick||"",status:s.orderStatus||"",status_str:s.orderStatus||"",in_refund:Boolean(s.inRefund),total_price:String(S.totalPrice||""),refund_fee:String(S.refundFee||""),create_time:String(s.createTime||""),pay_time:String(s.paySuccessTime||""),consign_time:String(s.consignTime||"")}});return u.length?u:[{order_id:"",status_str:"(无已售订单)",total_price:"",buyer_nick:"",item_id:"",create_time:""}]}});l({site:"xianyu",name:"order-get",access:"read",description:"查询单个闲鱼订单详情",domain:"www.goofish.com",strategy:_.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"order_id",required:!0,positional:!0,help:"订单号 orderId"},{name:"role",default:"seller",help:"视角:seller / buyer"}],columns:["order_id","item_id","status_str","total_price","buyer_nick"],func:async(e,r)=>{const t=c(r.order_id,"order_id","2800000000000000000"),i=String(r.role||"seller").trim()==="buyer"?"buyer":"seller";await e.goto(p);await e.wait(1);const o=await g(e,"order get",{api:"mtop.idle.web.trade.order.detail",data:{orderId:t,role:i},version:"1.0"}),n=o.itemInfoDTO||{},y=o.buyerInfoDTO||{},d=o.sellerInfoDTO||{},m=o.priceInfoDTO||{},u=o.logisticsInfoDTO||{},a=o.statusInfoDTO||{};return[{order_id:t,role:i,item_id:String(n.itemId||""),title:n.title||"",main_pic:n.picUrl||"",quantity:Number(n.quantity||1),buyer_id:String(y.buyerId||""),buyer_nick:y.userNick||"",seller_id:String(d.sellerId||""),seller_nick:d.userNick||"",total_price:String(m.totalPrice||""),auction_price:String(m.auctionPrice||""),post_fee:String(m.postFee||""),status:a.orderStatus,status_str:a.orderStatusStr||"",logistic_no:u.logisticsNo||"",logistic_company:u.logisticsCompany||""}]}});l({site:"xianyu",name:"order-ship-dummy",access:"write",description:"虚拟发货(仅虚拟商品,无需物流单号)",domain:"www.goofish.com",strategy:_.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"order_id",required:!0,positional:!0,help:"订单号 orderId"},{name:"text",fromFile:!0,default:"",help:"发货附言(可选)"}],columns:["order_id","ok"],func:async(e,r)=>{const t=c(r.order_id,"order_id","2800000000000000000");await e.goto(p);await e.wait(1);const i=await g(e,"order ship-dummy",{api:"mtop.taobao.idle.logistic.consign.dummy",data:{orderId:t,tradeText:String(r.text||""),picList:[],newUnconsign:!0},version:"1.0"});return[{order_id:t,ok:!0,data:i}]}});l({site:"xianyu",name:"order-ship-freeshipping",access:"write",description:"包邮发货(拼团活动专用)",domain:"www.goofish.com",strategy:_.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"order_id",required:!0,positional:!0,help:"订单号 bizOrderId"},{name:"item_id",required:!0,help:"商品 itemId"},{name:"buyer_id",required:!0,help:"买家 userId"}],columns:["order_id","ok"],func:async(e,r)=>{const t=c(r.order_id,"order_id","2800000000000000000"),i=c(r.item_id,"item_id","1017530128619"),o=c(r.buyer_id,"buyer_id","2208502750464");await e.goto(p);await e.wait(1);const n=await g(e,"order ship-freeshipping",{api:"mtop.idle.groupon.activity.seller.freeshipping",data:{bizOrderId:t,itemId:Number(i),buyerId:Number(o)},version:"1.0"});return[{order_id:t,ok:!0,data:n}]}});export const __test__={normalizeStatus:h,normalizePage:w,ORDER_STATUS:f};
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import*as c from"node:fs";import*as a from"node:path";import{fileURLToPath as x,pathToFileURL as S}from"node:url";import{getErrorMessage as F}from"./errors.js";import{fullName as R,getRegistry as A}from"./registry.js";import{findPackageRoot as C,getCliManifestPath as j}from"./package-paths.js";import{isRecord as M}from"./utils.js";const y=C(x(import.meta.url)),w=a.join(y,"clis"),h=j(w),k=/\bcli\s*\(|\bregisterSiteAuthCommands\s*\(|\bregisterArticleLogin\s*\(|\bregisterTokenAuth\s*\(|\bregisterWatch\s*\(|\bmake[A-Z]\w*Command\s*\(/;export class ManifestImportError extends Error{filePath;cause;constructor(e,t){super(`failed to scan ${e}: ${F(t)}`);this.filePath=e;this.cause=t;this.name="ManifestImportError"}}function I(e){return e.map((t)=>({name:t.name,type:t.type??"str",default:t.default,required:!!t.required,valueRequired:!!t.valueRequired||void 0,positional:t.positional||void 0,help:t.help??"",choices:t.choices,fromFile:t.fromFile||void 0,requiredUnlessFile:t.requiredUnlessFile||void 0,generatedFileFor:t.generatedFileFor||void 0}))}function N(e,t){const r=a.basename(e,a.extname(e));return`${t}/${r}.js`}export function normalizeManifestPath(e){return e.replace(/\\/g,"/")}function O(e,t){return normalizeManifestPath(a.relative(t,e))}function T(e,t){return M(e)&&typeof e.site==="string"&&e.site===t&&typeof e.name==="string"&&(e.access==="read"||e.access==="write")&&Array.isArray(e.args)}function E(e,t,r){return{site:e.site,name:e.name,aliases:e.aliases,description:e.description??"",access:e.access,example:e.example,domain:e.domain,strategy:(e.strategy??"public").toString().toLowerCase(),browser:e.browser??!0,args:I(e.args),columns:e.columns,defaultFormat:e.defaultFormat,type:"js",modulePath:t,sourceFile:r,navigateBefore:e.navigateBefore,siteSession:e.siteSession,defaultWindowMode:e.defaultWindowMode}}export async function loadManifestEntries(e,t,r=(u)=>import(u),n=w){let u;try{u=c.readFileSync(e,"utf-8")}catch(s){throw new ManifestImportError(e,s)}if(!k.test(u))return[];try{const s=N(e,t),o=A(),i=new Map(o.entries()),d=await r(S(e).href),l=Object.values(M(d)?d:{}).filter((f)=>T(f,t)),p=l.length>0?l:[...o.entries()].filter(([f,m])=>{if(m.site!==t)return!1;const v=i.get(f);return!v||v!==m}).map(([,f])=>f),g=O(e,n),b=new Set;return p.filter((f)=>{const m=R(f);if(b.has(m))return!1;b.add(m);return!0}).sort((f,m)=>f.name.localeCompare(m.name)).map((f)=>E(f,s,g))}catch(s){throw new ManifestImportError(e,s)}}export async function scanClisDir(e,t=(r)=>import(r)){const r=new Map,n=[];if(!c.existsSync(e))return{entries:[],failures:n};for(const s of c.readdirSync(e)){const o=a.join(e,s);if(!c.statSync(o).isDirectory())continue;for(const i of c.readdirSync(o))if(i.endsWith(".js")&&!i.endsWith(".d.js")&&!i.endsWith(".test.js")&&i!=="index.js"){const d=a.join(o,i);try{const l=await loadManifestEntries(d,s,t,e);for(const p of l){const g=`${p.site}/${p.name}`;r.set(g,p)}}catch(l){if(l instanceof ManifestImportError){n.push(l);continue}throw l}}}return{entries:[...r.values()].sort((s,o)=>s.site.localeCompare(o.site)||s.name.localeCompare(o.name)),failures:n}}export async function buildManifest(){return scanClisDir(w)}export function serializeManifest(e){return`${JSON.stringify(e,null,2)}
2
+ import*as c from"node:fs";import*as a from"node:path";import{fileURLToPath as x,pathToFileURL as S}from"node:url";import{getErrorMessage as F}from"./errors.js";import{fullName as R,getRegistry as A}from"./registry.js";import{findPackageRoot as C,getCliManifestPath as j}from"./package-paths.js";import{isRecord as M}from"./utils.js";const y=C(x(import.meta.url)),w=a.join(y,"clis"),h=j(w),k=/\bcli\s*\(|\bregisterSiteAuthCommands\s*\(|\bregisterArticleLogin\s*\(|\bregisterTokenAuth\s*\(|\bregisterWatch\s*\(|\bmake[A-Z]\w*Command\s*\(/;export class ManifestImportError extends Error{filePath;cause;constructor(e,t){super(`failed to scan ${e}: ${F(t)}`);this.filePath=e;this.cause=t;this.name="ManifestImportError"}}function I(e){return e.map((t)=>({name:t.name,type:t.type??"str",default:t.default,required:!!t.required,valueRequired:!!t.valueRequired||void 0,positional:t.positional||void 0,help:t.help??"",choices:t.choices,fromFile:t.fromFile||void 0,requiredUnlessFile:t.requiredUnlessFile||void 0,generatedFileFor:t.generatedFileFor||void 0}))}function N(e,t){const r=a.basename(e,a.extname(e));return`${t}/${r}.js`}export function normalizeManifestPath(e){return e.replace(/\\/g,"/")}function O(e,t){return normalizeManifestPath(a.relative(t,e))}function T(e,t){return M(e)&&typeof e.site==="string"&&e.site===t&&typeof e.name==="string"&&(e.access==="read"||e.access==="write")&&Array.isArray(e.args)}function E(e,t,r){return{site:e.site,name:e.name,aliases:e.aliases,description:e.description??"",access:e.access,example:e.example,domain:e.domain,strategy:(e.strategy??"public").toString().toLowerCase(),browser:e.browser??!0,args:I(e.args),columns:e.columns,defaultFormat:e.defaultFormat,type:"js",modulePath:t,sourceFile:r,navigateBefore:e.navigateBefore,siteSession:e.siteSession,sessionKey:e.sessionKey,defaultWindowMode:e.defaultWindowMode}}export async function loadManifestEntries(e,t,r=(u)=>import(u),n=w){let u;try{u=c.readFileSync(e,"utf-8")}catch(s){throw new ManifestImportError(e,s)}if(!k.test(u))return[];try{const s=N(e,t),o=A(),i=new Map(o.entries()),d=await r(S(e).href),l=Object.values(M(d)?d:{}).filter((f)=>T(f,t)),p=l.length>0?l:[...o.entries()].filter(([f,m])=>{if(m.site!==t)return!1;const v=i.get(f);return!v||v!==m}).map(([,f])=>f),g=O(e,n),b=new Set;return p.filter((f)=>{const m=R(f);if(b.has(m))return!1;b.add(m);return!0}).sort((f,m)=>f.name.localeCompare(m.name)).map((f)=>E(f,s,g))}catch(s){throw new ManifestImportError(e,s)}}export async function scanClisDir(e,t=(r)=>import(r)){const r=new Map,n=[];if(!c.existsSync(e))return{entries:[],failures:n};for(const s of c.readdirSync(e)){const o=a.join(e,s);if(!c.statSync(o).isDirectory())continue;for(const i of c.readdirSync(o))if(i.endsWith(".js")&&!i.endsWith(".d.js")&&!i.endsWith(".test.js")&&i!=="index.js"){const d=a.join(o,i);try{const l=await loadManifestEntries(d,s,t,e);for(const p of l){const g=`${p.site}/${p.name}`;r.set(g,p)}}catch(l){if(l instanceof ManifestImportError){n.push(l);continue}throw l}}}return{entries:[...r.values()].sort((s,o)=>s.site.localeCompare(o.site)||s.name.localeCompare(o.name)),failures:n}}export async function buildManifest(){return scanClisDir(w)}export function serializeManifest(e){return`${JSON.stringify(e,null,2)}
3
3
  `}export function findManifestMetadataIssues(e){const t=[];for(const r of e){if(!Array.isArray(r.args))continue;for(const n of r.args){if(!n.positional)continue;if((typeof n.help==="string"?n.help.trim():"")==="")t.push({site:r.site,command:r.name,arg:n.name,sourceFile:r.sourceFile,reason:"positional arg missing non-empty `help` text"})}}return t}export function diffRemovedEntries(e,t){const r=new Set(t.map((n)=>`${n.site}/${n.name}`));return e.map((n)=>`${n.site}/${n.name}`).filter((n)=>!r.has(n)).sort()}export function parseBuildManifestArgs(e){let t=0;for(const r of e){if(r==="--allow-removals"){t=Number.POSITIVE_INFINITY;continue}const n=r.match(/^--allow-removals=(\d+)$/);if(n){t=Number.parseInt(n[1],10);continue}}return{allowRemovals:t}}function P(e){try{if(!c.existsSync(e))return null;const t=c.readFileSync(e,"utf-8"),r=JSON.parse(t);return Array.isArray(r)?r:null}catch{return null}}async function L(){if(x(import.meta.url).includes(`${a.sep}dist${a.sep}`)){process.stderr.write(`❌ Refusing to run build-manifest from dist/.
4
4
  `+" Stale compiled output silently drops adapters that import renamed/removed exports.\n Run `npm run build-manifest` (or `tsx src/build-manifest.ts`) from the source tree instead.\n");process.exit(1)}const e=parseBuildManifestArgs(process.argv.slice(2)),{entries:t,failures:r}=await buildManifest();if(r.length>0){process.stderr.write(`❌ ${r.length} adapter(s) failed to load:
5
5
  `);for(const s of r){const o=a.relative(y,s.filePath)||s.filePath;process.stderr.write(` - ${o}: ${F(s.cause)}
@@ -1,2 +1,2 @@
1
- import*as i from"node:fs";import*as R from"node:os";import*as c from"node:path";import{fileURLToPath as S,pathToFileURL as p}from"node:url";import A from"js-yaml";import{Strategy as y,registerCommand as E}from"./registry.js";import{getErrorMessage as m}from"./errors.js";import{log as u}from"./logger.js";import{findPackageRoot as k,getCliManifestPath as I}from"./package-paths.js";import{migrateLegacyUserRuntime as x}from"./user-runtime.js";x();export const USER_OPENCLI_DIR=c.join(R.homedir(),".ppcli"),USER_CLIS_DIR=c.join(USER_OPENCLI_DIR,"clis"),PLUGINS_DIR=c.join(USER_OPENCLI_DIR,"plugins");const M=/\b(?:cli|registerSiteAuthCommands|onStartup|onBeforeExecute|onAfterExecute)\s*\(/,h=/\.ya?ml$/i,g=new Set,F=new Map;function T(e,r=y.COOKIE){if(!e)return r;const t=e.toUpperCase();return y[t]??r}const D=k(S(import.meta.url));export async function ensureUserCliCompatShims(e=USER_OPENCLI_DIR){await i.promises.mkdir(e,{recursive:!0});const r=c.join(e,"package.json"),t=`${JSON.stringify({name:"opencli-user-runtime",private:!0,type:"module"},null,2)}
2
- `;try{if(await i.promises.readFile(r,"utf-8")!==t)await i.promises.writeFile(r,t,"utf-8")}catch{await i.promises.writeFile(r,t,"utf-8")}const a=D,n=c.join(e,"node_modules","@jackwener"),s=c.join(n,"opencli");try{let o=!0;try{if(await i.promises.readlink(s)===a)o=!1}catch{}if(o){await i.promises.mkdir(n,{recursive:!0});try{await i.promises.rm(s,{recursive:!0,force:!0})}catch{}const l=process.platform==="win32"?"junction":"dir";await i.promises.symlink(a,s,l)}}catch(o){u.warn(`Could not create symlink at ${s}: ${m(o)}`)}}export async function ensureUserAdapters(){await i.promises.mkdir(USER_CLIS_DIR,{recursive:!0})}export async function discoverClis(...e){for(const r of e){const t=I(r);try{await i.promises.access(t);const a=await L(t,r);if(a){await j(r,a.manifestFiles);continue}}catch{}await W(r)}}async function L(e,r){try{const t=await i.promises.readFile(e,"utf-8"),a=JSON.parse(t),n=new Set;for(const s of a){if(!s.modulePath)continue;const o=c.resolve(r,s.modulePath);n.add(o);if(s.sourceFile)n.add(c.resolve(r,s.sourceFile));const l={site:s.site,name:s.name,aliases:s.aliases,description:s.description??"",access:s.access,example:s.example,domain:s.domain,strategy:T(s.strategy),browser:s.browser,args:s.args??[],columns:s.columns,defaultFormat:s.defaultFormat,pipeline:s.pipeline,source:s.sourceFile?c.resolve(r,s.sourceFile):o,navigateBefore:s.navigateBefore,siteSession:s.siteSession,defaultWindowMode:s.defaultWindowMode,_lazy:!0,_modulePath:o};E(l)}return{manifestFiles:n}}catch(t){u.warn(`Failed to load manifest ${e}: ${m(t)}`);return null}}async function W(e){try{await i.promises.access(e)}catch{return}const r=await i.promises.readdir(e,{withFileTypes:!0});await j(e);const t=r.filter((a)=>a.isDirectory()).map(async(a)=>{const n=a.name,s=c.join(e,n),o=await i.promises.readdir(s);await Promise.all(o.map(async(l)=>{const d=c.join(s,l);if(f(l))return;if(l.endsWith(".ts")&&!l.endsWith(".d.ts")&&!l.endsWith(".test.ts")){u.warn(`Ignoring TypeScript adapter ${d} — .ts adapters are no longer loaded. Rename to .js or convert to JavaScript.`);return}if(l.endsWith(".js")&&!l.endsWith(".d.js")&&!l.endsWith(".test.js")){if(!await w(d))return;await import(p(d).href).catch((C)=>{u.warn(`Failed to load module ${d}: ${m(C)}`)})}}))});await Promise.all(t)}export async function discoverPlugins(){try{await i.promises.access(PLUGINS_DIR)}catch{return}const e=(await i.promises.readdir(PLUGINS_DIR,{withFileTypes:!0})).sort((t,a)=>t.name.localeCompare(a.name)),r=[];for(const t of e){const a=c.join(PLUGINS_DIR,t.name);if(await Y(t,a))r.push({entry:t,pluginDir:a})}for(const{entry:t,pluginDir:a}of r)await P(a,{owner:`plugin ${t.name}`,pluginMode:!0});await Promise.all(r.map(async({entry:t,pluginDir:a})=>{await _(a,t.name)}))}async function _(e,r){let t;try{t=await i.promises.readdir(e)}catch(n){const s=n.code;if(s!=="ENOENT"&&s!=="ENOTDIR")u.warn(`Failed to scan plugin directory ${e}: ${m(n)}`);return}const a=new Set(t);await Promise.all(t.map(async(n)=>{const s=c.join(e,n);if(f(n))return;if(n.endsWith(".js")&&!n.endsWith(".d.js")){if(!await w(s))return;await import(p(s).href).catch((o)=>{u.warn(`Plugin ${r}/${n}: ${m(o)}`)})}else if(n.endsWith(".ts")&&!n.endsWith(".d.ts")&&!n.endsWith(".test.ts")){const o=n.replace(/\.ts$/,".js");if(a.has(o))return;u.warn(`Plugin ${r}/${n}: no compiled .js found. Run "ppcli plugin update ${r}" to re-transpile, or install esbuild.`)}}))}async function j(e,r){let t;try{t=await i.promises.readdir(e,{withFileTypes:!0})}catch{return}for(const a of t.filter((n)=>n.isDirectory()).sort((n,s)=>n.name.localeCompare(s.name))){const n=c.join(e,a.name);await P(n,{owner:`site ${a.name}`,manifestFiles:r})}}async function P(e,r){let t;try{t=await i.promises.readdir(e)}catch(s){u.warn(`Could not inspect YAML adapters for ${r.owner}: ${m(s)}`);return}const a=new Set(t),n=[];for(const s of t.filter(f).sort()){const o=c.join(e,s);if(!await N(o,r.pluginMode===!0))continue;if(await U(e,s,a,r.manifestFiles))continue;const l=await v(o);if(g.has(l))continue;g.add(l);n.push(s)}if(n.length===0)return;u.warn(`Ignoring ${n.length} YAML adapter${n.length===1?"":"s"} for ${r.owner}: ${n.join(", ")}. .yaml/.yml adapters are no longer loaded; convert them to .js with the cli() API.`)}function f(e){return h.test(e)}async function U(e,r,t,a){const n=r.replace(h,".js");if(!t.has(n))return!1;const s=c.resolve(e,n);if(a)return a.has(s);return b(s)}async function N(e,r){let t;try{t=await i.promises.readFile(e,"utf-8")}catch{return!r}let a;try{a=A.load(t)}catch{return!r}if(!a||typeof a!=="object"||Array.isArray(a))return!1;const n=a;if(typeof n.name!=="string"||!n.name.trim())return!1;return["site","description","access","strategy","browser","domain","args","columns","pipeline","url","request","steps","extract","output","func","method","headers"].some((o)=>(o in n))}async function v(e){try{return await i.promises.realpath(e)}catch{return c.resolve(e)}}async function O(e){const r=await v(e);try{const t=await i.promises.stat(e);return`${r}:${t.mtimeMs}:${t.size}`}catch{return r}}async function b(e){const r=await O(e);let t=F.get(r);if(!t){t=(async()=>{if(!await w(e))return!1;try{await import(p(e).href);return!0}catch(a){u.warn(`Failed to load module ${e}: ${m(a)}`);return!1}})();F.set(r,t)}return t}async function w(e){try{const r=await i.promises.readFile(e,"utf-8");return M.test(r)}catch(r){u.warn(`Failed to inspect module ${e}: ${m(r)}`);return!1}}async function Y(e,r){if(e.isDirectory())return!0;if(!e.isSymbolicLink())return!1;try{return(await i.promises.stat(r)).isDirectory()}catch(t){const a=t.code;if(a!=="ENOENT"&&a!=="ENOTDIR")u.warn(`Failed to inspect plugin link ${r}: ${m(t)}`);return!1}}
1
+ import*as i from"node:fs";import*as R from"node:os";import*as c from"node:path";import{fileURLToPath as S,pathToFileURL as p}from"node:url";import A from"js-yaml";import{Strategy as y,registerCommand as E}from"./registry.js";import{getErrorMessage as m}from"./errors.js";import{log as u}from"./logger.js";import{findPackageRoot as k,getCliManifestPath as I}from"./package-paths.js";import{migrateLegacyUserRuntime as x}from"./user-runtime.js";x();export const USER_OPENCLI_DIR=c.join(R.homedir(),".ppcli"),USER_CLIS_DIR=c.join(USER_OPENCLI_DIR,"clis"),PLUGINS_DIR=c.join(USER_OPENCLI_DIR,"plugins");const M=/\b(?:cli|registerSiteAuthCommands|registerWatch|onStartup|onBeforeExecute|onAfterExecute)\s*\(/,h=/\.ya?ml$/i,g=new Set,F=new Map;function T(e,r=y.COOKIE){if(!e)return r;const t=e.toUpperCase();return y[t]??r}const D=k(S(import.meta.url));export async function ensureUserCliCompatShims(e=USER_OPENCLI_DIR){await i.promises.mkdir(e,{recursive:!0});const r=c.join(e,"package.json"),t=`${JSON.stringify({name:"opencli-user-runtime",private:!0,type:"module"},null,2)}
2
+ `;try{if(await i.promises.readFile(r,"utf-8")!==t)await i.promises.writeFile(r,t,"utf-8")}catch{await i.promises.writeFile(r,t,"utf-8")}const a=D,n=c.join(e,"node_modules","@jackwener"),s=c.join(n,"opencli");try{let o=!0;try{if(await i.promises.readlink(s)===a)o=!1}catch{}if(o){await i.promises.mkdir(n,{recursive:!0});try{await i.promises.rm(s,{recursive:!0,force:!0})}catch{}const l=process.platform==="win32"?"junction":"dir";await i.promises.symlink(a,s,l)}}catch(o){u.warn(`Could not create symlink at ${s}: ${m(o)}`)}}export async function ensureUserAdapters(){await i.promises.mkdir(USER_CLIS_DIR,{recursive:!0})}export async function discoverClis(...e){for(const r of e){const t=I(r);try{await i.promises.access(t);const a=await W(t,r);if(a){await j(r,a.manifestFiles);continue}}catch{}await L(r)}}async function W(e,r){try{const t=await i.promises.readFile(e,"utf-8"),a=JSON.parse(t),n=new Set;for(const s of a){if(!s.modulePath)continue;const o=c.resolve(r,s.modulePath);n.add(o);if(s.sourceFile)n.add(c.resolve(r,s.sourceFile));const l={site:s.site,name:s.name,aliases:s.aliases,description:s.description??"",access:s.access,example:s.example,domain:s.domain,strategy:T(s.strategy),browser:s.browser,args:s.args??[],columns:s.columns,defaultFormat:s.defaultFormat,pipeline:s.pipeline,source:s.sourceFile?c.resolve(r,s.sourceFile):o,navigateBefore:s.navigateBefore,siteSession:s.siteSession,sessionKey:s.sessionKey,defaultWindowMode:s.defaultWindowMode,_lazy:!0,_modulePath:o};E(l)}return{manifestFiles:n}}catch(t){u.warn(`Failed to load manifest ${e}: ${m(t)}`);return null}}async function L(e){try{await i.promises.access(e)}catch{return}const r=await i.promises.readdir(e,{withFileTypes:!0});await j(e);const t=r.filter((a)=>a.isDirectory()).map(async(a)=>{const n=a.name,s=c.join(e,n),o=await i.promises.readdir(s);await Promise.all(o.map(async(l)=>{const d=c.join(s,l);if(f(l))return;if(l.endsWith(".ts")&&!l.endsWith(".d.ts")&&!l.endsWith(".test.ts")){u.warn(`Ignoring TypeScript adapter ${d} — .ts adapters are no longer loaded. Rename to .js or convert to JavaScript.`);return}if(l.endsWith(".js")&&!l.endsWith(".d.js")&&!l.endsWith(".test.js")){if(!await w(d))return;await import(p(d).href).catch((C)=>{u.warn(`Failed to load module ${d}: ${m(C)}`)})}}))});await Promise.all(t)}export async function discoverPlugins(){try{await i.promises.access(PLUGINS_DIR)}catch{return}const e=(await i.promises.readdir(PLUGINS_DIR,{withFileTypes:!0})).sort((t,a)=>t.name.localeCompare(a.name)),r=[];for(const t of e){const a=c.join(PLUGINS_DIR,t.name);if(await b(t,a))r.push({entry:t,pluginDir:a})}for(const{entry:t,pluginDir:a}of r)await P(a,{owner:`plugin ${t.name}`,pluginMode:!0});await Promise.all(r.map(async({entry:t,pluginDir:a})=>{await _(a,t.name)}))}async function _(e,r){let t;try{t=await i.promises.readdir(e)}catch(n){const s=n.code;if(s!=="ENOENT"&&s!=="ENOTDIR")u.warn(`Failed to scan plugin directory ${e}: ${m(n)}`);return}const a=new Set(t);await Promise.all(t.map(async(n)=>{const s=c.join(e,n);if(f(n))return;if(n.endsWith(".js")&&!n.endsWith(".d.js")){if(!await w(s))return;await import(p(s).href).catch((o)=>{u.warn(`Plugin ${r}/${n}: ${m(o)}`)})}else if(n.endsWith(".ts")&&!n.endsWith(".d.ts")&&!n.endsWith(".test.ts")){const o=n.replace(/\.ts$/,".js");if(a.has(o))return;u.warn(`Plugin ${r}/${n}: no compiled .js found. Run "ppcli plugin update ${r}" to re-transpile, or install esbuild.`)}}))}async function j(e,r){let t;try{t=await i.promises.readdir(e,{withFileTypes:!0})}catch{return}for(const a of t.filter((n)=>n.isDirectory()).sort((n,s)=>n.name.localeCompare(s.name))){const n=c.join(e,a.name);await P(n,{owner:`site ${a.name}`,manifestFiles:r})}}async function P(e,r){let t;try{t=await i.promises.readdir(e)}catch(s){u.warn(`Could not inspect YAML adapters for ${r.owner}: ${m(s)}`);return}const a=new Set(t),n=[];for(const s of t.filter(f).sort()){const o=c.join(e,s);if(!await N(o,r.pluginMode===!0))continue;if(await U(e,s,a,r.manifestFiles))continue;const l=await v(o);if(g.has(l))continue;g.add(l);n.push(s)}if(n.length===0)return;u.warn(`Ignoring ${n.length} YAML adapter${n.length===1?"":"s"} for ${r.owner}: ${n.join(", ")}. .yaml/.yml adapters are no longer loaded; convert them to .js with the cli() API.`)}function f(e){return h.test(e)}async function U(e,r,t,a){const n=r.replace(h,".js");if(!t.has(n))return!1;const s=c.resolve(e,n);if(a)return a.has(s);return K(s)}async function N(e,r){let t;try{t=await i.promises.readFile(e,"utf-8")}catch{return!r}let a;try{a=A.load(t)}catch{return!r}if(!a||typeof a!=="object"||Array.isArray(a))return!1;const n=a;if(typeof n.name!=="string"||!n.name.trim())return!1;return["site","description","access","strategy","browser","domain","args","columns","pipeline","url","request","steps","extract","output","func","method","headers"].some((o)=>(o in n))}async function v(e){try{return await i.promises.realpath(e)}catch{return c.resolve(e)}}async function O(e){const r=await v(e);try{const t=await i.promises.stat(e);return`${r}:${t.mtimeMs}:${t.size}`}catch{return r}}async function K(e){const r=await O(e);let t=F.get(r);if(!t){t=(async()=>{if(!await w(e))return!1;try{await import(p(e).href);return!0}catch(a){u.warn(`Failed to load module ${e}: ${m(a)}`);return!1}})();F.set(r,t)}return t}async function w(e){try{const r=await i.promises.readFile(e,"utf-8");return M.test(r)}catch(r){u.warn(`Failed to inspect module ${e}: ${m(r)}`);return!1}}async function b(e,r){if(e.isDirectory())return!0;if(!e.isSymbolicLink())return!1;try{return(await i.promises.stat(r)).isDirectory()}catch(t){const a=t.code;if(a!=="ENOENT"&&a!=="ENOTDIR")u.warn(`Failed to inspect plugin link ${r}: ${m(t)}`);return!1}}
@@ -46,6 +46,8 @@ export interface ManifestEntry {
46
46
  navigateBefore?: boolean | string;
47
47
  /** Site session lifecycle defaults — see CliCommand.siteSession */
48
48
  siteSession?: 'ephemeral' | 'persistent';
49
+ /** 标签页会话名后缀 — see CliCommand.sessionKey(不进清单的话,懒加载的内置命令会丢掉它) */
50
+ sessionKey?: string;
49
51
  /** Default browser window visibility — see CliCommand.defaultWindowMode */
50
52
  defaultWindowMode?: 'foreground' | 'background';
51
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "publishport-opencli",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": false