diff --git a/NEXT-ROUND-PROMPT.md b/NEXT-ROUND-PROMPT.md index eb52c12..f57da66 100644 --- a/NEXT-ROUND-PROMPT.md +++ b/NEXT-ROUND-PROMPT.md @@ -65,10 +65,14 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){window.__cap.push(l+': '+[...a ## 5. 这轮要做的(按优先级) -0. **EAN 台账回 S03(最优先,硬约束)** - Temu 这 46 个号目前只落在 KVM-2 的 `temu建品/ean_temu_used.txt` - (pc 3447~3551,游标 `ean_cursor.json` → **3552**)。KVM-2 够不到 S03, - **需要在 S03 上把这份台账并进 `/root/ean_*.txt`**,之后从 3552 续号。 +0. ~~**EAN 台账回 S03(最优先,硬约束)**~~ ✅ **2026-09-21 已完成** + pc 3400~3551(Walmart 3400~3446 + Temu 3447~3551)已并进 **SG3 `43.134.190.229`** 的 + `/root/ean_69803093_3400-3551.txt`(152 行 = 连续区间,含 58 个重跑烧掉的空洞), + 两端 sha256 一致、SG3 全台账跨文件重复 0。 + ⚠️ **纠正一个长期误判:「S03」是客户/团队代号(湖北易千亿彩),不是主机名** —— 台账主机是 + **SG3**,经 **A3** 落账(`ssh -i ~/9webs_sites.pem root@43.134.190.229`,key 在 A3 .102; + 别名 `ssh SG3` 解析不了,一律用 IP)。下一号 **pc 3552**。 + 以后一律用 `~/ean-sync/sync_to_sg3.sh`(幂等:哈希一致即跳过,区间重叠则中止),别再手工推。 1. **继续铺剩余店小秘平台**(已侦察,直接用): diff --git a/README.md b/README.md index db4ecc1..fe3262b 100644 --- a/README.md +++ b/README.md @@ -117,12 +117,28 @@ function wrap(o, n, l) { - 结构:8 位前缀 `69803093` + 4 位产品码 + 1 位校验 - 校验位:`(10 - sum(d*(1 if i%2==0 else 3) for i,d in enumerate(d12)) % 10) % 10` (左起权重 1,3,1,3…;UPC-A 权重相反,别混用) -- 台账唯一来源:**S03 主机 `/root/ean_*.txt`**(KVM-2 够不到) -- 已用 3190,下一号 pc 3400 → `6980309334003` -- **本批已分配**:Walmart pc **3400~3446**(见 `walmart建品/ean_69803093_3400-3446.txt`); - Temu 全托管 pc **3447~3551**(46 条,逐条见 `temu建品/ean_temu_used.txt`; - 游标 `temu建品/ean_cursor.json` → 下一号 pc **3552**) -- ⚠️ **必须在 S03 落盘**,否则下次续号会撞号。Temu 这批目前只落在 KVM-2 - (`temu建品/ean_temu_used.txt`),**还没回 S03** —— 下一轮开工第一件事就把它并进 - `/root/ean_*.txt` +- 台账唯一来源:**SG3 `43.134.190.229` 的 `/root/ean_*.txt`**(KVM-2 够不到,经 A3 落账) + - ⚠️ **「S03」是客户/团队代号(湖北易千亿彩),不是主机名** —— 舰队里没有叫 S03 的机器, + KVM-2 与 A3 的 hosts/DNS/known_hosts/记忆全查不到。别再找"S03 是哪台"了。 + - 登录:`ssh -i ~/9webs_sites.pem root@43.134.190.229`(key 在 **A3 192.168.9.102**, + `~/9webs_sites.pem`)。⚠️ 别名 `ssh SG3` **解析不了**,一律用 IP。 + - 格式惯例:**一个文件 = 一个完整连续区间** `ean_69803093_-.txt`,一行一个 EAN、 + 无表头无注释;区间的标签与内容都按"整段已发放"算 —— **空洞也要写进去** + (失败重跑烧掉的号,不写进去下次就会重发)。另有 `698030931_*`(9 位前缀)与 `upc_*` 系列,别混。 +- 已用情况(2026-09-21 核对): + - Walmart pc **3400~3446**(`walmart建品/ean_69803093_3400-3446.txt`) + - Temu 全托管 pc **3447~3551**(逐条见 `temu建品/ean_temu_used.txt`; + 游标 `temu建品/ean_cursor.json` → 下一号 pc **3552**) + - ✅ **已落 SG3**:`/root/ean_69803093_3400-3551.txt`(152 行 = pc 3400~3551 连续,含 58 个 + 重跑烧掉的空洞),两端 sha256 一致。SG3 全台账 = 11 个文件 / 2442 个号 / **跨文件重复 0**。 +- 工具(别再手搓,`~/ean-sync/`): + - `verify_ledger.py` —— 校验位复算、`-FIX` 取代规则、与 `tm_done.json` 对账、游标分叉检查, + 产出 `out/merge-.txt` 与 `out/pid-map-.json` + - `sync_to_sg3.sh` —— **幂等**同步:算区间 → 生成连续区间文件 → 查 SG3(哈希一致即跳过) + → 重叠安全检查(除目标文件外该区间有号就中止)→ 经 A3 推送 → 哈希复核(`--dry` 只看不动) +- ⚠️ 发号**只看台账**,不看 `tm_done.json` —— 它是断点文件、换号后不更新(实测它对 + `548459586741` 记 3449、对 `764423265986` 记 3449,均与台账不符) +- `-FIX` 行**取代**同一 pid 的早先行(pc 3449 曾被同时发给两个 pid,其中一个已由 + `tm_fixean2.mjs` 换到 pc 3551);但同一 pid 在不同轮次拿到**不同**号(如 548459586741 的 + 3449 与 3450)是**两次真实消耗**,两个号都已作废,不能当成修正丢掉前一个 - 说明书 §1 的例子 `pc 1000 → 6980309310004` **是错的**,正确是 `6980309310007` diff --git a/skill/SKILL.md b/skill/SKILL.md index 768c5c7..252c9d3 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -96,7 +96,13 @@ Ozon ≤500g 档(用户给定的表,已解出公式): - 结构:`69803093` + 4 位产品码 + 1 位校验 - 校验:`(10 - sum(d*(1 if i%2==0 else 3) for i,d in enumerate(d12)) % 10) % 10` (左起权重 1,3,1,3…;**UPC-A 权重相反**) -- 台账唯一来源 **S03 `/root/ean_*.txt`**(KVM-2 够不到)→ 用号后**必须去 S03 落盘**,否则撞号 +- 台账唯一来源 **SG3 `43.134.190.229` 的 `/root/ean_*.txt`**(KVM-2 够不到,经 A3 落账)→ + 用号后**必须同步过去**,否则撞号。⚠️ **「S03」是客户/团队代号,不是主机名**(舰队里没有这台机器); + 登录 `ssh -i ~/9webs_sites.pem root@43.134.190.229`(key 在 A3 .102),**别名 `ssh SG3` 解析不了**。 + 格式惯例:一个文件 = 一个完整连续区间 `ean_69803093_-.txt`,**空洞也要写进去**。 + 同步用 `~/ean-sync/sync_to_sg3.sh`(幂等,哈希一致即跳过,重叠则中止) +- 2026-09-21:pc **3400~3551** 已落 SG3(`/root/ean_69803093_3400-3551.txt`,152 行), + 下一号 pc **3552**。发号只看台账,**不看 `tm_done.json`**(断点文件、换号后不更新) - 说明书 §1 例子 `pc 1000 → 6980309310004` 是**错的**,正确 `6980309310007` ## 7. 批量作业约定 diff --git a/妙手建品/RECIPE.md b/妙手建品/RECIPE.md index 74a36bf..7c8167f 100644 --- a/妙手建品/RECIPE.md +++ b/妙手建品/RECIPE.md @@ -219,3 +219,16 @@ await c.send('DOM.setFileInputFiles', { files: ['/path/ms_import.csv'], nodeId } ### 下一步(明确到一次点击) 进 `/common_collect_box/items` → 点 **`导入产品`** → 看它是否列出 `ms2`(或那 47 条)→ 选择并确认 → 回到列表看是否出现 47 条 → 然后才是 `认领到` → 发布权限验证。 + +## 注意:妙手会话易失 + 登录表单在弹窗里(第 5 轮踩到) +- 关掉妙手标签后,新开标签访问受保护页会被重定向到 + `https://erp.91miaoshou.com/?redirect=%2Fcommon_collect_box%2Fitems` —— **等于登出**,需重新登录。 +- 登录表单**在一个弹窗里**:先点首页的「登录」把弹窗打开, + 再填 `.login-form__input`(账号/密码)与 `.captcha-field__input`,最后点 `.login-form__submit-btn`。 + **顺序错了会拿到 `Cannot read properties of null`**(选择器全空)——本轮我就是这么失败一次。 +- 验证码每次都要重新读(旧码会失效):取 `.captcha-field__img` 的 base64 → 存 `.jpg` → 读图。 +- **建议**:需要连续操作妙手时,别关那个标签页;中途关闭会白付一次登录(含验证码)。 + +### 「导入产品」按钮:本轮没测到 +点它之前会话就掉了(登录也未成功),所以**触发点仍未验证**。 +恢复顺序:重新登录 → `/common_collect_box/items` → 点 `导入产品` → 看是否列出 `ms2` → 确认后回列表数 47 条。 diff --git a/妙手建品/ms_cap3.mjs b/妙手建品/ms_cap3.mjs new file mode 100644 index 0000000..f562ae8 --- /dev/null +++ b/妙手建品/ms_cap3.mjs @@ -0,0 +1,20 @@ +import { listPages, wait } from '/home/admin9webs/shein建品/sh_lib.mjs'; +import { CDP } from '/tmp/cdp3.mjs'; +import { writeFileSync } from 'fs'; +const t = (await listPages()).find(x=>/91miaoshou/i.test(x.url)); +const c = await CDP.connect(t.webSocketDebuggerUrl); +await c.send('Page.enable'); await c.send('Runtime.enable'); +if (!String(await c.evalJS('location.href')).includes('91miaoshou.com/?redirect') && !String(await c.evalJS('!!document.querySelector(".captcha-field__img")'))) { + await c.send('Page.navigate',{url:'https://erp.91miaoshou.com/'}); await wait(10000); +} +// 确保登录弹窗/表单可见 +await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button,a')].find(e=>/^\\s*登录\\s*$/.test((e.innerText||'').trim())); if(b) b.click(); return 1;})()`); +await wait(2500); +const src = String(await c.evalJS(`(()=>{const i=document.querySelector('.captcha-field__img'); return i?(i.src||''):'';})()`)); +const b64 = src.includes('base64,') ? src.split('base64,')[1] : ''; +if(!b64){ console.log('没找到验证码图'); process.exit(1); } +writeFileSync('/tmp/ms_cap3.jpg', Buffer.from(b64,'base64')); +console.log('验证码已存 /tmp/ms_cap3.jpg'); +const f = await c.evalJS(`JSON.stringify({acc:!!document.querySelector('input.login-form__input[placeholder="手机号/子账号/邮箱"]'), pwd:!!document.querySelector('input.login-form__input[type=password]'), btn:!!document.querySelector('.login-form__submit-btn')})`); +console.log('表单:', String(f)); +process.exit(0); diff --git a/妙手建品/ms_importprod.mjs b/妙手建品/ms_importprod.mjs new file mode 100644 index 0000000..0f8005f --- /dev/null +++ b/妙手建品/ms_importprod.mjs @@ -0,0 +1,27 @@ +import { listPages, wait } from '/home/admin9webs/shein建品/sh_lib.mjs'; +import { CDP, SAN as S } from '/tmp/cdp3.mjs'; +const t = (await listPages()).find(x=>/91miaoshou/i.test(x.url)); +const c = await CDP.connect(t.webSocketDebuggerUrl); +await c.send('Page.enable'); await c.send('Runtime.enable'); +await c.send('Page.navigate',{url:'https://erp.91miaoshou.com/common_collect_box/items'}); +await wait(12000); +const pos = JSON.parse(String(await c.evalJS(`(()=>{ + const bs=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null&&(e.innerText||'').trim()==='导入产品'); + if(!bs.length) return '[]'; const b=bs[0]; b.scrollIntoView({block:'center'}); + const r=b.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`))); +console.log('导入产品坐标:', JSON.stringify(pos)); +if(pos.length){ + await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(300); + for(const tp of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:tp,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1}); +} +await wait(6000); +const o = await c.evalJS(`(()=>{ + const dlgs=[...document.querySelectorAll('.jx-dialog,.jx-drawer,[class*=dialog]')].filter(e=>e.offsetParent!==null) + .map(e=>({txt:(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,300), + btns:[...e.querySelectorAll('button')].map(b=>(b.innerText||'').trim()).filter(Boolean).slice(0,8), + files:[...e.querySelectorAll('input[type=file]')].length})); + const rows=[...document.querySelectorAll('tr')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).filter(Boolean).slice(0,4); + return JSON.stringify({href:location.href, dlgs:dlgs.slice(0,2), rows});})()`); +console.log('结果:', S(String(o)).slice(0,1100)); +try{ await c.shot('/tmp/ms_importprod.jpg'); console.log('shot ok'); }catch(e){} +process.exit(0); diff --git a/妙手建品/ms_login3.mjs b/妙手建品/ms_login3.mjs new file mode 100644 index 0000000..276c73f --- /dev/null +++ b/妙手建品/ms_login3.mjs @@ -0,0 +1,39 @@ +import { listPages, wait } from '/home/admin9webs/shein建品/sh_lib.mjs'; +import { CDP, SAN as S } from '/tmp/cdp3.mjs'; +import { readFileSync } from 'fs'; +const doc=readFileSync('/home/admin9webs/agent-a2-import/skills/ecommerce/miaoshou-erp/SKILL.md','utf8'); +const acc=(doc.match(/xiaochaoren\d+/)||[])[0], pwd=(doc.match(/Bingo\d+@/)||[])[0]; +const CAP=process.argv[2]; +const t=(await listPages()).find(x=>/91miaoshou/i.test(x.url)); +const c=await CDP.connect(t.webSocketDebuggerUrl); +await c.send('Page.enable'); await c.send('Runtime.enable'); await wait(1200); +await c.evalJS(`(()=>{const set=(sel,v)=>{const el=document.querySelector(sel); if(!el) return 0; + const s=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set; s.call(el,v); + el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); return 1;}; + const a=document.querySelector('input.login-form__input[placeholder="手机号/子账号/邮箱"]'); + const p=document.querySelector('input.login-form__input[type=password]'); + const k=document.querySelector('.captcha-field__input'); + return JSON.stringify({a:set(a, ${JSON.stringify(acc)}), p:set(p, ${JSON.stringify(pwd)}), k:set(k, ${JSON.stringify(CAP)})});})()`); +await wait(700); +await c.evalJS(`(()=>{const b=document.querySelector('.login-form__submit-btn'); if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`); +await wait(12000); +const st=await c.evalJS(`JSON.stringify({href:location.href, login:!!document.querySelector('input[type=password]'), err:[...document.querySelectorAll('*')].filter(e=>e.children.length===0&&/错误|不正确|锁定/.test(e.innerText||'')).map(e=>e.innerText.trim()).slice(0,2)})`); +console.log('登录:', S(String(st)).slice(0,220)); +// 进采集箱点「导入产品」 +await c.send('Page.navigate',{url:'https://erp.91miaoshou.com/common_collect_box/items'}); await wait(13000); +const pos=JSON.parse(String(await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null&&(e.innerText||'').trim()==='导入产品'); + if(!b.length) return '[]'; b[0].scrollIntoView({block:'center'}); const r=b[0].getBoundingClientRect(); + return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`))); +console.log('导入产品坐标:', JSON.stringify(pos)); +if(pos.length){ + await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(300); + for(const tp of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:tp,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1}); + await wait(6000); +} +const o=await c.evalJS(`(()=>{const dlgs=[...document.querySelectorAll('.jx-dialog,.jx-drawer,[class*=dialog]')].filter(e=>e.offsetParent!==null) + .map(e=>({txt:(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,260), btns:[...e.querySelectorAll('button')].map(b=>(b.innerText||'').trim()).filter(Boolean).slice(0,6)})); + const body=(document.body.innerText||'').replace(/\\s+/g,' '); + return JSON.stringify({dlgs:dlgs.slice(0,2), empty:/暂无数据/.test(body), body:body.slice(0,160)});})()`); +console.log('导入产品结果:', S(String(o)).slice(0,1000)); +try{ await c.shot('/tmp/ms_ip.jpg'); console.log('shot ok'); }catch(e){} +process.exit(0); diff --git a/妙手建品/ms_open.mjs b/妙手建品/ms_open.mjs new file mode 100644 index 0000000..8c32993 --- /dev/null +++ b/妙手建品/ms_open.mjs @@ -0,0 +1,33 @@ +import { newPage, listPages, wait } from '/home/admin9webs/shein建品/sh_lib.mjs'; +import { CDP, SAN as S } from '/tmp/cdp3.mjs'; +let t = (await listPages()).find(x=>/91miaoshou/i.test(x.url)); +let c; +if (t) { c = await CDP.connect(t.webSocketDebuggerUrl); await c.send('Page.enable'); await c.send('Runtime.enable'); } +else { + console.log('无妙手标签,新开…'); + c = await newPage('https://erp.91miaoshou.com/common_collect_box/items'); + await wait(14000); +} +await c.send('Page.navigate',{url:'https://erp.91miaoshou.com/common_collect_box/items'}); +await wait(13000); +const st = await c.evalJS(`JSON.stringify({href:location.href, login:!!document.querySelector('input[type=password]'), title:document.title})`); +console.log('页面:', S(String(st)).slice(0,200)); +const pos = JSON.parse(String(await c.evalJS(`(()=>{ + const bs=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null&&(e.innerText||'').trim()==='导入产品'); + if(!bs.length) return '[]'; const b=bs[0]; b.scrollIntoView({block:'center'}); + const r=b.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`))); +console.log('导入产品坐标:', JSON.stringify(pos)); +if(pos.length){ + await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(300); + for(const tp of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:tp,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1}); + await wait(6000); +} +const o = await c.evalJS(`(()=>{ + const dlgs=[...document.querySelectorAll('.jx-dialog,.jx-drawer,[class*=dialog]')].filter(e=>e.offsetParent!==null) + .map(e=>({txt:(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,260), btns:[...e.querySelectorAll('button')].map(b=>(b.innerText||'').trim()).filter(Boolean).slice(0,6), files:[...e.querySelectorAll('input[type=file]')].length})); + const rows=[...document.querySelectorAll('tr')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).filter(Boolean).slice(0,4); + const body=(document.body.innerText||'').replace(/\\s+/g,' '); + return JSON.stringify({dlgs:dlgs.slice(0,2), rows, empty:/暂无数据/.test(body)});})()`); +console.log('结果:', S(String(o)).slice(0,1200)); +try{ await c.shot('/tmp/ms_importprod.jpg'); console.log('shot ok'); }catch(e){} +process.exit(0);