// ==UserScript== // @name 闲管家批量发布 // @namespace https://cxbank.icu/goofish // @version 0.5.5 // @description 与页面同款表单:自己勾选要填的字段,脚本自动识别可填/需手动的项,支持批量导入发布 // @author ZZ // @match https://www.goofish.pro/* // @run-at document-idle // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; var VER = '0.5.5'; var SCRIPT_URL = 'https://api.cxbank.icu/goofish.user.js'; var CFG = { minGap: 20000, maxGap: 60000, dailyLimit: 40 }; var TYPES = ['普通商品', '验货宝', '已验货', '严选浅库存', '严选深库存', '闲鱼特卖', '品牌捡漏', '预售商品', '跨境商品']; var S = { rows: [], idx: 0, running: false, done: 0, fail: 0, log: [], fields: {}, // key -> { el, auto, note } }; function sleep(ms) { return new Promise(function (r) { setTimeout(r, ms); }); } function $(s, r) { return (r || document).querySelector(s); } function $$(s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); } function LOG(t) { var line = '[' + new Date().toTimeString().slice(0, 8) + '] ' + t; S.log.push(line); if (S.log.length > 400) S.log.shift(); var el = document.getElementById('gf-log'); if (el) { var d = document.createElement('div'); d.textContent = line; el.appendChild(d); el.scrollTop = el.scrollHeight; } try { console.log('[闲管家]', t); } catch (e) {} } /* ElementUI/Vue 受控输入 */ function setNative(el, value) { var proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype; try { var desc = Object.getOwnPropertyDescriptor(proto, 'value'); desc.set.call(el, value); } catch (e) { el.value = value; } el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); } /* ============ 字段定位 ============ */ function findTitle() { return $('input[placeholder*="32个汉字"]') || $('.w-800 input.el-input__inner') || null; } function findPriceInputs() { return $$('input.el-input__inner').filter(function (x) { return (x.getAttribute('placeholder') || '').indexOf('0.00') >= 0; }); } function findStock() { var c = $$('.w-370 input.el-input__inner').filter(function (x) { return !x.getAttribute('placeholder') && !x.disabled; }); return c[0] || null; } function findDesc() { var t = $('textarea'); if (t) return { el: t, kind: 'textarea' }; var rich = $('.ql-editor, .w-e-text, [contenteditable="true"]'); if (rich) return { el: rich, kind: 'rich' }; return null; } function findCategory() { /* 闲管家用的是 custom-cascader,不是标准 el-cascader */ return $('input[placeholder*="关键词"]') || $('.custom-cascader input.el-input__inner') || $('.el-cascader input.el-input__inner') || null; } function findTypeRadios() { var box = document.querySelector('.produect-type-2') || document.querySelector('.el-form-item'); if (!box) return []; return Array.prototype.slice.call(box.querySelectorAll('label.el-radio')); } function currentType() { var ls = findTypeRadios(); for (var i = 0; i < ls.length; i++) { if (ls[i].className.indexOf('is-checked') >= 0) return (ls[i].textContent || '').trim(); } return ''; } /* 店铺:找「发布的闲鱼店铺」区块里的店铺卡片 */ function findShopCards() { var items = Array.prototype.slice.call(document.querySelectorAll('.el-form-item')); var box = null; for (var i = 0; i < items.length; i++) { var lab = items[i].querySelector('.el-form-item__label'); if (lab && (lab.textContent || '').indexOf('店铺') >= 0) { box = items[i]; break; } } if (!box) return []; var lis = Array.prototype.slice.call(box.querySelectorAll('ul.auth-list li, ul.custom-element li, .auth-list li')); if (!lis.length) { lis = Array.prototype.slice.call(box.querySelectorAll('.el-radio')).filter(function (el) { var t = (el.textContent || '').trim(); return t && t.length < 30 && t.indexOf('创建') < 0; }); } return lis.filter(function (el) { var c = String(el.className || ''); if (c.indexOf('sku-add') >= 0 || c.indexOf('add-btn') >= 0) return false; return !!el.querySelector('.un-selected-icon, .auth-left, .auth-right') || c.indexOf('sku') < 0; }); } function findSpecButton() { var btns = Array.prototype.slice.call(document.querySelectorAll('button, .el-button, a, span')); for (var i = 0; i < btns.length; i++) { var t = (btns[i].textContent || '').replace(/\s+/g, ''); if (/添加多规格|添加规格|多规格深库存/.test(t) && t.length < 20) return btns[i]; } return null; } function hasImageUploader() { return !!$('.el-upload__input, input[type="file"]'); } /* 识别当前页面哪些能填 */ function detect() { var f = {}; var shops = findShopCards(); f.shop = { el: shops[0] || null, auto: shops.length > 0, note: shops.length ? ('自动(' + shops.length + ' 个店铺)') : '需手动' }; var sp = findSpecButton(); f.spec = { el: sp, auto: !!sp, note: sp ? '可点开(内容需手填)' : '需手动' }; var tr = findTypeRadios(); f.productType = { el: tr.length ? tr[0] : null, auto: tr.length > 0, note: tr.length ? '自动(当前:' + currentType() + ')' : '未找到' }; var t = findTitle(); f.title = { el: t, auto: !!t, note: t ? '自动' : '未找到' }; var pr = findPriceInputs(); f.originPrice = { el: pr[0] || null, auto: !!pr[0], note: pr[0] ? '自动' : '未找到' }; f.price = { el: pr[1] || null, auto: !!pr[1], note: pr[1] ? '自动' : '未找到' }; var st = findStock(); f.stock = { el: st, auto: !!st, note: st ? '自动' : '未找到' }; var de = findDesc(); f.desc = { el: de ? de.el : null, kind: de ? de.kind : '', auto: !!de, note: de ? (de.kind === 'rich' ? '自动(富文本)' : '自动') : '需手动' }; var cat = findCategory(); f.category = { el: cat, auto: !!cat, note: cat ? '自动(逐级点选)' : '未找到' }; f.image = { el: null, auto: hasImageUploader(), note: hasImageUploader() ? '需手动选择文件' : '需手动上传' }; S.fields = f; return f; } /* ============ 填表 ============ */ async function fillAll(row, picked) { detect(); var filled = []; var manual = []; function want(k) { return !picked || picked.indexOf(k) >= 0; } if (want('shop') && row.shop) { var shops = findShopCards(); var hitS = null; for (var k = 0; k < shops.length; k++) { if ((shops[k].textContent || '').indexOf(String(row.shop)) >= 0) { hitS = shops[k]; break; } } if (hitS) { var icon = hitS.querySelector('.un-selected-icon'); try { if (icon) icon.click(); else hitS.click(); } catch (e) {} var sw = hitS.querySelector('.el-switch'); if (sw && sw.className.indexOf('is-checked') < 0) { try { sw.click(); } catch (e) {} } filled.push('店铺=' + row.shop); } else manual.push('店铺(未找到该店铺)'); } if (want('spec') && row.openSpec) { var spb = findSpecButton(); if (spb) { try { spb.click(); filled.push('已点开多规格'); } catch (e) {} } } if (want('productType') && row.productType) { var ls = findTypeRadios(); var hit = null; for (var i = 0; i < ls.length; i++) { if ((ls[i].textContent || '').trim() === String(row.productType).trim()) { hit = ls[i]; break; } } if (hit) { hit.click(); filled.push('类型=' + row.productType); } else manual.push('类型(页面无此项)'); } if (want('title') && row.title && S.fields.title.el) { setNative(S.fields.title.el, String(row.title).slice(0, 30)); filled.push('标题'); } if (want('originPrice') && row.originPrice && S.fields.originPrice.el) { setNative(S.fields.originPrice.el, String(row.originPrice)); filled.push('原价'); } if (want('price') && row.price && S.fields.price.el) { setNative(S.fields.price.el, String(row.price)); filled.push('售价'); } if (want('stock') && row.stock != null && S.fields.stock.el) { setNative(S.fields.stock.el, String(row.stock)); filled.push('库存'); } if (want('desc') && row.desc) { var d = S.fields.desc; if (d.el) { if (d.kind === 'rich') { d.el.focus(); d.el.innerHTML = '

' + String(row.desc) + '

'; d.el.dispatchEvent(new Event('input', { bubbles: true })); } else { setNative(d.el, String(row.desc)); } filled.push('描述'); } else manual.push('描述'); } if (want('category') && row.category) { var n = await pickCategory(row.category); if (n) filled.push('分类'); else manual.push('分类'); } if (want('image')) manual.push('图片'); if (filled.length) LOG('已填:' + filled.join('、')); if (manual.length) LOG('⚠ 需手动:' + manual.join('、')); return filled.length; } /* ============ 提交 ============ */ function clickSubmit() { var btns = $$('button'); var want = ['确定并新建', '确定']; for (var w = 0; w < want.length; w++) { for (var i = 0; i < btns.length; i++) { if ((btns[i].textContent || '').replace(/\s+/g, '') === want[w]) { btns[i].click(); LOG('已点「' + want[w] + '」'); return true; } } } return false; } function gotoAdd() { if (location.pathname.indexOf('/sale/product/add') >= 0) return true; location.href = 'https://www.goofish.pro/sale/product/add'; return false; } /* ============ 批量循环 ============ */ async function run(picked) { if (S.running) return; if (!S.rows.length) { LOG('先导入或多填一条数据'); return; } S.running = true; paint(); while (S.running && S.idx < S.rows.length) { if (S.done >= CFG.dailyLimit) { LOG('达到每日上限'); break; } var row = S.rows[S.idx]; LOG('—— 第 ' + (S.idx + 1) + '/' + S.rows.length + ' 条 ——'); if (!gotoAdd()) return; await sleep(2500); var n = await fillAll(row, picked); if (!n) { S.fail++; S.idx++; paint(); continue; } await sleep(1200); if (!clickSubmit()) { LOG('⚠ 没找到确定按钮'); S.fail++; S.idx++; paint(); continue; } await sleep(3000); S.done++; S.idx++; paint(); LOG('✔ 完成 ' + S.idx + '(成功 ' + S.done + '/失败 ' + S.fail + ')'); if (S.idx < S.rows.length) { var gap = CFG.minGap + Math.floor(Math.random() * (CFG.maxGap - CFG.minGap)); LOG('等待 ' + Math.round(gap / 1000) + ' 秒…'); var t0 = Date.now(); while (S.running && Date.now() - t0 < gap) await sleep(1000); } } S.running = false; paint(); LOG('结束:成功 ' + S.done + ',失败 ' + S.fail); } /* ============ 数据集 ============ */ function parseRows(text) { text = (text || '').trim(); if (!text) return []; if (text[0] === '[' || text[0] === '{') { try { var j = JSON.parse(text); if (!Array.isArray(j)) j = [j]; return j.map(function (r) { return { title: r.title || r['标题'] || '', price: r.price || r['售价'] || '', originPrice: r.originPrice || r['原价'] || '', stock: r.stock != null ? r.stock : (r['库存'] != null ? r['库存'] : 10), desc: r.desc || r['描述'] || '', }; }); } catch (e) { LOG('JSON 解析失败'); return []; } } var out = []; text.split(/\r?\n/).forEach(function (line, i) { if (!line.trim()) return; var c = line.split(/\t|,/).map(function (x) { return x.trim(); }); if (i === 0 && /标题|title/i.test(c[0])) return; out.push({ title: c[0] || '', price: c[1] || '', originPrice: c[2] || '', stock: c[3] || 10, desc: c[4] || '' }); }); return out; } function cmpVer(a, b) { var x = String(a).split('.').map(Number), y = String(b).split('.').map(Number); for (var i = 0; i < Math.max(x.length, y.length); i++) { var p2 = x[i] || 0, q2 = y[i] || 0; if (p2 !== q2) return p2 - q2; } return 0; } async function checkUpdate() { try { var r = await fetch(SCRIPT_URL + '?t=' + Date.now(), { cache: 'no-store' }); var t = await r.text(); var m = t.match(/@version\s+([\d.]+)/); var latest = m ? m[1] : ''; if (!latest) { LOG('检查更新失败'); return; } if (cmpVer(latest, VER) <= 0) { LOG('已是最新版本 ' + VER); return; } LOG('发现新版本 ' + latest + '(当前 ' + VER + ')'); try { await navigator.clipboard.writeText(SCRIPT_URL); LOG('地址已复制,粘贴到浏览器打开更新'); } catch (e) { LOG('请手动打开 ' + SCRIPT_URL); } } catch (e) { LOG('检查更新失败:' + e.message); } } /* 商品分类:按 一级/二级/三级 逐级点击 el-cascader 节点 */ async function pickCategory(pathStr) { var parts = String(pathStr).split(/[/>、,]/).map(function (x) { return x.trim(); }).filter(Boolean); if (!parts.length) return 0; var inp = findCategory(); if (!inp) return 0; inp.click(); inp.focus(); await sleep(1200); // 加长首次展开等待 var hit = 0; for (var i = 0; i < parts.length; i++) { var want = parts[i].replace(/\s+/g, ''); var found = null; /* 每级最多重试 3 次,等待逐步加长 */ for (var attempt = 0; attempt < 3 && !found; attempt++) { await sleep(attempt === 0 ? 800 : 1200 * attempt); var nodes = Array.prototype.slice.call(document.querySelectorAll('.el-cascader-node, .el-cascader-menu__item')); for (var j = 0; j < nodes.length; j++) { var lb = nodes[j].querySelector('.el-cascader-node__label') || nodes[j]; if ((lb.textContent || '').replace(/\s+/g, '') === want) { found = nodes[j]; break; } } if (!found && attempt === 1) { /* 兜底:在搜索框里输入关键词,取第一个候选 */ try { inp.focus(); setNative(inp, want); await sleep(1200); var cas = Array.prototype.slice.call(document.querySelectorAll('.el-cascader-node')); if (cas.length) { found = cas[0]; LOG('分类用搜索兜底命中:' + want); } } catch (e) {} } } if (!found) { LOG('⚠ 分类未找到:' + parts[i] + '(已重试 3 次)'); break; } found.click(); hit++; await sleep(1000); // 每级点击后多等 } /* 校验:输入框应出现完整路径 */ await sleep(600); var shown = (inp.value || '').replace(/\s+/g, ''); var okPath = hit === parts.length && (!shown || shown.indexOf(parts[parts.length - 1]) >= 0); if (okPath) LOG('分类已选:' + parts.join(' / ')); else LOG('分类只选到第 ' + hit + ' 级(当前显示:' + (shown || '空') + ')'); return okPath ? hit : 0; } /* 抓取分类树:一层层展开,把节点文字抓下来存 localStorage */ async function grabCategoryTree(maxLevel) { var inp = findCategory(); if (!inp) { LOG('没找到分类框'); return; } inp.click(); inp.focus(); await sleep(1200); var tree = []; var lv1 = Array.prototype.slice.call(document.querySelectorAll('.el-cascader-node')) .map(function (n) { var lb = n.querySelector('.el-cascader-node__label') || n; return (lb.textContent || '').replace(/\s+/g, ''); }).filter(Boolean); LOG('一级分类 ' + lv1.length + ' 个:' + lv1.slice(0, 12).join('、')); tree.push(lv1); try { localStorage.setItem('gf_cat_l1', JSON.stringify(lv1)); } catch (e) {} try { await navigator.clipboard.writeText(JSON.stringify(lv1)); LOG('一级分类已复制到剪贴板(可粘贴到面板分类框里选)'); } catch (e) {} } /* ============ 面板 ============ */ var FIELDS_UI = [ { k: 'productType', label: '类型', type: 'select', options: TYPES }, { k: 'shop', label: '店铺', type: 'text', ph: '填店铺名,如:轩的杂货铺' }, { k: 'spec', label: '规格', type: 'check', ph: '点开多规格面板' }, { k: 'title', label: '标题', ph: '最多 30 字' }, { k: 'originPrice', label: '原价', ph: '选填' }, { k: 'price', label: '售价', ph: '必填' }, { k: 'stock', label: '库存', ph: '默认 10' }, { k: 'desc', label: '描述', ph: '商品描述', multi: true }, { k: 'category', label: '分类', ph: '一级/二级/三级,如:手机/手机配件' }, { k: 'image', label: '图片', ph: '需手动上传', manual: true }, ]; function paint() { var s = document.getElementById('gf-st'); if (s) s.textContent = (S.running ? '运行中 ' : '空闲 ') + '· ' + S.idx + '/' + S.rows.length + ' · 成功 ' + S.done + ' · 失败 ' + S.fail; } function fieldRow(it) { var chk = ''; var inp; if (it.type === 'check') { inp = ''; } else if (it.type === 'select') { inp = ''; } else if (it.multi) { inp = ''; } else { inp = ''; } var badge = '—'; return '
' + '' + '
' + inp + '
' + badge + '
'; } function mount() { if (document.getElementById('gf-host')) return; var host = document.createElement('div'); host.id = 'gf-host'; host.style.cssText = 'position:fixed;right:14px;top:80px;z-index:2147483000;width:360px;' + 'background:#181b21;color:#e8e8ea;border:1px solid #2a2e37;border-radius:14px;' + 'font:12px/1.6 -apple-system,"Segoe UI",sans-serif;box-shadow:0 10px 30px rgba(0,0,0,.45);max-height:86vh;overflow:auto'; host.innerHTML = '
' + '' + '闲管家批量发布 v' + VER + '—
' + '
空闲 · 0/0
' + '
' + FIELDS_UI.map(fieldRow).join('') + '
' + '
' + '' + '' + '' + '
' + '
' + '
' + '
' + '' + '' + '' + '' + '' + '
' + '
'; document.body.appendChild(host); /* 折叠 */ var collapsed = false; document.getElementById('gf-min').onclick = function () { collapsed = !collapsed; Array.prototype.slice.call(host.children).forEach(function (c, i) { if (i > 1) c.style.display = collapsed ? 'none' : ''; }); this.textContent = collapsed ? '+' : '—'; }; function pickedList() { return $$('input[data-k]', host).filter(function (x) { return x.checked; }).map(function (x) { return x.dataset.k; }); } function values() { var o = {}; $$('[data-v]', host).forEach(function (x) { o[x.dataset.v] = (x.getAttribute('data-check') === '1') ? x.checked : x.value; }); return o; } document.getElementById('gf-detect').onclick = function () { var f = detect(); Object.keys(f).forEach(function (k) { var b = $('[data-b="' + k + '"]', host); if (!b) return; b.textContent = f[k].note; b.style.color = f[k].auto ? '#34c759' : '#ff9f0a'; var inp = $('[data-v="' + k + '"]', host); if (inp) inp.disabled = !f[k].auto; var chk = $('input[data-k="' + k + '"]', host); if (chk) chk.checked = !!f[k].auto; }); LOG('字段识别完成:' + Object.keys(f).filter(function (k) { return f[k].auto; }).join('、') + ' 可自动填'); }; document.getElementById('gf-read').onclick = function () { detect(); var sel = $('[data-v="productType"]', host); if (sel) sel.value = currentType() || ''; var map = { title: 'title', originPrice: 'originPrice', price: 'price', stock: 'stock' }; Object.keys(map).forEach(function (k) { var el = S.fields[k] && S.fields[k].el; if (el) { var i2 = $('[data-v="' + k + '"]', host); if (i2) i2.value = el.value || ''; } }); var d = S.fields.desc && S.fields.desc.el; if (d) { var t2 = $('[data-v="desc"]', host); if (t2) t2.value = d.innerText || d.value || ''; } LOG('已读取页面当前值'); }; document.getElementById('gf-fill').onclick = async function () { var v = values(); var p = pickedList(); await fillAll(v, p); }; document.getElementById('gf-load').onclick = function () { S.rows = parseRows(document.getElementById('gf-in').value); S.idx = 0; S.done = 0; S.fail = 0; LOG('已导入 ' + S.rows.length + ' 条'); paint(); }; document.getElementById('gf-run').onclick = function () { var v = values(); var p = pickedList(); if (!S.rows.length && v.title) S.rows = [v]; run(p); }; document.getElementById('gf-stop').onclick = function () { S.running = false; LOG('已请求停止'); }; var gbtn = document.getElementById('gf-grab'); if (gbtn) gbtn.onclick = function () { grabCategoryTree(1); }; var ubtn = document.getElementById('gf-upd'); if (ubtn) ubtn.onclick = function () { checkUpdate(); }; detect(); paint(); } window.addEventListener('load', function () { setTimeout(mount, 1200); }); if (document.readyState === 'complete') setTimeout(mount, 1200); })();