- // ==UserScript==
- // @name 京东商品参数对比工具
- // @namespace http://tampermonkey.net/
- // @version 2077.0.9
- // @description 该脚本可用于对比不限数量的同类型商品(如:手机、笔记本)的详细参数
- // @author Yihang Wang <wangyihanger@gmail.com>
- // @match https://item.jd.com/*
- // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
- // @grant GM_setValue
- // @grant GM_getValue
- // @license MIT
- // ==/UserScript==
- (function () {
- 'use strict';
-
- var apiServer = "https://jd-compare.authu.online";
- var version = '2077.0.9';
- var itemIDs = GM_getValue("jd-price-compare-item-ids", []);
- var relatedItemIDs = getRelatedItemIDs(document);
- var userID = getUserID();
-
- function pollUntil(conditionFn, interval = 1000, maxAttempts = 10) {
- return new Promise((resolve, reject) => {
- let attempts = 0;
- function checkCondition() {
- if (conditionFn()) {
- resolve();
- } else if (attempts < maxAttempts) {
- attempts++;
- setTimeout(checkCondition, interval);
- } else {
- reject(new Error('Polling timed out'));
- }
- }
- checkCondition();
- });
- }
-
- function getItemID(doc) {
- let a = doc.querySelector('a.follow.J-follow[data-id]');
- return a ? a.getAttribute('data-id') : "unknown";
- }
-
- function getRelatedItemIDs(doc) {
- var dataSkuValues = [getItemID(doc)];
- if (Object.keys(pageConfig.product.colorSize).length > 0) {
- pageConfig.product.colorSize.forEach(function (item) {
- dataSkuValues.push(item.skuId.toString());
- });
- }
- let itemIdSet = new Set(dataSkuValues);
- return Array.from(itemIdSet.values());
- }
-
- function getPrice(doc) {
- const itemID = getItemID(doc);
- const targetSelector = `.price.J-p-${itemID}`;
- const targetNode = doc.querySelector(targetSelector);
- return parseFloat(targetNode.innerText);
- }
-
- function getBasicInfo(doc) {
- const basicInfoElement = doc.querySelector('#detail > div.tab-con > div:nth-child(1) > div.p-parameter');
- const basicInfo = {};
-
- basicInfoElement.querySelectorAll('li').forEach(dl => {
- let text = dl.textContent.trim();
- console.log(text);
- if (text.indexOf(":") < 0) {
- console.error(`invalid basic info: ${text}, colon is not present`);
- return;
- }
- let items = text.split(":");
- if (items.length != 2) {
- console.error(`invalid basic info: ${text}, incorrect number of items`);
- return;
- }
- const key = items[0]
- const value = items[1]
- basicInfo[key] = value;
- });
-
- return basicInfo;
- }
-
- function getMainInfo(doc) {
- const mainInfoElements = doc.querySelectorAll('.Ptable-item');
- const mainInfo = {};
-
- mainInfoElements.forEach(item => {
- const key = item.querySelector('h3').textContent.trim();
- const values = {};
-
- item.querySelectorAll('dl').forEach(dl => {
- const detailKey = dl.querySelector('dt').textContent.trim();
- const hintElement = dl.querySelector('dd.Ptable-tips');
- var tableHint = "";
- if (hintElement != null) {
- const tableHint = hintElement.textContent.trim();
- }
- const detailValue = dl.querySelector('dd:not(.Ptable-tips)').textContent.trim();
- const key = detailKey;
- values[key] = detailValue;
- });
-
- mainInfo[key] = values;
- });
-
- return mainInfo;
- }
-
- function getPackageList(doc) {
- const packageListElement = doc.querySelector('.package-list p');
- return packageListElement.textContent.trim();
- }
-
- function getImageUrl(doc) {
- const imageElement = doc.querySelector("#spec-list > ul > li:nth-child(1) > img");
- // https://img13.360buyimg.com/ n5/s54x54_jfs/t1/ 216364/18/36214/152585/65ade2ffFfabd3665/a7fe2701bfcd0c0a.jpg.avif
- // https://img10.360buyimg.com/ n0/jfs/t1/ 235161/18/13361/139190/65bef13aF2b8cfc2f/d4f68e1e90bd1677.jpg.avif
- let items = imageElement.src.split("/");
- items[3] = 'n0'
- items[4] = 'jfs'
- items[5] = 't1'
- return items.join("/");
- }
-
- function parseItemWithDocument(doc) {
- let basicInfo = getBasicInfo(doc);
- let mainInfo = getMainInfo(doc);
- let packageList = getPackageList(doc);
- let imageUrl = getImageUrl(doc);
- let itemID = getItemID(doc);
- let data = {
- "商品编号": itemID,
- "基本信息": basicInfo,
- "主体信息": mainInfo,
- "包装信息": packageList,
- "价格": 'N/A',
- "图片": imageUrl,
- };
- return data;
- }
-
- function parseItemByID(itemID) {
- return new Promise(function (resolve, reject) {
- let endpoint = `https://item.jd.com/${itemID}.html`;
- fetch(endpoint)
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
- resolve(response.text());
- })
- .catch(error => {
- console.error('Error:', error);
- });
- });
- }
-
- async function parseItem(itemID) {
- if (itemID == getItemID(document)) {
- pollUntil(() => (!isNaN(getPrice(document))));
- let price = getPrice(document);
- let data = parseItemWithDocument(document);
- data["价格"] = price;
- return data;
- } else {
- let html = await parseItemByID(itemID);
- let parser = new DOMParser();
- let doc = parser.parseFromString(html, 'text/html');
- let item = parseItemWithDocument(doc);
- return item;
- }
- }
-
- async function appendList(itemID) {
- let endpoint = `${apiServer}/api/v1/item`;
- let item = await parseItem(itemID);
- let body = JSON.stringify(item);
- fetch(endpoint, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Jd-Compare-Version': version,
- 'Jd-Compare-User-Id': userID,
- },
- body: body,
- })
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
- console.log(response);
- return response.json();
- })
- .then(data => {
- let itemIdList = GM_getValue("jd-price-compare-item-ids", []);
- let itemIdSet = new Set(itemIdList)
- itemIdSet.add(itemID);
- GM_setValue("jd-price-compare-item-ids", Array.from(itemIdSet.values()));
- let statusLine = document.getElementById('jd-price-compare-status-line');
- statusLine.textContent = `已成功添加 ${itemIdSet.size} 个待对比商品`;
- })
- .catch(error => {
- console.error('Error:', error);
- });
- }
-
- function createList() {
- let itemIdList = GM_getValue("jd-price-compare-item-ids", []);
- let statusLine = document.getElementById('jd-price-compare-status-line');
- if (itemIdList.length == 0) {
- statusLine.textContent = `当前待对比商品列表为空,请先点击添加商品按钮。`;
- return
- }
- statusLine.textContent = `正在创建 ${itemIdList.length} 个商品的对比列表...`;
- let endpoint = `${apiServer}/api/v1/list`;
- let listId = fetch(endpoint, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Jd-Compare-Version': version,
- 'Jd-Compare-User-Id': userID,
- },
- body: JSON.stringify(itemIdList)
- })
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
- return response.json();
- })
- .then(data => {
- statusLine.textContent = `对比成功,正在自动打开结果页面...`;
- return compareList(data._id);
- })
- .catch(error => {
- console.error('Error:', error);
- });
- return listId;
- }
-
- function compareList(listId) {
- let statusLine = document.getElementById('jd-price-compare-status-line');
- let endpoint = `${apiServer}/api/v1/list/${listId}/compare`;
- let resultUrl = fetch(endpoint, {
- method: 'GET',
- headers: {
- 'Jd-Compare-Version': version,
- 'Jd-Compare-User-Id': userID,
- },
- })
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
- return response.text();
- })
- .then(data => {
- let link = document.createElement('a');
- let url = `${apiServer}/${data}`;
- link.href = url;
- link.textContent = `列表 ID: ${listId} `; // 修正属性名,应该是textContent
- statusLine.textContent = `对比完成,正在打开商品对比结果页面。`;
- statusLine.appendChild(document.createElement("br"));
-
- // 创建并添加<p>元素
- let p = document.createElement("p");
- p.textContent = `如未打开,也可以直接点击下方链接`;
- statusLine.appendChild(p);
- statusLine.appendChild(link);
- statusLine.appendChild(document.createElement("br"));
-
- // 创建并添加文本节点
- let textNode = document.createTextNode(`您可以继续添加其他待对比商品。`);
- statusLine.appendChild(textNode);
-
- GM_setValue("jd-price-compare-item-ids", []);
- window.open(url);
- })
- .catch(error => {
- console.error('Error:', error);
- });
- return resultUrl;
- }
-
- function addSingleButton() {
- let button = document.createElement('a');
- let statusLine = document.getElementById('jd-price-compare-status-line');
- button.href = '#';
- button.id = 'jd-price-compare-add-single-button';
- button.textContent = `添加本商品`;
- button.style.backgroundColor = '#3498db';
- button.style.color = '#ffffff';
- button.style.padding = '3px';
- button.addEventListener("click", function (event) {
- event.preventDefault();
- let itemID = getItemID(document);
- statusLine.textContent = `正在获取商品(ID:${itemID})详信息...`;
- appendList(itemID);
- updateCompareButton();
- });
- let cartButtton = document.querySelector('#preview > div.preview-info > div.left-btns.shieldShopInfo');
- cartButtton.appendChild(button);
- }
-
- function addAllButton() {
- let button = document.createElement('a');
- let statusLine = document.getElementById('jd-price-compare-status-line');
- button.href = '#';
- button.id = 'jd-price-compare-add-all-button';
- button.textContent = `添加所有 ${relatedItemIDs.length} 个型号`;
- button.style.backgroundColor = '#3498db';
- button.style.color = '#ffffff';
- button.style.padding = '3px';
- button.addEventListener("click", function (event) {
- event.preventDefault();
- relatedItemIDs.forEach(function (itemID) {
- statusLine.textContent = `正在获取商品(ID:${itemID})详信息...`;
- appendList(itemID);
- });
- });
- let cartButtton = document.querySelector('#preview > div.preview-info > div.left-btns.shieldShopInfo');
- cartButtton.appendChild(button);
- }
-
- function updateCompareButton() {
- let element = document.getElementById('jd-price-compare-start-button');
- let itemIdList = GM_getValue("jd-price-compare-item-ids", []);
- element.textContent = `开始对比 (${itemIdList.length})`;
- }
-
- function addCompareButton() {
- let button = document.createElement('a');
- button.href = '#';
- button.id = 'jd-price-compare-start-button';
- button.style.backgroundColor = '#3498db';
- button.style.color = '#ffffff';
- button.style.padding = '3px';
- button.textContent = `开始对比 (${itemIDs.length})`;
- button.addEventListener("click", function (event) {
- event.preventDefault();
- createList();
- });
- let cartButtton = document.querySelector('#preview > div.preview-info > div.left-btns.shieldShopInfo');
- cartButtton.appendChild(button);
- }
-
- function addFeedbackButton() {
- let button = document.createElement('a');
- button.href = '#';
- button.id = 'jd-price-add-feedback-button';
- button.style.backgroundColor = '#3498db';
- button.style.color = '#ffffff';
- button.style.padding = '3px';
- button.textContent = `意见反馈`;
- button.addEventListener("click", function (event) {
- event.preventDefault();
- const url = "https://gf.qytechs.cn/zh-CN/scripts/486915-%E4%BA%AC%E4%B8%9C%E5%95%86%E5%93%81%E5%8F%82%E6%95%B0%E5%AF%B9%E6%AF%94%E5%B7%A5%E5%85%B7/feedback";
- window.open(url);
- });
- let cartButtton = document.querySelector('#preview > div.preview-info > div.left-btns.shieldShopInfo');
- cartButtton.appendChild(button);
- }
-
- function getUserID() {
- let userID = GM_getValue("jd-price-compare-user-id", "");
- if (userID == "") {
- userID = Math.random().toString(36).substring(2);
- GM_setValue("jd-price-compare-user-id", userID);
- }
- return userID;
- }
-
- function addStatusLine() {
- let statusLine = document.createElement('p');
- statusLine.id = 'jd-price-compare-status-line';
- statusLine.style.color = '#000000';
- statusLine.style.padding = '3px';
- statusLine.textContent = `京东商品参数对比工具 v${version}`;
-
- let statusLineDiv = document.createElement('div');
- statusLineDiv.id = 'jd-price-compare-status-line-div';
- statusLineDiv.style.textAlign = 'center';
- statusLineDiv.appendChild(statusLine);
-
- let targetElement = document.querySelector('#preview > div.preview-info');
- targetElement.parentNode.insertBefore(statusLineDiv, targetElement.nextSibling);
- }
-
- function main() {
- addStatusLine();
- addSingleButton();
- if (relatedItemIDs.length > 1) {
- addAllButton();
- }
- addCompareButton();
- addFeedbackButton();
- setInterval(updateCompareButton, 512);
- }
-
- window.addEventListener('load', main);
- })();