publishport-opencli 1.0.31 → 1.0.33
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 +32 -1
- package/clis/xianyu/auth.js +2 -2
- package/clis/xianyu/captcha.js +20 -0
- package/clis/xianyu/chat.js +1 -1
- package/clis/xianyu/inbox.js +1 -1
- package/clis/xianyu/item-stats.js +2 -2
- package/clis/xianyu/item.js +2 -2
- package/clis/xianyu/messages.js +1 -1
- package/clis/xianyu/mtop.js +2 -2
- package/clis/xianyu/promote-stats.js +2 -2
- package/clis/xianyu/publish.js +7 -7
- package/clis/xianyu/reply.js +1 -1
- package/clis/xianyu/search.js +2 -2
- package/clis/xianyu/seller-data.js +1 -1
- package/clis/xianyu/seller-list.js +1 -1
- package/clis/xianyu/system-mouse.js +23 -0
- package/clis/xianyu/watch.js +43 -1
- package/package.json +1 -1
package/cli-manifest.json
CHANGED
|
@@ -50319,6 +50319,37 @@
|
|
|
50319
50319
|
"modulePath": "wttr/forecast.js",
|
|
50320
50320
|
"sourceFile": "wttr/forecast.js"
|
|
50321
50321
|
},
|
|
50322
|
+
{
|
|
50323
|
+
"site": "xianyu",
|
|
50324
|
+
"name": "captcha",
|
|
50325
|
+
"description": "检测闲鱼滑块验证码,自动拖动并确认验证已解除",
|
|
50326
|
+
"access": "write",
|
|
50327
|
+
"domain": "www.goofish.com",
|
|
50328
|
+
"strategy": "cookie",
|
|
50329
|
+
"browser": true,
|
|
50330
|
+
"args": [
|
|
50331
|
+
{
|
|
50332
|
+
"name": "input",
|
|
50333
|
+
"type": "str",
|
|
50334
|
+
"default": "auto",
|
|
50335
|
+
"required": false,
|
|
50336
|
+
"help": "鼠标方式:auto 自动选择,system 系统鼠标,cdp 浏览器鼠标",
|
|
50337
|
+
"choices": [
|
|
50338
|
+
"auto",
|
|
50339
|
+
"system",
|
|
50340
|
+
"cdp"
|
|
50341
|
+
]
|
|
50342
|
+
}
|
|
50343
|
+
],
|
|
50344
|
+
"columns": [
|
|
50345
|
+
"status",
|
|
50346
|
+
"attempts"
|
|
50347
|
+
],
|
|
50348
|
+
"type": "js",
|
|
50349
|
+
"modulePath": "xianyu/auth.js",
|
|
50350
|
+
"sourceFile": "xianyu/auth.js",
|
|
50351
|
+
"navigateBefore": false
|
|
50352
|
+
},
|
|
50322
50353
|
{
|
|
50323
50354
|
"site": "xianyu",
|
|
50324
50355
|
"name": "chat",
|
|
@@ -51328,7 +51359,7 @@
|
|
|
51328
51359
|
{
|
|
51329
51360
|
"site": "xianyu",
|
|
51330
51361
|
"name": "watch",
|
|
51331
|
-
"description": "
|
|
51362
|
+
"description": "闲鱼私信监听:旁听网页版 IM 自己的 WebSocket 推送,一有新消息就读已打开的会话列表,按 NDJSON 逐条输出",
|
|
51332
51363
|
"access": "read",
|
|
51333
51364
|
"domain": "www.goofish.com",
|
|
51334
51365
|
"strategy": "cookie",
|
package/clis/xianyu/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{AuthRequiredError as
|
|
1
|
+
import{evaluateWithCaptcha as d}from"./captcha.js";import{AuthRequiredError as n,CommandExecutionError as l}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as w}from"../_shared/site-auth.js";async function a(o){return(await o.getCookies({url:"https://www.goofish.com"})).some((t)=>(t.name==="unb"||t.name==="tracknick")&&t.value)}async function h(o){if(!await a(o))throw new n("goofish.com","Xianyu unb/tracknick cookie missing — anonymous");await o.goto("https://www.goofish.com/personal");await o.wait(2);const e=await d(o,"location.href");if(/passport\.(taobao|goofish)\.com\/(member\/login|login)/.test(String(e||"")))throw new n("goofish.com",`Xianyu /personal redirected to login: ${e}`);const t=await o.getCookies({url:"https://www.goofish.com"}),r=t.find((s)=>s.name==="tracknick")?.value||"",k=t.find((s)=>s.name==="unb")?.value||"",i=await d(o,`
|
|
2
2
|
(() => {
|
|
3
3
|
const bodyText = document.body?.innerText || '';
|
|
4
4
|
const requiresAuth = /请先登录|登录后/.test(bodyText);
|
|
@@ -8,4 +8,4 @@ import{AuthRequiredError as t,CommandExecutionError as k}from"@jackwener/opencli
|
|
|
8
8
|
const userIdMatch = html.match(/['"]?userId['"]?\\s*[:=]\\s*['"]?(\\d+)/i);
|
|
9
9
|
return { requiresAuth, blocked, domNick: nick, domUserId: userIdMatch?.[1] || '' };
|
|
10
10
|
})()
|
|
11
|
-
`);if(i.blocked)throw new
|
|
11
|
+
`);if(i.blocked)throw new n("goofish.com","Xianyu blocked by verification / risk control");if(i.requiresAuth)throw new n("goofish.com","Xianyu /personal shows login prompt — anonymous");const u=i.domUserId||k;let c="";if(r)try{c=JSON.parse('"'+r.replace(/\\/g,"\\\\")+'"')}catch{c=r}const m=i.domNick||c;if(!u&&!m)throw new l("Xianyu /personal rendered but no user identity extractable — stale unb or layout drift");return{user_id:String(u),nickname:String(m)}}w({site:"xianyu",domain:"goofish.com",loginUrl:"https://www.goofish.com/login",columns:["user_id","nickname"],quickCheck:a,verify:h,poll:async(o)=>{if(!await a(o))throw new n("goofish.com","Waiting for Xianyu unb/tracknick cookie");return h(o)}});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import{cli as x,Strategy as p}from"@jackwener/opencli/registry";import{CliError as v,TimeoutError as _}from"@jackwener/opencli/errors";import{createSystemMouse as b,systemMouseAvailable as h}from"./system-mouse.js";const m="/_____tmd_____/punish",l=(t)=>new Promise((e)=>setTimeout(e,t));export function captchaError(t){return new v("CAPTCHA",`闲鱼验证码:${t}`)}const w=`(() => {
|
|
2
|
+
const visible = (e) => !!e && e.getBoundingClientRect().width > 0 && e.getBoundingClientRect().height > 0 && getComputedStyle(e).visibility !== 'hidden' && getComputedStyle(e).display !== 'none';
|
|
3
|
+
const box = (e) => { const r = e.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height }; };
|
|
4
|
+
const container = document.querySelector('.nc-container, #nocaptcha, #baxia-punish');
|
|
5
|
+
const track = document.querySelector('.nc_scale');
|
|
6
|
+
const handle = track?.querySelector('.btn_slide');
|
|
7
|
+
const retry = document.querySelector('.nc_wrapper .errloading, .nc_wrapper .nc-lang-cnt a');
|
|
8
|
+
const text = container?.innerText?.replace(/\\s+/g, ' ').trim() || '';
|
|
9
|
+
const expired = location.pathname.includes('/_____tmd_____/') && /抱歉,页面访问出现了问题/.test(document.body?.innerText || '');
|
|
10
|
+
const success = visible(document.querySelector('.nc-success, .nc_scale .btn_ok')) || /验证通过|验证成功/.test(text);
|
|
11
|
+
const blocked = !success && (expired || visible(handle) || visible(retry) || location.pathname.includes(${JSON.stringify(m)}));
|
|
12
|
+
return { blocked, success, expired, text, handle: visible(handle) ? box(handle) : null, track: visible(track) ? box(track) : null, retry: visible(retry) ? box(retry) : null, viewport: { width: innerWidth, height: innerHeight } };
|
|
13
|
+
})()`;async function d(t){const e=await t.evaluate(`(() => {
|
|
14
|
+
const widget = ${w};
|
|
15
|
+
const frames = Array.from(document.querySelectorAll('iframe')).filter(f => f.src.includes(${JSON.stringify(m)})).map(f => {
|
|
16
|
+
const r = f.getBoundingClientRect();
|
|
17
|
+
return { url: f.src, x: r.x + f.clientLeft, y: r.y + f.clientTop, scaleX: f.clientWidth ? (r.width / f.offsetWidth) : 1, scaleY: f.clientHeight ? (r.height / f.offsetHeight) : 1, visible: r.width > 0 && r.height > 0 && getComputedStyle(f).display !== 'none' && getComputedStyle(f).visibility !== 'hidden' };
|
|
18
|
+
}).filter(f => f.visible);
|
|
19
|
+
return { widget, frames };
|
|
20
|
+
})()`);if(e.widget.blocked||e.widget.success)return{...e.widget,offset:{x:0,y:0,scaleX:1,scaleY:1},viewport:e.widget.viewport};if(!e.frames.length)return null;if(typeof t.frames!=="function"||typeof t.evaluateInFrame!=="function")throw captchaError("当前浏览器无法读取跨域滑块,验证尚未解除");const r=(await t.frames()).find((s)=>e.frames.some((c)=>c.url===s.url));if(!r)throw captchaError("验证码框架尚未就绪,验证尚未解除");return{...await t.evaluateInFrame(w,r.index),frameIndex:r.index,frameId:r.frameId,offset:e.frames.find((s)=>s.url===r.url),viewport:e.widget.viewport}}function y(t,e){return{x:t.offset.x+(e.x+e.width/2)*t.offset.scaleX,y:t.offset.y+(e.y+e.height/2)*t.offset.scaleY}}async function u(t,e,o,r=!1){await t.cdp("Input.dispatchMouseEvent",{type:e,x:o.x,y:o.y,button:e==="mouseMoved"&&!r?"none":"left",buttons:r?1:0,...e!=="mouseMoved"?{clickCount:1}:{}})}async function k(t,e,o){const r=y(e,e.retry);if(o)await o.click(r);else{await u(t,"mouseMoved",r);await u(t,"mousePressed",r,!0);await u(t,"mouseReleased",r)}await l(650)}async function S(t,e,o){const r=y(e,e.handle),a=(e.track.x+e.track.width-e.handle.x-e.handle.width+3)*e.offset.scaleX,s={x:r.x+a,y:r.y};if(!Number.isFinite(a)||a<=0||r.x<0||r.y<0||s.x>=e.viewport.width||s.y>=e.viewport.height)throw captchaError("滑块位置不可用,尚未拖动");const c=[r];for(let i=1;i<=36;i++){const n=i/36,f=n<0.65?0.88*(n/0.65)**1.3:0.88+0.12*(1-((1-n)/0.35)**2);c.push({x:r.x+a*f,y:r.y+Math.sin(n*Math.PI*2)*1.5})}if(o){await o.drag(c);return}for(let i=1;i<=10;i++){const n=i/10;await u(t,"mouseMoved",{x:r.x-45*(1-n),y:r.y-20*(1-n)});await l(10)}await l(90);await u(t,"mousePressed",r,!0);try{for(const i of c.slice(1)){await u(t,"mouseMoved",i,!0);await l(12+Math.random()*16)}}finally{await u(t,"mouseReleased",s)}}export async function solveCaptcha(t,{input:e="auto"}={}){let o=await d(t);if(!o?.blocked)return{status:"clear",attempts:0};if(o.expired)throw captchaError("验证地址已失效,需要由原操作取得当前验证码");const r=e==="system"||e==="auto"&&h(t);if(r&&!h(t))throw captchaError("系统鼠标需要 Linux KDE 桌面、ydotool、qdbus6、journalctl 和可写的 /dev/uinput");if(!r&&typeof t.cdp!=="function")throw captchaError("当前浏览器不支持原生鼠标拖动");console.error("[xianyu-captcha] solving verification",{input:r?"system":"cdp"});const a=(await t.getCookies({domain:"goofish.com"})).filter((i)=>i.name==="x5sec").map((i)=>i.value),s=async(i)=>{if(!(await t.getCookies({domain:"goofish.com"})).filter((f)=>f.name==="x5sec").some((f)=>f.value&&!a.includes(f.value)))throw captchaError("滑块已消失或显示通过,但未确认新的 x5sec 通行凭证");return{status:"solved",attempts:i}},c=r?await b(t,()=>d(t)):null;try{for(let i=1;i<=2;i++){if(o.retry){await k(t,o,c);o=await d(t)}for(let n=0;o?.blocked&&!o.handle&&!o.retry&&n<12;n++){await l(200);o=await d(t)}if(!o?.blocked)return await s(i-1);if(!o.handle||!o.track)throw captchaError(`不是可拖动的滑块或滑块未加载:${o.text}`);await S(t,o,c);for(let n=0;n<20;n++){await l(200);o=await d(t);if(!o?.blocked)return await s(i);if(o.retry)break}console.error("[xianyu-captcha] verification rejected",{attempt:i,status:o?.text})}throw captchaError(`自动拖动未通过,验证仍未解除:${o?.text||"验证结果未确认"}`)}finally{if(c)await c.close()}}export function isCaptchaRejection(t){return t?.error==="blocked"||/RGV587_ERROR|FAIL_SYS_USER_VALIDATE|FAIL_SYS_USER_INPUT|\/punish/.test(`${t?.error_code||""} ${t?.error_message||""}`)}export async function evaluateWithCaptcha(t,...e){await solveCaptcha(t);const o=Date.now();for(let r=0;r<2;r++){let a,s=!1;const c=t.evaluate(...e).then((n)=>{a={value:n}},(n)=>{a={error:n}});for(let n=0;!a&&n<120;n++){await Promise.race([c,l(400)]);if(!a)s=(await solveCaptcha(t)).status==="solved"||s}if(!a)throw new _("xianyu operation",Math.ceil((Date.now()-o)/1000),"原操作的结果尚未确认,请先回查;尚未重发原请求");if(a.error)throw a.error;if(!isCaptchaRejection(a.value))return a.value;const i=await solveCaptcha(t);if(!s&&i.status!=="solved"||r===1)throw captchaError("接口仍被验证码拦截,原操作未完成")}}x({site:"xianyu",name:"captcha",access:"write",description:"检测闲鱼滑块验证码,自动拖动并确认验证已解除",domain:"www.goofish.com",strategy:p.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"input",default:"auto",choices:["auto","system","cdp"],help:"鼠标方式:auto 自动选择,system 系统鼠标,cdp 浏览器鼠标"}],columns:["status","attempts"],func:async(t,e)=>{const o=await t.getCurrentUrl();await t.goto(!o||o==="about:blank"?"https://www.goofish.com":o);return[await solveCaptcha(t,{input:e.input})]}});
|
package/clis/xianyu/chat.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AuthRequiredError as
|
|
1
|
+
import{evaluateWithCaptcha as u}from"./captcha.js";import{AuthRequiredError as p,CommandExecutionError as h,selectorError as w}from"@jackwener/opencli/errors";import{cli as f,Strategy as g}from"@jackwener/opencli/registry";import{buildChatUrl as c,buildExtractChatStateEvaluate as l,buildSendMessageEvaluate as _,requireEvaluateObject as d}from"./im.js";import{normalizeNumericId as n}from"./utils.js";f({site:"xianyu",name:"chat",access:"write",description:"打开闲鱼聊一聊会话,并可选发送消息",domain:"www.goofish.com",strategy:g.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"item_id",required:!0,positional:!0,help:"闲鱼商品 item_id"},{name:"user_id",required:!0,positional:!0,help:"聊一聊对方的 user_id / peerUserId"},{name:"text",fromFile:!0,help:"Message to send after opening the chat"}],columns:["status","peer_name","item_title","price","location","message"],func:async(t,i)=>{const r=n(i.item_id,"item_id","1038951278192"),a=n(i.user_id,"user_id","3650092411"),o=c(r,a),s=String(i.text||"").trim();await t.goto(o);await t.wait(2);const e=d(await u(t,l()),"chat");if(e?.requiresAuth)throw new p("www.goofish.com","Xianyu chat requires a logged-in browser session");if(!e?.can_input)throw w("闲鱼聊天输入框","未找到可用的聊天输入框,请确认该会话页已正确加载");if(!s)return[{status:"ready",peer_name:e.peer_name||"",item_title:e.item_title||"",price:e.price||"",location:e.location||"",message:(e.visible_messages||[]).slice(-1)[0]||"",peer_user_id:a,item_id:r,url:o,item_url:e.item_url||""}];const m=d(await u(t,_(s)),"chat send");if(!m?.ok)throw new h(`Xianyu chat did not observe the sent message: ${m?.reason||"unknown-reason"}`);await t.wait(1);return[{status:"sent",peer_name:e.peer_name||"",item_title:e.item_title||"",price:e.price||"",location:e.location||"",message:s,peer_user_id:a,item_id:r,url:o,item_url:e.item_url||""}]}});export const __test__={normalizeNumericId:n,buildChatUrl:c,buildExtractChatStateEvaluate:l,buildSendMessageEvaluate:_};
|
package/clis/xianyu/inbox.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AuthRequiredError as
|
|
1
|
+
import{evaluateWithCaptcha as l}from"./captcha.js";import{AuthRequiredError as d,CommandExecutionError as y,EmptyResultError as v}from"@jackwener/opencli/errors";import{cli as x,Strategy as I}from"@jackwener/opencli/registry";import{buildClickInboxConversationEvaluate as E,buildExtractInboxEvaluate as m,buildInboxUrl as _,buildReadCurrentConversationUrlEvaluate as C,DEFAULT_INBOX_LIMIT as f,MAX_INBOX_LIMIT as g,normalizeLimit as w,requireClickResult as k,requireEvaluateObject as b}from"./im.js";x({site:"xianyu",name:"inbox",access:"read",description:"列出闲鱼最近私信会话",domain:"www.goofish.com",strategy:I.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"limit",type:"int",default:f,help:"Number of conversations to return"},{name:"unread-only",type:"bool",default:!1,help:"Return only conversations with unread messages"},{name:"resolve-ids",type:"bool",default:!1,help:"Click each visible conversation to resolve item_id and peer_user_id from the chat URL"}],columns:["rank","peer_name","peer_user_id","item_id","item_title","price","last_message","unread","unread_count","url"],func:async(r,n)=>{const u=w(n.limit,f,g,"inbox --limit"),p=Boolean(n["unread-only"]),h=Boolean(n["resolve-ids"]);let a="";if(r.getCurrentUrl)try{a=await r.getCurrentUrl()}catch{a=""}if(!/https:\/\/www\.goofish\.com\/im\b/.test(a))await r.goto(_());await r.wait(4);const t=b(await l(r,m(u)),"inbox");if(t?.requiresAuth)throw new d("www.goofish.com","Xianyu inbox requires a logged-in browser session");if(t?.blocked)throw new d("www.goofish.com","Xianyu inbox is blocked by verification or risk control");if(!Array.isArray(t.items))throw new y("Xianyu inbox returned malformed conversation list");const c=t.items;if(!c.length)throw new v("xianyu inbox","No Xianyu inbox conversations were found");let o=c.slice(0,u);if(p)o=o.filter((e)=>Boolean(e.unread));if(h)for(const e of o){if(e.item_id&&e.peer_user_id)continue;const i=Number(e.row_index);if(!Number.isInteger(i)||i<0)continue;k(await l(r,E(i)),"inbox resolve-ids click");await r.wait(1);const s=b(await l(r,C()),"inbox current-url");e.item_id=s?.item_id||e.item_id||"";e.peer_user_id=s?.peer_user_id||e.peer_user_id||"";e.url=s?.url||e.url||""}return o.map((e,i)=>({rank:i+1,peer_name:e.peer_name||"",peer_user_id:e.peer_user_id||"",item_id:e.item_id||"",item_title:e.item_title||"",price:e.price||"",last_message:e.last_message||"",unread:Boolean(e.unread),unread_count:Number(e.unread_count||0),url:e.url||""}))}});export const __test__={buildInboxUrl:_,buildExtractInboxEvaluate:m,normalizeLimit:w};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{CommandExecutionError as
|
|
2
|
-
`).map((c)=>c.trim()).filter(Boolean)[0]||"";if(!n)return;const
|
|
1
|
+
import{evaluateWithCaptcha as y}from"./captcha.js";import{CommandExecutionError as w,ArgumentError as A}from"@jackwener/opencli/errors";import{cli as x,Strategy as g}from"@jackwener/opencli/registry";const d={recent1d:"近1天",recent7d:"近7天",recent30d:"近30天"},U="https://seller.goofish.com/?site=COMMONPRO#/seller-data/commodity",h={商品曝光次数:"exposureCount",商品曝光人数:"exposureUsers",商品浏览次数:"viewCount",商品浏览人数:"viewUsers",询单人数:"inquiryUsers",支付人数:"payUsers",支付订单数:"payOrders",支付金额:"payAmount",浏览支付转化率:"viewPayCvr",发起退款人数:"refundUsers",发起退款订单数:"refundOrders",发起退款金额:"refundAmount",成功退款人数:"refundOkUsers",成功退款订单数:"refundOkOrders",成功退款金额:"refundOkAmount"};function O(e){const a=Array.from(document.querySelectorAll("thead th")).map((t)=>(t.innerText||"").trim());if(!a.length)return{error:"no-table"};const o={};a.forEach((t,r)=>{if(e[t]!=null)o[e[t]]=r});const s=[];document.querySelectorAll("tbody tr").forEach((t)=>{const r=Array.from(t.querySelectorAll("td"));if(!r.length)return;if(r.every((c)=>!(c.innerText||"").trim()))return;const i=(r[0].innerText||"").replace(/\r/g,""),n=i.match(/(\d{10,})/),l=i.match(/¥\s*([\d.]+)/),p=i.split(`
|
|
2
|
+
`).map((c)=>c.trim()).filter(Boolean)[0]||"";if(!n)return;const m={itemId:n[1],title:p,price:l?l[1]:""};for(const c of Object.keys(e)){const f=e[c],u=o[f];m[f]=u!=null&&r[u]?(r[u].innerText||"").trim():""}s.push(m)});return{rows:s}}x({site:"xianyu",name:"item-stats",access:"read",description:"读每个在售商品的经营数据(曝光/浏览/询单/支付/退款,逐商品一行)",domain:"seller.goofish.com",strategy:g.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"range",default:"recent1d",help:"时间范围:recent1d / recent7d / recent30d"}],columns:["itemId","title","price","exposureCount","exposureUsers","viewCount","viewUsers","inquiryUsers","payUsers","payOrders","payAmount","viewPayCvr","refundUsers","refundOrders","refundAmount","refundOkUsers","refundOkOrders","refundOkAmount"],func:async(e,a)=>{const o=String(a.range||"recent1d").trim(),s=d[o];if(!s)throw new A(`--range 只能是 ${Object.keys(d).join(" / ")},收到 "${o}"`);await e.goto(U);await e.wait(3);if(o!=="recent1d"){await y(e,(i)=>{Array.from(document.querySelectorAll("label,span,div,button")).filter((n)=>n.children.length===0&&(n.innerText||"").trim()===i).forEach((n)=>n.click())},s);await e.wait(2)}const t=await y(e,O,h);if(t.error==="no-table")throw new w("商品数据表未渲染 —— 可能未登录卖家中心 / 页面改版 / 无权限");const r=t.rows||[];if(r.length===0)throw new w("商品数据表为空 —— 该账号可能无在售商品或当前范围无数据");return r}});export const __test__={readItemTable:O,HEADER_MAP:h,RANGE_TEXT:d};
|
package/clis/xianyu/item.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{AuthRequiredError as a,EmptyResultError as r,selectorError as
|
|
1
|
+
import{evaluateWithCaptcha as m}from"./captcha.js";import{AuthRequiredError as a,EmptyResultError as r,selectorError as d}from"@jackwener/opencli/errors";import{cli as u,Strategy as w}from"@jackwener/opencli/registry";import{normalizeNumericId as s}from"./utils.js";function i(t){return`https://www.goofish.com/item?id=${encodeURIComponent(t)}`}function p(t){return`
|
|
2
2
|
(async () => {
|
|
3
3
|
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
4
4
|
const extractRetCode = (ret) => {
|
|
@@ -94,4 +94,4 @@ import{AuthRequiredError as a,EmptyResultError as r,selectorError as m}from"@jac
|
|
|
94
94
|
image_urls: images,
|
|
95
95
|
};
|
|
96
96
|
})()
|
|
97
|
-
`}
|
|
97
|
+
`}u({site:"xianyu",name:"item",access:"read",description:"查看闲鱼商品详情",domain:"www.goofish.com",strategy:w.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"item_id",required:!0,positional:!0,help:"闲鱼商品 item_id"}],columns:["item_id","title","price","condition","brand","location","seller_name","want_count"],func:async(t,l)=>{const o=s(l.item_id,"item_id","1040754408976");await t.goto(i(o));await t.wait(2);const e=await m(t,p(o));if(e?.error==="auth-required")throw new a("www.goofish.com","Xianyu item detail requires a logged-in browser session");if(e?.error==="blocked")throw new r("xianyu item","Xianyu item detail is blocked by verification or risk control");if(e?.error==="mtop-not-ready")throw d("window.lib.mtop","闲鱼页面未完成初始化,无法调用商品详情接口");if(!e||typeof e!=="object")throw new r("xianyu item","闲鱼商品详情接口未返回有效数据");const c=String(e.error_code||""),n=String(e.error_message||"");if(/FAIL_SYS_SESSION_EXPIRED|SESSION_EXPIRED|FAIL_SYS/.test(c)||/FAIL_SYS_SESSION_EXPIRED|SESSION_EXPIRED/.test(n))throw new a("www.goofish.com","Xianyu item detail requires a logged-in browser session");if(e.error)throw new r("xianyu item",n||`Xianyu item detail request failed: ${e.error}`);if(!String(e.title||"").trim())throw new r("xianyu item","No item detail was returned for the specified item_id");return[e]}});export const __test__={normalizeNumericId:s,buildItemUrl:i};
|
package/clis/xianyu/messages.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ArgumentError as m,AuthRequiredError as
|
|
1
|
+
import{evaluateWithCaptcha as c}from"./captcha.js";import{ArgumentError as m,AuthRequiredError as E,CommandExecutionError as I,EmptyResultError as v}from"@jackwener/opencli/errors";import{cli as C,Strategy as A}from"@jackwener/opencli/registry";import{buildChatUrl as h,buildClickInboxConversationEvaluate as q,buildExtractChatStateEvaluate as f,DEFAULT_MESSAGE_LIMIT as w,MAX_MESSAGE_LIMIT as k,normalizeLimit as g,normalizeRank as p,requireClickResult as S,requireEvaluateObject as U}from"./im.js";import{normalizeNumericId as y}from"./utils.js";C({site:"xianyu",name:"messages",access:"read",description:"读取指定闲鱼私信会话的最近聊天内容",domain:"www.goofish.com",strategy:A.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"item_id",positional:!0,help:"闲鱼商品 item_id"},{name:"user_id",positional:!0,help:"聊一聊对方的 user_id / peerUserId"},{name:"limit",type:"int",default:w,help:"Number of visible messages to return"},{name:"rank",type:"int",default:0,help:"Conversation rank from xianyu inbox; clicks the visible row instead of requiring IDs"}],columns:["index","peer_name","item_title","message","item_id","peer_user_id","url"],func:async(e,i)=>{const s=i.item_id!=null&&i.item_id!=="",a=i.user_id!=null&&i.user_id!=="",t=p(i.rank);if(t>0&&(s||a))throw new m("xianyu messages accepts either item_id/user_id or --rank, not both");if(t===0&&s!==a)throw new m("xianyu messages requires both item_id and user_id, or --rank from xianyu inbox");if(t===0&&!s&&!a)throw new m("xianyu messages requires item_id/user_id or --rank from xianyu inbox");const o=s&&a,u=o?y(i.item_id,"item_id","1038951278192"):"",l=o?y(i.user_id,"user_id","3650092411"):"",d=g(i.limit,w,k,"messages --limit");let n="";if(o){n=h(u,l);await e.goto(n)}else if(!e.getCurrentUrl||!/https:\/\/www\.goofish\.com\/im\b/.test(await e.getCurrentUrl()))await e.goto("https://www.goofish.com/im");await e.wait(2);if(t>0){S(await c(e,q(t-1)),"messages rank click");await e.wait(2)}const r=U(await c(e,f(d)),"messages");if(r?.requiresAuth)throw new E("www.goofish.com","Xianyu messages requires a logged-in browser session");if(!Array.isArray(r.messages))throw new I("Xianyu messages returned malformed message list");const _=r.messages;if(!_.length)throw new v("xianyu messages","No visible messages were found in this Xianyu conversation");return _.slice(-d).map((x,b)=>({index:b+1,peer_name:r.peer_name||"",item_title:r.item_title||"",message:x.text||"",item_id:u,peer_user_id:l,url:n||""}))}});export const __test__={buildChatUrl:h,buildExtractChatStateEvaluate:f,normalizeLimit:g,normalizeRank:p};
|
package/clis/xianyu/mtop.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{AuthRequiredError as a,CommandExecutionError as
|
|
1
|
+
import{evaluateWithCaptcha as d}from"./captcha.js";import{AuthRequiredError as a,CommandExecutionError as S,EmptyResultError as i,selectorError as f}from"@jackwener/opencli/errors";export const HOME_URL="https://www.goofish.com";const c=["RGV587_ERROR","FAIL_SYS_USER_VALIDATE","哎哟喂","/punish"],w=["FAIL_SYS_SESSION_EXPIRED","FAIL_SYS_TOKEN_EXOIRED","FAIL_SYS_TOKEN_EMPTY","FAIL_SYS_ILLEGAL_ACCESS","令牌过期","NEED_LOGIN"];function p(e,r,t){return`
|
|
2
2
|
(async () => {
|
|
3
3
|
const clean = (v) => String(v ?? '').replace(/\\s+/g, ' ').trim();
|
|
4
4
|
const extractRetCode = (ret) => clean(Array.isArray(ret) ? ret[0] : '').split('::')[0] || '';
|
|
@@ -44,4 +44,4 @@ import{AuthRequiredError as a,CommandExecutionError as p,EmptyResultError as i,s
|
|
|
44
44
|
}
|
|
45
45
|
return { ok: true, ret: response?.ret || [], data: response?.data || {} };
|
|
46
46
|
})()
|
|
47
|
-
`}function
|
|
47
|
+
`}function u(e,r){if(r?.error==="auth-required")throw new a("www.goofish.com",`闲鱼 ${e} 需要已登录的浏览器会话`);if(r?.error==="blocked")throw new i(`xianyu ${e}`,"闲鱼页面被验证码 / 安全验证拦截,请人工处理后重试");if(r?.error==="mtop-not-ready")throw f("window.lib.mtop","闲鱼页面未完成初始化,无法调用 mtop 接口");const t=String(r?.error_code||""),n=String(r?.error_message||""),s=`${t} ${n}`;if(w.some((o)=>s.includes(o)))throw new a("www.goofish.com",`闲鱼 ${e} 登录态失效:${n||t}`);if(c.some((o)=>s.includes(o)))throw new i(`xianyu ${e}`,`闲鱼 ${e} 触发风控:${n||t},请人工处理`);if(r?.error)throw new S(`闲鱼 ${e} 接口调用失败:${n||t||r.error}`)}export async function mtopRequest(e,r,{api:t,data:n,version:s="1.0"}){const o=await d(e,p(t,n,s));u(r,o);if(!o||typeof o!=="object")throw new i(`xianyu ${r}`,"闲鱼接口未返回有效数据");return o.data||{}}export function retOk(e){return Array.isArray(e)&&e.some((r)=>String(r).includes("SUCCESS"))}export const __test__={buildMtopEvaluate:p,classifyAndThrow:u,RISK_KEYWORDS:c,AUTH_KEYWORDS:w};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{CommandExecutionError as
|
|
2
|
-
`).map((d)=>d.trim()).filter(Boolean);if(e.length<2)return;const t=e[0],s=e[1],u=e.slice(2).join(" ");a(t,s,u)});const i=(r)=>{const e=Array.from(document.querySelectorAll("div,span")).filter((t)=>t.children.length===0&&(t.innerText||"").trim().replace(/(.*)|\(.*\)/g,"")===r);for(const t of e){let s=t.parentElement;for(let u=0;u<3&&s;u++){const d=(s.innerText||"").match(/-?[\d,]+(\.\d+)?%?/);if(d&&d[0]!==r)return d[0];s=s.parentElement}}return null};[["擦亮中计划","擦亮中计划"],["已暂停擦亮计划","已暂停擦亮计划"],["即将消耗完成计划","即将消耗完成计划"],["已完成擦亮计划","已完成擦亮计划"]].forEach(([r,e])=>{const t=i(e);if(t!=null)a(r,t,"")});const l=document.body?document.body.innerText.replace(/\s+/g," "):"",c=/[^\d-]*(-?[\d,]+(?:\s*\.\s*\d+)?)/,m=l.match(new RegExp("可用余额[((]元[))]"+c.source));if(m)a("可用余额(元)",m[1].replace(/\s+/g,""),"");const o=l.match(new RegExp("今日花费[((]元[))]"+c.source));if(o)a("今日花费(元)",o[1].replace(/\s+/g,""),"");return n}
|
|
1
|
+
import{evaluateWithCaptcha as y}from"./captcha.js";import{CommandExecutionError as p,ArgumentError as g}from"@jackwener/opencli/errors";import{cli as w,Strategy as E}from"@jackwener/opencli/registry";const f={today:"今日",yesterday:"昨日",recent7d:"近7日",recent30d:"近30日",recent90d:"近90日"},x="https://seller.goofish.com/?site=COMMONPRO#/seller-ad/home";function h(){const n=[],a=(r,e,t)=>{if(r&&e!==""&&e!=null)n.push({metric:r,value:e,compare:t||""})};document.querySelectorAll('[class*="metricCard--"]').forEach((r)=>{const e=(r.innerText||"").split(`
|
|
2
|
+
`).map((d)=>d.trim()).filter(Boolean);if(e.length<2)return;const t=e[0],s=e[1],u=e.slice(2).join(" ");a(t,s,u)});const i=(r)=>{const e=Array.from(document.querySelectorAll("div,span")).filter((t)=>t.children.length===0&&(t.innerText||"").trim().replace(/(.*)|\(.*\)/g,"")===r);for(const t of e){let s=t.parentElement;for(let u=0;u<3&&s;u++){const d=(s.innerText||"").match(/-?[\d,]+(\.\d+)?%?/);if(d&&d[0]!==r)return d[0];s=s.parentElement}}return null};[["擦亮中计划","擦亮中计划"],["已暂停擦亮计划","已暂停擦亮计划"],["即将消耗完成计划","即将消耗完成计划"],["已完成擦亮计划","已完成擦亮计划"]].forEach(([r,e])=>{const t=i(e);if(t!=null)a(r,t,"")});const l=document.body?document.body.innerText.replace(/\s+/g," "):"",c=/[^\d-]*(-?[\d,]+(?:\s*\.\s*\d+)?)/,m=l.match(new RegExp("可用余额[((]元[))]"+c.source));if(m)a("可用余额(元)",m[1].replace(/\s+/g,""),"");const o=l.match(new RegExp("今日花费[((]元[))]"+c.source));if(o)a("今日花费(元)",o[1].replace(/\s+/g,""),"");return n}w({site:"xianyu",name:"promote-stats",access:"read",description:"读超级擦亮(付费推广)投放数据:花费/曝光/点击/询单/下单/单次成本 + 账户余额 + 计划状态",domain:"seller.goofish.com",strategy:E.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"range",default:"today",help:"时间范围:today / yesterday / recent7d / recent30d / recent90d"}],columns:["metric","value","compare"],func:async(n,a)=>{const i=String(a.range||"today").trim(),l=f[i];if(!l)throw new g(`--range 只能是 ${Object.keys(f).join(" / ")},收到 "${i}"`);await n.goto(x);await n.wait(4);if(i!=="today"){await y(n,(m)=>{Array.from(document.querySelectorAll("label,span,div,button")).filter((o)=>o.children.length===0&&(o.innerText||"").trim()===m).forEach((o)=>o.click())},l);await n.wait(2)}const c=await y(n,h);if(!Array.isArray(c)||c.length===0)throw new p("未读到擦亮数据 —— 可能未开通超级擦亮 / 未登录 / 页面改版");return c}});export const __test__={readAdDashboard:h,RANGE_TEXT:f};
|
package/clis/xianyu/publish.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import{evaluateWithCaptcha as s}from"./captcha.js";import*as P from"node:fs";import*as x from"node:path";import{ArgumentError as c,AuthRequiredError as N,CommandExecutionError as u}from"@jackwener/opencli/errors";import{cli as C,Strategy as M}from"@jackwener/opencli/registry";import{setInputFiles as _}from"../_shared/file-inject.js";const j=new Set([".jpg",".jpeg",".png",".webp"]),f=9,h=["全新","几乎全新","轻微使用","明显使用","老旧"];function w(){return"https://www.goofish.com/publish"}async function b(t){if(t.getCurrentUrl)try{const n=await t.getCurrentUrl();if(n)return n}catch{}return w()}function p(t,n){const e=String(t??"").replace(/\s+/g," ").trim();if(!e)throw new c(`xianyu publish ${n} cannot be empty`);return e}function E(t,n){if(t==null||String(t).trim()==="")return null;const e=String(t).trim();if(!/^\d+(?:\.\d{1,2})?$/.test(e))throw new c(`xianyu publish ${n} must be a positive price with at most 2 decimals`);const o=Number(e);if(!Number.isFinite(o)||o<=0)throw new c(`xianyu publish ${n} must be a positive price`);return e}function B(t){const n=p(t,"condition");if(!h.includes(n))throw new c(`xianyu publish condition must be one of: ${h.join(", ")}`);return n}function k(t){if(!t)return[];const n=String(t).split(",").map((e)=>e.trim()).filter(Boolean);if(n.length===0)return[];if(n.length>f)throw new c(`xianyu publish images supports at most ${f} files`);return n.map((e)=>{const o=x.resolve(e),l=x.extname(o).toLowerCase();if(!j.has(l))throw new c(`Unsupported image format "${l}". Supported: jpg, jpeg, png, webp`);const a=P.statSync(o,{throwIfNoEntry:!1});if(!a||!a.isFile())throw new c(`Not a valid image file: ${o}`);return o})}function q(t){const n=E(t.price,"price");if(n==null)throw new c("xianyu publish price cannot be empty");const e={};e.title=p(t.title,"title");e.description=p(t.description,"description");e.price=n;e.condition=B(t.condition);e.category=p(t.category,"category");e.original_price=E(t.original_price,"original_price");e.location=t.location?p(t.location,"location"):"";e.images=k(t.images);return e}function g(t){return`
|
|
2
2
|
(() => {
|
|
3
3
|
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
4
4
|
|
|
@@ -103,7 +103,7 @@ import*as T from"node:fs";import*as S from"node:path";import{ArgumentError as a,
|
|
|
103
103
|
|
|
104
104
|
return { ok: missing.length === 0, filled, missing };
|
|
105
105
|
})()
|
|
106
|
-
`}function
|
|
106
|
+
`}function y(t){return`
|
|
107
107
|
(async () => {
|
|
108
108
|
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
109
109
|
|
|
@@ -147,7 +147,7 @@ import*as T from"node:fs";import*as S from"node:path";import{ArgumentError as a,
|
|
|
147
147
|
}
|
|
148
148
|
return { ok: false, reason: 'category-match-failed' };
|
|
149
149
|
})()
|
|
150
|
-
`}function
|
|
150
|
+
`}function I(){return`
|
|
151
151
|
(() => {
|
|
152
152
|
// 找图片上传相关的 file input
|
|
153
153
|
const fileInput = document.querySelector('input[type="file"]');
|
|
@@ -162,7 +162,7 @@ import*as T from"node:fs";import*as S from"node:path";import{ArgumentError as a,
|
|
|
162
162
|
|
|
163
163
|
return { ok: true, selector, hasMultiple: fileInput.multiple };
|
|
164
164
|
})()
|
|
165
|
-
`}function
|
|
165
|
+
`}function A(){return`
|
|
166
166
|
(() => {
|
|
167
167
|
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
168
168
|
|
|
@@ -181,7 +181,7 @@ import*as T from"node:fs";import*as S from"node:path";import{ArgumentError as a,
|
|
|
181
181
|
submitBtn.click();
|
|
182
182
|
return { ok: true };
|
|
183
183
|
})()
|
|
184
|
-
`}function
|
|
184
|
+
`}function O(){return`
|
|
185
185
|
(() => {
|
|
186
186
|
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
187
187
|
const bodyText = document.body?.innerText || '';
|
|
@@ -209,7 +209,7 @@ import*as T from"node:fs";import*as S from"node:path";import{ArgumentError as a,
|
|
|
209
209
|
|
|
210
210
|
return { ok: false, reason: 'unknown-state' };
|
|
211
211
|
})()
|
|
212
|
-
`}function
|
|
212
|
+
`}function T(){return`
|
|
213
213
|
(() => {
|
|
214
214
|
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
215
215
|
const bodyText = document.body?.innerText || '';
|
|
@@ -242,4 +242,4 @@ import*as T from"node:fs";import*as S from"node:path";import{ArgumentError as a,
|
|
|
242
242
|
bodySnippet: bodyText.slice(0, 500),
|
|
243
243
|
};
|
|
244
244
|
})()
|
|
245
|
-
`}export const publishCommand=
|
|
245
|
+
`}export const publishCommand=C({site:"xianyu",name:"publish",access:"write",description:"发布闲鱼宝贝(需先在浏览器中登录闲鱼)",domain:"www.goofish.com",strategy:M.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"title",required:!0,positional:!0,help:"商品标题"},{name:"description",fromFile:!0,required:!0,positional:!0,help:"商品描述/详情"},{name:"price",required:!0,positional:!0,type:"float",help:"出售价格(元)"},{name:"condition",required:!0,positional:!0,help:"成色:全新 / 几乎全新 / 轻微使用 / 明显使用 / 老旧"},{name:"category",required:!0,positional:!0,help:"商品分类关键词(如:手机、衣服、图书)"},{name:"original_price",type:"float",help:"原价(选填,用于显示折扣)"},{name:"location",help:"所在地区(选填,如:杭州)"},{name:"images",help:"本地图片路径,多张用逗号分隔(选填,如:/tmp/a.jpg,/tmp/b.jpg)"}],columns:["status","item_id","title","price","condition","url","message"],func:async(t,n)=>{const e=q(n);await t.goto(w());await t.wait(3);let o=await s(t,T());for(let i=0;i<30;i++){if(o?.requiresAuth||o?.hasPublishForm)break;await t.wait(0.5);o=await s(t,T())}if(o?.requiresAuth)throw new N("www.goofish.com","发布闲鱼需要先登录,请在 Chrome 中打开 goofish.com 并完成登录");if(!o?.hasPublishForm)throw new u("Xianyu publish form was not detected","Confirm goofish.com is logged in and the publish page finished loading.");let l=await s(t,y(e.category));for(let i=0;i<30;i++){if(l?.ok||l?.reason!=="category-trigger-not-found")break;await t.wait(0.5);l=await s(t,y(e.category))}if(!l?.ok)throw new u(`Xianyu category selection failed: ${l?.reason||"unknown-reason"}`);await t.wait(1.5);let a=await s(t,g(e));for(let i=0;i<30;i++){if(a?.ok)break;await t.wait(0.5);a=await s(t,g(e))}if(!a?.ok){const i=Array.isArray(a?.missing)?a.missing.join(", "):"unknown";throw new u(`Xianyu publish form fill failed; missing fields: ${i}`)}await t.wait(1);if(e.images.length>0){let i=await s(t,I());for(let r=0;r<30;r++){if(i?.ok)break;await t.wait(0.5);i=await s(t,I())}if(!i?.ok)throw new u(`Xianyu image upload input was not found: ${i?.reason||"unknown-reason"}`);try{await _(t,i.selector||'input[type="file"]',e.images);await t.wait(3)}catch(r){throw new u(`Xianyu image upload failed: ${r?.message||r}`)}}let d=await s(t,A());for(let i=0;i<30;i++){if(d?.ok)break;await t.wait(0.5);d=await s(t,A())}if(!d?.ok)throw new u(`Xianyu publish submit failed: ${d?.reason||"unknown-reason"}`);await t.wait(2);let S="",m=await b(t),v="";for(let i=0;i<10;i++){await t.wait(1.5);const r=await s(t,O());m=await b(t);if(r?.status==="published"){S=String(r.item_id||"").replace(/\D/g,"");return[{status:"published",item_id:S,title:e.title.slice(0,50),price:`¥${e.price}`,condition:e.condition,url:r.url||m,message:"发布成功"}]}if(r?.status==="failed"){v=r.message||"发布失败";break}}throw new u(v||"Xianyu publish result was not confirmed before timeout",`Open ${m} and verify whether the listing was published.`)}}),__test__={CONDITION_CHOICES:h,MAX_IMAGES:f,validateImagePaths:k,normalizePublishArgs:q,buildPublishUrl:w,getCurrentPageUrl:b,buildFillFormEvaluate:g,buildSelectCategoryEvaluate:y,buildFindFileInputSelectorEvaluate:I,buildDetectSuccessEvaluate:O};
|
package/clis/xianyu/reply.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ArgumentError as
|
|
1
|
+
import{evaluateWithCaptcha as s}from"./captcha.js";import{ArgumentError as u,AuthRequiredError as y,CommandExecutionError as x,selectorError as b}from"@jackwener/opencli/errors";import{cli as C,Strategy as E}from"@jackwener/opencli/registry";import{buildChatUrl as w,buildClickInboxConversationEvaluate as I,buildExtractChatStateEvaluate as q,buildSendMessageEvaluate as h,normalizeRank as v,requireClickResult as k,requireEvaluateObject as p,requireText as g}from"./im.js";import{normalizeNumericId as f}from"./utils.js";C({site:"xianyu",name:"reply",access:"write",description:"回复指定闲鱼私信会话",domain:"www.goofish.com",strategy:E.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"item_id",positional:!0,help:"闲鱼商品 item_id"},{name:"user_id",positional:!0,help:"聊一聊对方的 user_id / peerUserId"},{name:"text",fromFile:!0,required:!0,help:"Message text to send"},{name:"rank",type:"int",default:0,help:"Conversation rank from xianyu inbox; clicks the visible row instead of requiring IDs"}],columns:["status","peer_name","item_title","price","location","message"],func:async(e,t)=>{const o=t.item_id!=null&&t.item_id!=="",n=t.user_id!=null&&t.user_id!=="",r=v(t.rank);if(r>0&&(o||n))throw new u("xianyu reply accepts either item_id/user_id or --rank, not both");if(r===0&&o!==n)throw new u("xianyu reply requires both item_id and user_id, or --rank from xianyu inbox");if(r===0&&!o&&!n)throw new u("xianyu reply requires item_id/user_id or --rank from xianyu inbox");const a=o&&n,l=a?f(t.item_id,"item_id","1038951278192"):"",m=a?f(t.user_id,"user_id","3650092411"):"",d=g(t.text,"xianyu reply --text"),c=a?w(l,m):"";if(a)await e.goto(c);else if(!e.getCurrentUrl||!/https:\/\/www\.goofish\.com\/im\b/.test(await e.getCurrentUrl()))await e.goto("https://www.goofish.com/im");await e.wait(2);if(r>0){k(await s(e,I(r-1)),"reply rank click");await e.wait(2)}const i=p(await s(e,q()),"reply");if(i?.requiresAuth)throw new y("www.goofish.com","Xianyu reply requires a logged-in browser session");if(!i?.can_input)throw b("闲鱼聊天输入框","未找到可用的聊天输入框,请确认该会话页已正确加载");const _=p(await s(e,h(d)),"reply send");if(!_?.ok)throw new x(`Xianyu reply did not observe the sent message: ${_?.reason||"unknown-reason"}`);await e.wait(1);return[{status:"sent",peer_name:i.peer_name||"",item_title:i.item_title||"",price:i.price||"",location:i.location||"",message:d,peer_user_id:m,item_id:l,url:c||"",item_url:i.item_url||""}]}});export const __test__={buildChatUrl:w,buildSendMessageEvaluate:h};
|
package/clis/xianyu/search.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{ArgumentError as f,AuthRequiredError as w,CommandExecutionError as s,EmptyResultError as
|
|
1
|
+
import{evaluateWithCaptcha as P}from"./captcha.js";import{ArgumentError as f,AuthRequiredError as w,CommandExecutionError as s,EmptyResultError as A,selectorError as N}from"@jackwener/opencli/errors";import{cli as v,Strategy as C}from"@jackwener/opencli/registry";const g=30,c=60;function y(e){const r=Number(e);if(!Number.isFinite(r))return 20;return Math.min(c,Math.max(1,Math.floor(r)))}function h(e){return`https://www.goofish.com/search?q=${encodeURIComponent(e)}`}function l(e,r){if(e===void 0||e===null||e==="")return null;const n=Number(String(e).trim());if(!Number.isFinite(n)||n<0)throw new f(`xianyu search ${r} must be a non-negative number`,`For example: --${r} 100000`);return n}function x(e,r){if(e==null&&r==null)return"";const n=e!=null?e:0,o=r!=null?r:99999999;return`priceRange:${n},${o};`}function b(e,r){if(!e&&!r)return"{}";return JSON.stringify({divisionList:[{province:e||"",city:r||""}],excludeMultiPlacesSellers:"0",extraDivision:""})}function q({keyword:e,searchFilter:r,extraFilterValue:n,fromFilter:o,maxItems:i}){return`
|
|
2
2
|
(async () => {
|
|
3
3
|
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
4
4
|
const cleanFirst = (...values) => values.map(clean).find(Boolean) || '';
|
|
@@ -138,4 +138,4 @@ import{ArgumentError as f,AuthRequiredError as w,CommandExecutionError as s,Empt
|
|
|
138
138
|
|
|
139
139
|
return { items: collected };
|
|
140
140
|
})()
|
|
141
|
-
`}
|
|
141
|
+
`}v({site:"xianyu",name:"search",access:"read",description:"搜索闲鱼商品(支持服务端价格区间 / 地区筛选)",domain:"www.goofish.com",strategy:C.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"query",required:!0,positional:!0,help:"搜索关键词"},{name:"limit",type:"int",default:20,help:`返回结果数(最多 ${c},自动翻页)`},{name:"min-price",type:"float",help:"最低价格(元),服务端筛选"},{name:"max-price",type:"float",help:"最高价格(元),服务端筛选"},{name:"province",type:"string",help:"省份名(如 广东),服务端按地区筛选"},{name:"city",type:"string",help:"城市名(如 深圳 / 湛江),可单独使用,服务端按地区筛选"}],columns:["item_id","rank","title","price","condition","brand","location","badge","want","url"],func:async(e,r)=>{const n=String(r.query||"").trim(),o=y(r.limit),i=l(r["min-price"],"min-price"),a=l(r["max-price"],"max-price");if(i!=null&&a!=null&&i>a)throw new f("xianyu search min-price cannot be greater than max-price",`Received --min-price ${i} and --max-price ${a}`);const S=String(r.province||"").trim(),_=String(r.city||"").trim(),m=x(i,a),u=b(S,_),E=Boolean(m)||u!=="{}";await e.goto(h(n));await e.wait(2);const t=await P(e,q({keyword:n,searchFilter:m,extraFilterValue:u,fromFilter:E,maxItems:o}));if(t?.error==="auth-required")throw new w("www.goofish.com","Xianyu search requires a logged-in browser session");if(t?.error==="blocked")throw new s("Xianyu returned a verification page or blocked the current browser session");if(t?.error==="mtop-not-ready")throw N("window.lib.mtop","闲鱼页面未完成初始化,无法调用搜索接口");if(!t||typeof t!=="object")throw new s("Xianyu search returned a malformed response");const F=String(t?.error_code||""),d=String(t?.error_message||"");if(/FAIL_SYS_SESSION_EXPIRED|SESSION_EXPIRED|FAIL_SYS_TOKEN/.test(F)||/FAIL_SYS_SESSION_EXPIRED|SESSION_EXPIRED/.test(d))throw new w("www.goofish.com","Xianyu search requires a logged-in browser session");if(t?.error)throw new s(d||`Xianyu search request failed: ${t.error}`);if(!Array.isArray(t.items))throw new s("Xianyu search response did not include an items array");const p=t.items;if(!p.length)throw new A("xianyu search","没有匹配的商品(筛选条件可能过窄,或当前关键词无结果)");return p.map((I,R)=>({rank:R+1,...I}))}});export const __test__={ROWS_PER_PAGE:g,MAX_LIMIT:c,normalizeLimit:y,buildSearchUrl:h,parsePriceArg:l,buildSearchFilter:x,buildExtraFilterValue:b};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ArgumentError as
|
|
1
|
+
import{evaluateWithCaptcha as f}from"./captcha.js";import{ArgumentError as g,CommandExecutionError as y}from"@jackwener/opencli/errors";import{cli as p,Strategy as E}from"@jackwener/opencli/registry";const d={overview:{hash:"#/seller-data/data",label:"数据总览"},commodity:{hash:"#/seller-data/commodity",label:"商品数据"},fans:{hash:"#/seller-data/fanData",label:"粉丝数据"},service:{hash:"#/seller-data/customerService",label:"客服数据"}},m={recent1d:"近1天",recent7d:"近7天",recent30d:"近30天"},v="https://seller.goofish.com/?site=COMMONPRO";function w(){const t=[],c=new Set;document.querySelectorAll('[class*="statValue--"]').forEach((n)=>{let e=n,r=null,o=null;for(let h=0;h<5&&e;h++){e=e.parentElement;if(!e)break;r=e.querySelector('[class*="statHeader--"]');if(r){o=e;break}}if(!r||!o)return;const a=(r.innerText||"").trim(),l=(n.innerText||"").trim();if(!a)return;const i=o.querySelector('[class*="statPrevious--"]'),s=i?(i.innerText||"").replace(/\s+/g," ").trim():"",u=a+"|"+l;if(c.has(u))return;c.add(u);t.push({metric:a,value:l,compare:s})});return t}p({site:"xianyu",name:"seller-data",access:"read",description:"读卖家工作台经营数据(曝光/访问/浏览/转化/复购/售后等指标卡)",domain:"seller.goofish.com",strategy:E.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"page",default:"overview",help:`数据页:${Object.keys(d).join(" / ")}`},{name:"range",default:"recent1d",help:"时间范围:recent1d / recent7d / recent30d"}],columns:["metric","value","compare"],func:async(t,c)=>{const n=String(c.page||"overview").trim(),e=d[n];if(!e)throw new g(`--page 只能是 ${Object.keys(d).join(" / ")},收到 "${n}"`);const r=String(c.range||"recent1d").trim(),o=m[r];if(!o)throw new g(`--range 只能是 ${Object.keys(m).join(" / ")},收到 "${r}"`);await t.goto(`${v}${e.hash}`);await t.wait(3);if(r!=="recent1d"){await f(t,(l)=>{const i=Array.from(document.querySelectorAll("label,span,div,button")).filter((s)=>s.children.length===0&&(s.innerText||"").trim()===l);i.forEach((s)=>s.click());return i.length},o);await t.wait(2)}const a=await f(t,w);if(!Array.isArray(a)||a.length===0)throw new y(`未读到任何指标卡(page=${n})—— 可能未登录卖家中心、页面结构改版或该账号无权限`);return a}});export const __test__={readCards:w,PAGE_ROUTES:d,RANGE_TEXT:m};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CommandExecutionError as h,ArgumentError as
|
|
1
|
+
import{evaluateWithCaptcha as f}from"./captcha.js";import{CommandExecutionError as h,ArgumentError as g}from"@jackwener/opencli/errors";import{cli as p,Strategy as d}from"@jackwener/opencli/registry";const a={refund:{hash:"#/seller-trade/refund-manage",label:"退款管理"},review:{hash:"#/seller-trade/evaluation-manage",label:"评价管理"},complaint:{hash:"#/seller-trade/complaint-manage",label:"投诉管理"},income:{hash:"#/seller-finance/income-bill",label:"收入账单"},expense:{hash:"#/seller-finance/expense-bill",label:"支出账单"},"shipping-template":{hash:"#/seller-item/post-temple",label:"运费模版"}};function m(){const t=Array.from(document.querySelectorAll("thead th")).map((r)=>(r.innerText||"").trim()).filter(Boolean);if(!t.length)return{error:"no-table"};const n=[];let l=!1;document.querySelectorAll("tbody tr").forEach((r)=>{const e=Array.from(r.querySelectorAll("td"));if(!e.length)return;if(e.every((s)=>!(s.innerText||"").trim()))return;if(e.length===1)return;if(e.length!==t.length){l=!0;return}const o={};e.forEach((s,c)=>{const u=t[c]||`col${c+1}`;o[u]=(s.innerText||"").replace(/\s+/g," ").trim()});const i=Object.values(o).filter(Boolean);if(i.length<=1&&/^(暂无数据|没有数据|暂无记录|空空如也)$/.test(i[0]||""))return;n.push(o)});return{heads:t,rows:n,misaligned:l}}p({site:"xianyu",name:"seller-list",access:"read",description:`读卖家工作台列表页(${Object.keys(a).join(" / ")})—— 表头即列,动态返回`,domain:"seller.goofish.com",strategy:d.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"page",required:!0,positional:!0,help:`列表页:${Object.keys(a).join(" / ")}`}],func:async(t,n)=>{const l=String(n.page||"").trim(),r=a[l];if(!r)throw new g(`page 只能是 ${Object.keys(a).join(" / ")},收到 "${l}"`);await t.goto(`https://seller.goofish.com/?site=COMMONPRO${r.hash}`);await t.wait(4);const e=await f(t,m);if(e.error==="no-table")throw new h(`${r.label} 未渲染出表格 —— 可能未登录 / 页面改版 / 无权限`);if(e.misaligned&&(e.rows||[]).length===0)throw new h(`${r.label} 表格单元格数与表头(${(e.heads||[]).length}列)不一致,疑似带选择/展开列,需为该页单独适配以免列错位`);return e.rows||[]}});export const __test__={readTable:m,PAGE_ROUTES:a};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import{accessSync as D,constants as H,existsSync as N,mkdtempSync as j,rmSync as F,writeFileSync as X}from"node:fs";import{tmpdir as Y}from"node:os";import{delimiter as G,join as k}from"node:path";import{spawn as R,spawnSync as W}from"node:child_process";import{CliError as z}from"@jackwener/opencli/errors";const d=(u)=>new Promise((c)=>setTimeout(c,u)),s=(u)=>new z("CAPTCHA",`闲鱼验证码:系统鼠标 ${u}`);function U(u){return(process.env.PATH||"").split(G).some((c)=>N(k(c,u)))}export function systemMouseAvailable(u){if(process.platform!=="linux"||!u.getActivePage?.()||!["ydotool","ydotoold","qdbus6","journalctl"].every(U)||!process.env.XDG_CURRENT_DESKTOP?.includes("KDE"))return!1;try{D("/dev/uinput",H.W_OK);return!0}catch(c){if(c.code!=="EACCES"&&c.code!=="ENOENT")throw c;return!1}}export async function createSystemMouse(u,c){const v=j(k(Y(),"ppcli-mouse-")),y=k(v,"input.sock"),M=`__ppcli_mouse_${process.pid}_${Date.now()}`,f=u,b=`(() => {
|
|
2
|
+
const key = ${JSON.stringify(M)};
|
|
3
|
+
if (!window[key]) {
|
|
4
|
+
const state = { point: null };
|
|
5
|
+
state.listener = e => { state.point = { x: e.clientX, y: e.clientY, time: performance.timeOrigin + e.timeStamp }; };
|
|
6
|
+
document.addEventListener('mousemove', state.listener, true);
|
|
7
|
+
window[key] = state;
|
|
8
|
+
}
|
|
9
|
+
return window[key].point;
|
|
10
|
+
})()`,J=`(() => {
|
|
11
|
+
const key = ${JSON.stringify(M)}, state = window[key];
|
|
12
|
+
if (state) document.removeEventListener('mousemove', state.listener, true);
|
|
13
|
+
delete window[key];
|
|
14
|
+
return true;
|
|
15
|
+
})()`;let O;const a=R("ydotoold",[`--socket-path=${y}`],{stdio:["ignore","ignore","pipe"]});a.on("error",(t)=>{O=t});a.stderr.on("data",(t)=>console.error("[xianyu-captcha] mouse daemon",{message:String(t).trim()}));const A={...process.env,YDOTOOL_SOCKET:y};let T,g=2,h=2;const _=new Map;let q=0,K,C;function x(t,e,i=!1){const r=W(t,e,{encoding:"utf8",timeout:3000});if(r.error)throw r.error;if(r.status!==0&&!(i&&r.status===1&&!r.stderr.trim()))throw s(`${t} 调用失败:${r.stderr||r.stdout||r.status}`);return r.stdout.trim()}async function P(t=!1){const e=`ppcli-pointer-${M}-${++q}`,i=`${e}:`,r=k(v,"cursor.js");X(r,`
|
|
16
|
+
var matches = workspace.windowList().filter(function(w) { return ${C?`String(w.internalId) === ${JSON.stringify(C)}`:`String(w.resourceClass).toLowerCase().indexOf('chrome') >= 0 && String(w.caption).indexOf(${JSON.stringify(K)}) >= 0`}; });
|
|
17
|
+
var target = matches.length === 1 ? matches[0] : null;
|
|
18
|
+
if (!target) print(${JSON.stringify(i)} + JSON.stringify({error: 'Verification window is unavailable or ambiguous'}));
|
|
19
|
+
else {
|
|
20
|
+
if (${t} || !target.active) { target.minimized = false; workspace.activeWindow = target; }
|
|
21
|
+
print(${JSON.stringify(i)} + JSON.stringify({cursor: workspace.cursorPos, geometry: target.frameGeometry, active: target.active, windowId: String(target.internalId)}));
|
|
22
|
+
}
|
|
23
|
+
`);const o=x("qdbus6",["org.kde.KWin","/Scripting","org.kde.kwin.Scripting.loadScript",r,e]);try{x("qdbus6",["org.kde.KWin",`/Scripting/Script${o}`,"org.kde.kwin.Script.run"]);for(let n=0;n<5;n++){await d(40);const l=x("journalctl",["--user","--since","10 seconds ago","-o","cat","--grep",i,"-n","1"],!0),m=l.indexOf(i);if(m>=0){const w=JSON.parse(l.slice(m+i.length));if(w.error)throw s(w.error);return w}}throw s("读取桌面鼠标位置超时")}finally{x("qdbus6",["org.kde.KWin","/Scripting","org.kde.kwin.Scripting.unloadScript",e])}}function p(t){const e=W("ydotool",t,{env:A,encoding:"utf8",timeout:2000});if(e.error)throw e.error;if(e.status!==0)throw s(`输入失败:${e.stderr||e.stdout||e.status}`)}async function S(){const t=await f.evaluate(b),e=await c();let i;if(e?.frameIndex!==void 0){_.set(e.frameId,e.frameIndex);const o=await f.evaluateInFrame(b,e.frameIndex);if(o)i={...o,x:e.offset.x+o.x*e.offset.scaleX,y:e.offset.y+o.y*e.offset.scaleY}}const r=i&&(!t||i.time>t.time)?i:t;if(r)T=r;return T}async function I(t){for(let e=0;e<40;e++){const i=await S();if(!i)throw s("没有收到桌面鼠标事件,尚未按下滑块");const r=t.x-i.x,o=t.y-i.y;if(Math.hypot(r,o)<=2)return;const n=Math.round(Math.max(-30,Math.min(30,r/g))),l=Math.round(Math.max(-30,Math.min(30,o/h)));p(["mousemove","-x",String(n),"-y",String(l)]);await d(35);const m=await S();if(m&&m.time>i.time){const w=n?(m.x-i.x)/n:g,E=l?(m.y-i.y)/l:h;if(w>=0.25&&w<=4)g=w;if(E>=0.25&&E<=4)h=E}else throw s("鼠标离开了目标页面或事件未送达,停止拖动")}throw s("无法校准滑块位置,尚未完成拖动")}async function L(){if(a.exitCode===null&&a.signalCode===null){const t=new Promise((e)=>a.once("exit",e));a.kill();await Promise.race([t,d(500)]);if(a.exitCode===null&&a.signalCode===null)a.kill("SIGKILL");await Promise.race([t,d(500)])}await f.evaluate(J).catch((t)=>console.error("[xianyu-captcha] pointer cleanup failed",{error:t}));for(const t of await f.frames())if(_.has(t.frameId))await f.evaluateInFrame(J,t.index).catch((e)=>console.error("[xianyu-captcha] frame cleanup failed",{error:e}));F(v,{recursive:!0,force:!0})}try{for(let r=0;!N(y)&&r<25;r++){if(O)throw O;if(a.exitCode!==null)throw s(`虚拟鼠标启动失败(${a.exitCode})`);await d(40)}if(!N(y))throw s("虚拟鼠标启动超时");await d(1000);K=await f.evaluate("document.title");const t=await P(!0);if(!t.active)throw s("验证窗口尚未激活");C=t.windowId;const e=await f.evaluate("({ top: outerHeight - innerHeight, width: innerWidth, height: innerHeight })");await S();const i={x:t.geometry.x+e.width/3,y:t.geometry.y+e.top+e.height/3};for(let r=0;r<60;r++){const{cursor:o}=await P(),n=i.x-o.x,l=i.y-o.y;if(Math.hypot(n,l)<4)break;p(["mousemove","-x",String(Math.round(Math.max(-100,Math.min(100,n/2)))),"-y",String(Math.round(Math.max(-100,Math.min(100,l/2))))]);await d(30)}await d(100);if(!await S())throw s("目标 Chrome 未收到鼠标事件");return{async click(r){await I(r);p(["click","0xC0"])},async drag(r){await I(r[0]);p(["click","0x40"]);try{let o=r[0];for(const n of r.slice(1)){p(["mousemove","-x",String(Math.round((n.x-o.x)/g)),"-y",String(Math.round((n.y-o.y)/h))]);o=n;await d(16+Math.random()*12)}if((await c())?.blocked)await I(r.at(-1))}finally{p(["click","0x80"])}},close:L}}catch(t){console.error("[xianyu-captcha] system input failed",{error:t});await L();throw t}}
|
package/clis/xianyu/watch.js
CHANGED
|
@@ -1 +1,43 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{evaluateWithCaptcha as w}from"./captcha.js";import{AuthRequiredError as d,CommandExecutionError as u}from"@jackwener/opencli/errors";import{registerWatch as _}from"../_shared/watch.js";import{buildChatUrl as p,buildInboxUrl as o,requireEvaluateObject as g}from"./im.js";const y=/"lwp"\s*:\s*"\/s\//,S=[300,700,1000,1500],c=1e4,h=`
|
|
2
|
+
(() => {
|
|
3
|
+
const rows = Array.from(document.querySelectorAll('#conv-list-scrollable [class*="conversation-item"]'));
|
|
4
|
+
const sessions = [];
|
|
5
|
+
for (const row of rows) {
|
|
6
|
+
const fk = Object.keys(row).find((k) => k.startsWith('__reactFiber'));
|
|
7
|
+
let f = fk ? row[fk] : null;
|
|
8
|
+
let info = null;
|
|
9
|
+
for (let d = 0; f && d < 25; d++, f = f.return) {
|
|
10
|
+
const p = f.memoizedProps;
|
|
11
|
+
if (p && p.sessionInfo) { info = p.sessionInfo; break; }
|
|
12
|
+
}
|
|
13
|
+
if (!info) continue;
|
|
14
|
+
const summary = info.summary || {};
|
|
15
|
+
const lm = summary.latestMessage || {};
|
|
16
|
+
const c = lm.content || {};
|
|
17
|
+
const item = info.itemInfo || {};
|
|
18
|
+
sessions.push({
|
|
19
|
+
session_id: String(info.sessionId || ''),
|
|
20
|
+
session_type: info.sessionType,
|
|
21
|
+
peer_user_id: String((info.userInfo && info.userInfo.userId) || ''),
|
|
22
|
+
peer_name: String((lm.reminder && lm.reminder.title) || (info.userInfo && (info.userInfo.fishNick || info.userInfo.nick)) || ''),
|
|
23
|
+
item_id: String(item.itemId || ''),
|
|
24
|
+
item_title: String(item.title || ''),
|
|
25
|
+
price: String(item.price || ''),
|
|
26
|
+
seller_id: String((item.sellerInfo && item.sellerInfo.userId) || ''),
|
|
27
|
+
sender_id: String((lm.senderInfo && lm.senderInfo.userId) || ''),
|
|
28
|
+
message_id: String(lm.messageId || ''),
|
|
29
|
+
content_type: c.contentType,
|
|
30
|
+
text: String((c.text && c.text.text) || summary.summaryContent || ''),
|
|
31
|
+
ts: Number(lm.timeStamp || summary.timeStamp || 0),
|
|
32
|
+
unread: Number(info.unread || 0),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const bodyText = rows.length ? '' : (document.body ? document.body.innerText : '');
|
|
36
|
+
return {
|
|
37
|
+
rows: rows.length,
|
|
38
|
+
sessions,
|
|
39
|
+
requiresAuth: /请先登录|登录后/.test(bodyText),
|
|
40
|
+
blocked: /验证码|安全验证|异常访问/.test(bodyText),
|
|
41
|
+
};
|
|
42
|
+
})()
|
|
43
|
+
`;async function a(t){const s=g(await w(t,h),"watch sessions");if(!s.rows&&s.requiresAuth)throw new d("www.goofish.com","Xianyu IM requires a logged-in browser session");if(!s.rows&&s.blocked)throw new d("www.goofish.com","Xianyu IM is blocked by verification or risk control");if(s.rows&&!s.sessions.length)throw new u("闲鱼会话列表渲染出来了,但行上读不到 sessionInfo:页面结构可能改版了");return s}function l(t,s){const n=[];for(const e of t){if(!e.message_id||!e.peer_user_id)continue;const i=e.sender_id===e.peer_user_id;if(!i&&!s?.includeOwn)continue;n.push({event:"dm",id:e.message_id,ts:e.ts,conversation_id:e.session_id,sender:{id:e.sender_id,name:i?e.peer_name:""},text:e.text,url:e.item_id?p(e.item_id,e.peer_user_id):o(),raw:{session_id:e.session_id,session_type:e.session_type,peer_user_id:e.peer_user_id,item_id:e.item_id,item_title:e.item_title,price:e.price,seller_id:e.seller_id,content_type:e.content_type,unread_count:e.unread,from_peer:i}})}return n}function m(t){return t.map((s)=>`${s.session_id}:${s.message_id}`).join("|")}_({site:"xianyu",name:"watch",domain:"www.goofish.com",description:"闲鱼私信监听:旁听网页版 IM 自己的 WebSocket 推送,一有新消息就读已打开的会话列表,按 NDJSON 逐条输出",watchUrl:o(),pattern:"wss-goofish.dingtalk.com",parse:async(t,s)=>{if(!y.test(String(t?.responsePreview??"")))return null;const n=Number(t?.timestamp??0);if(n&&s.state.readStartedAt&&n<s.state.readStartedAt)return null;s.state.readStartedAt=Date.now();const e=s.state.fingerprint;let i=null;for(const r of S){await new Promise((f)=>setTimeout(f,r));i=await a(s.page);if(m(i.sessions)!==e)break}s.state.fingerprint=m(i.sessions);return l(i.sessions,s)},reconcile:async(t,s)=>{const n=await(t.getCurrentUrl?t.getCurrentUrl().catch(()=>""):Promise.resolve(""));if(!/https:\/\/www\.goofish\.com\/im\b/.test(String(n||"")))await t.goto(o());const e=Date.now()+c;let i=await a(t);while(!i.rows&&Date.now()<e){await new Promise((r)=>setTimeout(r,500));i=await a(t)}if(!i.rows)throw new u(`闲鱼会话列表 ${c/1000} 秒内没有渲染出来(收件箱为空,或页面没加载完)`);s.state.fingerprint=m(i.sessions);return l(i.sessions,s)}});
|