店小秘多平台铺货配方:7 平台建品脚本 + 静默拦截诊断法 + 项目状态

- TikTok/Shopee/速卖通/Walmart 各 46-47 条已在待发布
- Ozon 47 条已建、改价 40/47
- SHEIN 配方跑通第一条;Temu 差「预览图」一步
- 核心产出:MYJ.message.error 钩子诊断法(解开了三个平台的保存静默拦截)
- 含下一轮提示词 NEXT-ROUND-PROMPT.md
This commit is contained in:
admin9webs committed 2026-09-20 17:29:38 +08:00
commit f5720ad613
94 files changed
+21561

No files matched your search

+211
View File
@@ -0,0 +1,211 @@
import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
import fs from 'fs';
/* ── TikTok (AutoMavenShop_MY) 建品配方 ──
node tkbatch.mjs <oid> [catKeyword] [variantValue]
流程:选店铺 → 分类弹窗 → 标题/卖点/关键词/描述 → 变种属性 → SKU价+库存
→ 重量/尺寸 → 仓库 → 图片(外链) → 来源URL → 保存 → 回读 */
const OID = process.argv[2];
const CAT_KW = process.argv[3] || '';
const VAR_VAL = process.argv[4] || 'Default';
if (!OID) { console.log('用法: node tkbatch.mjs <oid> [catKeyword] [variantValue]'); process.exit(1); }
const IN = JSON.parse(fs.readFileSync('/tmp/tk_input.json', 'utf8'))[OID];
if (!IN) { console.log('无输入数据', OID); process.exit(1); }
const wait = ms => new Promise(z => setTimeout(z, ms));
const log = (...a) => console.log(' ', ...a);
const DIMS = (() => { const W = Math.max(100, Math.round(IN.weight * 1.3));
return { L: Math.max(60, Math.min(400, W)), Wi: Math.max(40, Math.min(300, Math.round(W * 0.6))), H: Math.max(10, Math.min(200, Math.round(W * 0.2))) }; })();
// 0. 清理编辑页
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
let k = 0;
for (const t of tl.filter(x => x.type === 'page' && /tiktok/i.test(x.url))) { await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(() => {}); k++; }
if (k) { log(`清理 tiktok 页 ${k} 个`); await wait(1200); }
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate', { url: 'https://www.dianxiaomi.com/web/tiktokProduct/add' });
await wait(20000);
const cur = async () => String(await c.evalJS(`(()=>{const s=[...document.querySelectorAll('.ant-select')].filter(e=>e.offsetParent!==null)[0];
return s?((s.querySelector('.ant-select-selection-item')||{}).innerText||''):'';})()`));
// 1. 选店铺 AutoMavenShop_MY
async function pickShop() {
for (let attempt = 0; attempt < 3; attempt++) {
const pos = JSON.parse(String(await c.evalJS(`(()=>{const s=[...document.querySelectorAll('.ant-select')].filter(e=>e.offsetParent!==null)[0];
if(!s)return'[]'; s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if (!pos.length) return 'no-select';
for (const type of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type, x: pos[0], y: pos[1], button: 'left', clickCount: 1 });
await wait(1800);
await c.evalJS(`(()=>{const inp=document.querySelector('.ant-select-selection-search-input')||document.querySelector('.ant-select input');
if(!inp)return 0; inp.focus();
Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(inp,'AutoMaven');
inp.dispatchEvent(new Event('input',{bubbles:true})); return 1;})()`);
await wait(2200);
const r = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null);
if(!dd.length)return'no-dd'; const d=dd[dd.length-1];
const hit=[...d.querySelectorAll('.ant-select-item-option')].find(o=>/AutoMavenShop_MY/i.test(o.innerText||''));
if(!hit)return'no-hit';
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked';})()`);
await wait(6000);
const s = await cur();
if (/AutoMavenShop_MY/i.test(s)) return 'ok:' + s;
log(' 店铺重试', attempt + 1, r, JSON.stringify(s));
}
return 'fail';
}
// 2. 分类
async function pickCategory(kw) {
await c.evalJS(`(()=>{document.querySelectorAll('.ant-select-dropdown').forEach(d=>{if(d.offsetParent!==null)d.style.display='none';});
document.body.dispatchEvent(new MouseEvent('mousedown',{bubbles:true})); document.body.click(); return 1;})()`);
await wait(1000);
const open = await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&/^\\s*选择分类\\s*$/.test(e.innerText||''));
if(!b.length)return'nf'; for(const ev of ['mousedown','mouseup','click']) b[0].dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'ok';})()`);
if (open === 'nf') return 'no-btn';
await wait(3500);
const r = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const inp=m.querySelector('input[placeholder="搜索分类名称"]'); if(!inp)return'no-input';
inp.focus(); Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(inp,${JSON.stringify(kw)});
inp.dispatchEvent(new Event('input',{bubbles:true})); inp.dispatchEvent(new Event('change',{bubbles:true}));
const b=[...m.querySelectorAll('button')].find(x=>/^\\s*搜索\\s*$/.test(x.innerText||''));
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'searched';})()`);
if (r !== 'searched') return r;
await wait(4000);
const pick = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const its=[...m.querySelectorAll('.search-result-item')];
if(!its.length)return'no-result';
const BAD=/二手|Barang Terpakai|Second|Used|Pre-?owned|Refurb/i;
const texts=its.map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
let idx=texts.findIndex(t=>!BAD.test(t));
if(idx<0) idx=0;
const hit=its[idx];
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked['+idx+'/'+texts.length+']:'+texts[idx];})()`);
await wait(4500);
return pick;
}
// 2b. 多关键词回退 + 失败即中止
async function pickCategoryAny(kws) {
const tried = [];
for (const kw of kws) {
const r = String(await pickCategory(kw));
tried.push(kw + '\u2192' + r.slice(0, 45));
if (!/no-result|no-input|no-modal|no-btn/.test(r)) return { r, tried };
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(m){const x=m.querySelector('.ant-modal-close'); if(x&&x.offsetParent!==null)x.click();} return 1;})()`);
await wait(1500);
}
return { r: 'ALL-FAILED', tried };
}
// 3. 直填 store
const setStore = (js) => c.evalJS(js);
log('=== TikTok 建品 ===', OID, IN.title.slice(0, 50));
log('1) 店铺:', await pickShop());
const kw = CAT_KW || IN.cat_kw || IN.title.split(/[ ,]+/).slice(0, 2).join(' ');
const catRes = await pickCategoryAny([kw].concat(IN.cat_fb || []));
log('2) 分类:', SAN(String(catRes.r)).slice(0, 110), '| 尝试:', catRes.tried.join(' / '));
if (/ALL-FAILED|no-result|no-input|no-btn/.test(String(catRes.r))) { log('\u274c 分类未选中,跳过保存'); await c.shot('/tmp/tk_fail_' + OID + '.jpeg').catch(() => {}); process.exit(2); }
// 4. 文案
const desc = IN.bullets.map(b => '• ' + b).join('<br>') + '<br><br>Keywords: ' + (IN.keywords || '');
const wr = await setStore(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const f=p._s.get('tiktokGlobalAddProductStore').$state.formState;
f.productName=${JSON.stringify(IN.title)};
f.keyProductFeatures=${JSON.stringify(IN.bullets.slice(0, 5))};
f.searchTerms=${JSON.stringify((IN.keywords || '').split(',').map(s => s.trim()).filter(Boolean).slice(0, 10))};
f.description='<style>img{display: block}p{margin-block-start: 0; margin-block-end: 0}</style>'+${JSON.stringify(desc)};
return 'ok';})()`);
log('3) 文案:', wr, '| 标长', IN.title.length);
// 5. 变种属性
const vr = await c.evalJS(`(()=>{
const h=[...document.querySelectorAll('*')].filter(e=>e.offsetParent!==null&&/^\\s*变种属性\\s*$/.test((e.innerText||'').trim())).pop();
if(!h)return'no-sec'; let box=h;
for(let i=0;i<6&&box.parentElement;i++){ box=box.parentElement; if(box.querySelector('input[class*="!w-200"]'))break; }
const inp=box.querySelector('input[class*="!w-200"]'); if(!inp)return'no-inp';
inp.focus(); Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(inp,${JSON.stringify(VAR_VAL)});
inp.dispatchEvent(new Event('input',{bubbles:true})); inp.dispatchEvent(new Event('change',{bubbles:true}));
inp.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',code:'Enter',keyCode:13,which:13,bubbles:true}));
const btn=[...box.querySelectorAll('button,a,span')].find(e=>/^\\s*(添加|Add)\\s*$/.test(e.innerText||''));
if(btn) for(const ev of ['mousedown','mouseup','click']) btn.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'ok';})()`);
log('4) 变种属性:', vr, VAR_VAL);
await wait(3500);
// 6. SKU 价格/库存
const SKU = 'AM-' + OID;
const sr = await c.evalJS(`(()=>{
const setV=(el,v)=>{ Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,String(v));
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); };
const tbl=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).find(t=>[...t.querySelectorAll('th')].some(h=>/本地展示价/.test(h.innerText||'')));
if(!tbl)return'no-table';
const ths=[...tbl.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
const rows=[...tbl.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length);
const done=[];
rows.forEach((tr,ri)=>{ [...tr.querySelectorAll('td')].forEach((td,ci)=>{ const name=ths[ci]||''; const ins=[...td.querySelectorAll('input')];
if(/^SKU/i.test(name)&&ins[0]&&!ins[0].value){ setV(ins[0],${JSON.stringify(SKU)}+(rows.length>1?('-'+ri):'')); done.push('sku'); }
if(/本地展示价/.test(name)&&ins[0]&&!ins[0].value){ setV(ins[0],${JSON.stringify(String(IN.price_myr))}); done.push('price'); }
if(/^库存/.test(name)&&ins[0]&&!ins[0].value){ setV(ins[0],'999'); done.push('stock'); } }); });
return 'rows'+rows.length+':'+[...new Set(done)].join(',');})()`);
log('5) SKU:', sr, '| MYR', IN.price_myr);
await wait(2000);
// 7. 重量 + 尺寸
const setItem = (rx, sel, val) => c.evalJS(`(()=>{
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(x=>{const l=x.querySelector('label'); return l&&new RegExp(${JSON.stringify(rx)}).test(l.innerText||'');});
const el=it?it.querySelector(${JSON.stringify(sel)}):null; if(!el)return'nf';
const proto=el.tagName==='TEXTAREA'?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;
Object.getOwnPropertyDescriptor(proto,'value').set.call(el, ${JSON.stringify(String(val))});
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); return 'ok';})()`);
log('6) 重量:', await setItem('重量', 'input:not([type=hidden])', IN.weight));
const dim = await c.evalJS(`(()=>{
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(x=>{const l=x.querySelector('label'); return l&&/尺寸规格/.test(l.innerText||'');});
if(!it)return'nf'; const ins=[...it.querySelectorAll('input')];
const setV=(el,v)=>{ Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,String(v));
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); };
const L=ins.find(i=>i.placeholder==='长')||ins[0], W2=ins.find(i=>i.placeholder==='宽')||ins[1], H=ins.find(i=>i.placeholder==='高')||ins[2];
if(L)setV(L,${DIMS.L}); if(W2)setV(W2,${DIMS.Wi}); if(H)setV(H,${DIMS.H});
return 'ok:'+ins.length;})()`);
log('7) 尺寸:', dim, JSON.stringify(DIMS));
// 8. 图片
const ir = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const st=p._s.get('tiktokGlobalImgStore').$state;
const urls=${JSON.stringify(IN.imgs)};
st.formState.mainImage=urls.map((u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800,checked:true}));
return 'imgs:'+st.formState.mainImage.length;})()`);
log('8) 图片:', ir, '(共', IN.imgs.length, ')');
await wait(2500);
// 9. 来源URL
log('9) 来源URL:', await setItem('来源URL', 'input:not([type=hidden])', 'https://detail.1688.com/offer/' + OID + '.html'));
await wait(1200);
// 10. 保存
const before = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const b=p._s.get('tiktokGlobalAddBasicStore').$state;
return JSON.stringify({shopId:b.formState.shopId, cat:b.formState.categoryId, site:b.site});})()`);
log('10) 保存前状态:', before);
const sv = await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&/^\\s*保存\\s*$/.test(e.innerText||''));
if(!b.length)return'nf'; for(const ev of ['mousedown','mouseup','click']) b[b.length-1].dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'clicked';})()`);
log('11) 保存点击:', sv);
await wait(9000);
const msg = await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal')].filter(e=>e.offsetParent!==null)
.map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,160)).filter(Boolean);
return JSON.stringify({url:location.href, msgs:ms.slice(0,4)});})()`);
log('12) 结果:', SAN(String(msg)).slice(0, 400));
await c.shot('/tmp/tk_pilot.jpeg').catch(() => {});
process.exit(0);