// ==UserScript== // @name WB 助手 // @namespace https://www.wildberries.ru/ // @version 1.2.8 // @description 监控 Wildberries 搜索请求(u-search/exactmatch/ru/common/v18/search),提取产品数据,支持导出JSON和Excel,支持复制JSON // @author juju486 // @match https://www.wildberries.ru/* // @match https://seller.wildberries.ru/* // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @run-at document-start // ==/UserScript== (function () { 'use strict'; const TARGET_URL_PATTERN = 'u-search/exactmatch/ru/common/v18/search'; const V9_URL_PATTERN = 'ru/common/v9/search'; const CARD_URL_PATTERN = 'info/ru/card.json'; const NMS_DETAIL_URL_PATTERN = 'competitor-comparison/nms/detail'; const STORAGE_KEY = 'wb_search_monitor_products'; let latestNmsData = null; let productList = []; let v18HasData = false; function loadStoredData() { try { const stored = GM_getValue(STORAGE_KEY, '[]'); productList = JSON.parse(stored); } catch (e) { productList = []; } } function saveData() { try { GM_setValue(STORAGE_KEY, JSON.stringify(productList)); } catch (e) { } } function fetchProductImage(product) { if (product._img) return; try { const card = document.querySelector('.product-card[data-nm-id="' + product.id + '"] img.j-thumbnail'); if (card && card.src) { product._img = card.src; } } catch (e) { } } function refreshAllImages() { for (let i = 0; i < productList.length; i++) { fetchProductImage(productList[i]); } } function addProducts(newProducts) { if (!newProducts || !Array.isArray(newProducts) || newProducts.length === 0) { return; } let addedCount = 0; const existingIds = new Set(productList.map(function (p) { return p.id; })); for (let i = 0; i < newProducts.length; i++) { const product = newProducts[i]; if (!existingIds.has(product.id)) { product._img = null; fetchProductImage(product); productList.push(product); existingIds.add(product.id); addedCount++; } } if (addedCount > 0) { saveData(); updatePanelContent(); updateBadge(); } } function formatTimestamp() { const now = new Date(); const pad = function (n) { return String(n).padStart(2, '0'); }; return pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds()) + '.' + String(now.getMilliseconds()).padStart(3, '0'); } function tryParseJSON(str) { try { return JSON.parse(str); } catch (e) { return null; } } function toChinaTime(dateStr) { if (!dateStr) return ''; var d; if (/^\d+$/.test(dateStr)) { d = new Date(dateStr.length > 10 ? Number(dateStr) : Number(dateStr) * 1000); } else { d = new Date(dateStr); } if (isNaN(d.getTime())) return dateStr; return d.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }); } function showProductDates(resp) { var createDate = resp.create_date || resp.createDate || ''; var updateDate = resp.update_date || resp.updateDate || ''; if (!createDate && !updateDate) return; var chinaCreate = toChinaTime(createDate); var chinaUpdate = toChinaTime(updateDate); var id = 'wb-card-dates'; var existing = document.getElementById(id); if (existing) { existing.innerHTML = '\n \u521B\u5EFA\u65F6\u95F4: ' + escapeHtml(chinaCreate) + '\n \u66F4\u65B0\u65F6\u95F4: ' + escapeHtml(chinaUpdate) + '\n '; return; } var titleEl = document.querySelector('h2[class*="productTitle"]'); if (!titleEl) return; var div = document.createElement('div'); div.id = id; div.innerHTML = '\n \u521B\u5EFA\u65F6\u95F4: ' + escapeHtml(chinaCreate) + '\n \u66F4\u65B0\u65F6\u95F4: ' + escapeHtml(chinaUpdate) + '\n '; titleEl.parentNode.insertBefore(div, titleEl.nextSibling); } function handleNmsDetailResponse(responseText) { console.log('[WB助手] handleNmsDetailResponse 被调用, responseText 长度:', responseText ? responseText.length : 0); const resp = tryParseJSON(responseText); console.log('[WB助手] JSON 解析结果:', resp ? '成功' : '失败'); if (!resp || !resp.data) { console.log('[WB助手] resp.data 不存在, resp keys:', resp ? Object.keys(resp) : 'null'); return; } latestNmsData = resp.data; console.log('[WB助手] 提取到 data, 类型:', typeof latestNmsData); injectNmsCopyButton(); showToast('NMS Detail 数据已捕获'); } function injectNmsCopyButton() { if (document.getElementById('wb-nms-copy-btn')) return; var titleEl = document.querySelector('div[class*="Cards-comparison-chart__title"]'); if (!titleEl) { setTimeout(injectNmsCopyButton, 500); return; } var btn = document.createElement('button'); btn.id = 'wb-nms-copy-btn'; btn.textContent = '\u590D\u5236 NMS'; btn.addEventListener('click', function () { if (!latestNmsData) { showToast('暂无 NMS 数据'); return; } var jsonStr = JSON.stringify(latestNmsData, null, 2); if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(jsonStr).then(function () { showToast('NMS Detail data 已复制'); }).catch(function () { fallbackCopyNms(jsonStr, 'NMS Detail data 已复制'); }); } else { fallbackCopyNms(jsonStr, 'NMS Detail data 已复制'); } }); titleEl.parentNode.insertBefore(btn, titleEl.nextSibling); } function fallbackCopyNms(text, successMsg) { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.left = '-9999px'; textarea.style.top = '-9999px'; document.body.appendChild(textarea); textarea.focus(); textarea.select(); try { document.execCommand('copy'); showToast('\u2705 ' + (successMsg || '\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F')); } catch (e) { showToast('\u274C \u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u590D\u5236'); } document.body.removeChild(textarea); } // ==================== XHR Hook ==================== function hookXHR() { const origOpen = XMLHttpRequest.prototype.open; const origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url, async, user, password) { this._wb_method = method; this._wb_url = (typeof url === 'string') ? url : url.toString(); this._wb_async = async !== false; return origOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function (body) { const xhr = this; if (xhr._wb_url && xhr._wb_url.indexOf(TARGET_URL_PATTERN) !== -1) { xhr.addEventListener('load', function () { const resp = tryParseJSON(xhr.responseText); if (resp && resp.products && Array.isArray(resp.products)) { v18HasData = true; addProducts(resp.products); } }); } if (xhr._wb_url && xhr._wb_url.indexOf(V9_URL_PATTERN) !== -1) { xhr.addEventListener('load', function () { if (v18HasData) return; const resp = tryParseJSON(xhr.responseText); if (resp && resp.products && Array.isArray(resp.products)) { addProducts(resp.products); } }); } if (xhr._wb_url && xhr._wb_url.indexOf(CARD_URL_PATTERN) !== -1) { xhr.addEventListener('load', function () { const resp = tryParseJSON(xhr.responseText); if (resp) { showProductDates(resp); } }); } if (xhr._wb_url && xhr._wb_url.indexOf(NMS_DETAIL_URL_PATTERN) !== -1) { console.log('[WB助手] XHR 匹配到 NMS Detail:', xhr._wb_url); xhr.addEventListener('load', function () { console.log('[WB助手] NMS Detail 响应已接收'); handleNmsDetailResponse(xhr.responseText); }); } return origSend.apply(this, arguments); }; } // ==================== Fetch Hook ==================== function hookFetch() { const origFetch = unsafeWindow.fetch; unsafeWindow.fetch = function (input, init) { const url = (typeof input === 'string') ? input : (input.url || input.toString()); if (url.indexOf(TARGET_URL_PATTERN) !== -1) { return origFetch.apply(this, arguments).then(function (response) { const cloned = response.clone(); cloned.text().then(function (text) { const resp = tryParseJSON(text); if (resp && resp.products && Array.isArray(resp.products)) { v18HasData = true; addProducts(resp.products); } }).catch(function () { }); return response; }); } if (url.indexOf(V9_URL_PATTERN) !== -1) { return origFetch.apply(this, arguments).then(function (response) { if (v18HasData) return response; const cloned = response.clone(); cloned.text().then(function (text) { const resp = tryParseJSON(text); if (resp && resp.products && Array.isArray(resp.products)) { addProducts(resp.products); } }).catch(function () { }); return response; }); } if (url.indexOf(CARD_URL_PATTERN) !== -1) { return origFetch.apply(this, arguments).then(function (response) { const cloned = response.clone(); cloned.text().then(function (text) { const resp = tryParseJSON(text); if (resp) { showProductDates(resp); } }).catch(function () { }); return response; }); } if (url.indexOf(NMS_DETAIL_URL_PATTERN) !== -1) { console.log('[WB助手] Fetch 匹配到 NMS Detail:', url); return origFetch.apply(this, arguments).then(function (response) { console.log('[WB助手] NMS Detail Fetch 响应已接收'); const cloned = response.clone(); cloned.text().then(function (text) { handleNmsDetailResponse(text); }).catch(function (e) { console.error('[WB助手] NMS Detail 解析失败:', e); }); return response; }); } return origFetch.apply(this, arguments); }; } // ==================== UI ==================== const PANEL_STYLES = '\n#wb-monitor-btn {\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 999999;\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: #fff;\n border: none;\n cursor: grab;\n box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);\n font-size: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.2s, box-shadow 0.2s;\n user-select: none;\n}\n#wb-monitor-btn:hover {\n transform: scale(1.1);\n box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);\n}\n#wb-monitor-btn .badge {\n position: absolute;\n top: -4px;\n right: -4px;\n background: #ef4444;\n color: #fff;\n font-size: 11px;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 4px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n}\n#wb-monitor-panel {\n position: fixed;\n bottom: 80px;\n right: 20px;\n z-index: 999998;\n width: 620px;\n max-height: 550px;\n background: #fff;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.15);\n display: none;\n flex-direction: column;\n overflow: hidden;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\n}\n#wb-monitor-panel.show {\n display: flex;\n}\n#wb-monitor-panel .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: #fff;\n font-size: 14px;\n font-weight: 600;\n flex-shrink: 0;\n}\n#wb-monitor-panel .panel-header .title {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n#wb-monitor-panel .panel-header .count {\n background: rgba(255,255,255,0.2);\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 12px;\n}\n#wb-monitor-panel .panel-header .btn-close {\n background: none;\n border: none;\n color: #fff;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n line-height: 1;\n opacity: 0.8;\n}\n#wb-monitor-panel .panel-header .btn-close:hover {\n opacity: 1;\n}\n#wb-monitor-panel .panel-toolbar {\n display: flex;\n gap: 6px;\n padding: 8px 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e9ecef;\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n#wb-monitor-panel .panel-toolbar button {\n padding: 5px 12px;\n border: 1px solid #dee2e6;\n border-radius: 6px;\n background: #fff;\n cursor: pointer;\n font-size: 12px;\n color: #495057;\n transition: all 0.15s;\n white-space: nowrap;\n}\n#wb-monitor-panel .panel-toolbar button:hover {\n background: #e9ecef;\n border-color: #adb5bd;\n}\n#wb-monitor-panel .panel-toolbar button.btn-export-json {\n background: #0d6efd;\n color: #fff;\n border-color: #0d6efd;\n}\n#wb-monitor-panel .panel-toolbar button.btn-export-json:hover {\n background: #0b5ed7;\n}\n#wb-monitor-panel .panel-toolbar button.btn-export-excel {\n background: #198754;\n color: #fff;\n border-color: #198754;\n}\n#wb-monitor-panel .panel-toolbar button.btn-export-excel:hover {\n background: #157347;\n}\n#wb-monitor-panel .panel-toolbar button.btn-copy {\n background: #6f42c1;\n color: #fff;\n border-color: #6f42c1;\n}\n#wb-monitor-panel .panel-toolbar button.btn-copy:hover {\n background: #5e3aaa;\n}\n#wb-monitor-panel .panel-toolbar button.btn-clear {\n background: #dc3545;\n color: #fff;\n border-color: #dc3545;\n}\n#wb-monitor-panel .panel-toolbar button.btn-clear:hover {\n background: #bb2d3b;\n}\n#wb-monitor-panel .panel-list {\n flex: 1;\n overflow-y: auto;\n padding: 8px 16px;\n max-height: 390px;\n}\n#wb-monitor-panel .panel-list::-webkit-scrollbar {\n width: 4px;\n}\n#wb-monitor-panel .panel-list::-webkit-scrollbar-thumb {\n background: #ced4da;\n border-radius: 2px;\n}\n#wb-monitor-panel .panel-list .empty {\n text-align: center;\n color: #adb5bd;\n padding: 40px 0;\n font-size: 13px;\n}\n#wb-monitor-panel .panel-list .product-item {\n border: 1px solid #e9ecef;\n border-radius: 8px;\n margin-bottom: 6px;\n overflow: hidden;\n font-size: 12px;\n}\n#wb-monitor-panel .panel-list .product-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 10px;\n background: #f8f9fa;\n cursor: pointer;\n user-select: none;\n gap: 8px;\n}\n#wb-monitor-panel .panel-list .product-header:hover {\n background: #e9ecef;\n}\n#wb-monitor-panel .panel-list .product-header .product-info {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n#wb-monitor-panel .panel-list .product-header .product-thumb {\n width: 36px;\n height: 36px;\n border-radius: 4px;\n object-fit: cover;\n flex-shrink: 0;\n background: #e9ecef;\n}\n#wb-monitor-panel .panel-list .product-header .product-id {\n color: #6c757d;\n font-size: 11px;\n white-space: nowrap;\n}\n#wb-monitor-panel .panel-list .product-header .product-name {\n color: #212529;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n#wb-monitor-panel .panel-list .product-header .product-price {\n color: #198754;\n font-weight: 600;\n white-space: nowrap;\n}\n#wb-monitor-panel .panel-list .product-header .product-arrow {\n transition: transform 0.2s;\n color: #adb5bd;\n flex-shrink: 0;\n}\n#wb-monitor-panel .panel-list .product-header .product-arrow.open {\n transform: rotate(90deg);\n}\n#wb-monitor-panel .panel-list .product-body {\n display: none;\n padding: 8px 10px;\n background: #fff;\n border-top: 1px solid #e9ecef;\n max-height: 200px;\n overflow: auto;\n}\n#wb-monitor-panel .panel-list .product-body.open {\n display: block;\n}\n#wb-monitor-panel .panel-list .product-body pre {\n margin: 0;\n font-size: 11px;\n white-space: pre-wrap;\n word-break: break-all;\n color: #212529;\n font-family: "SF Mono", "Monaco", "Menlo", "Consolas", monospace;\n}\n#wb-monitor-toast {\n position: fixed;\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 9999999;\n background: #333;\n color: #fff;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n opacity: 0;\n transition: opacity 0.3s;\n pointer-events: none;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\n}\n#wb-monitor-toast.show {\n opacity: 1;\n}\n#wb-card-dates {\n display: flex;\n gap: 16px;\n padding: 6px 0 4px;\n font-size: 13px;\n color: #6c757d;\n}\n#wb-card-dates span {\n white-space: nowrap;\n}\n'; function injectStyles() { GM_addStyle(PANEL_STYLES); GM_addStyle('#wb-nms-copy-btn {' + 'display: inline-block;' + 'padding: 6px 14px;' + 'background: #e83e8c;' + 'color: #fff;' + 'border: none;' + 'border-radius: 6px;' + 'cursor: pointer;' + 'font-size: 13px;' + 'margin-left: 12px;' + 'vertical-align: middle;' + '}' + '#wb-nms-copy-btn:hover {' + 'background: #d63384;' + '}'); } function getProductPrice(product) { if (product.sizes && product.sizes.length > 0 && product.sizes[0].price) { const price = product.sizes[0].price; return (price.product || price.basic || 0) / 100; } return 0; } function formatPrice(product) { const price = getProductPrice(product); return Math.floor(price * 0.98) + ' ₽'; } function createUI() { const btn = document.createElement('button'); btn.id = 'wb-monitor-btn'; btn.title = 'WB Search Monitor'; btn.innerHTML = '\u{1F4E1}0'; const panel = document.createElement('div'); panel.id = 'wb-monitor-panel'; panel.innerHTML = `\n
' + escapeHtml(JSON.stringify(product, null, 2)) + '\n