// ==UserScript== // @name dlsite购物车增强 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 为 DLsite 购物车添加评分、销量、发售日、标签等信息 // @author 0moi // @match https://www.dlsite.com/maniax/cart // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant GM_xmlhttpRequest // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; const ajaxApi = 'https://www.dlsite.com/maniax/product/info/ajax?cdn_cache_min=1&product_id='; const jsonApi = 'https://www.dlsite.com/maniax/api/=/product.json?workno='; const workMap = new Map(); // id → DOM const tagMap = new Map(); // id → [{ old, new }] const formatter = new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "long", day: "numeric", hour: "numeric", hour12: false }); async function main() { importShoelace(); collectCartWorks(); await loadJsonTagData(); const ajaxJson = await loadAjaxData(); injectInfo(ajaxJson); } /* ----------------------- Step 1. 解析购物车作品 -------------------------*/ function collectCartWorks() { const works = document.querySelectorAll('#cart_wrapper > ul > li'); works.forEach(work => { const id = work.getAttribute('data-workno'); if (id) workMap.set(id, work); }); } /* ----------------------- Step 2. 加载 JSON 标签数据 -------------------------*/ async function loadJsonTagData() { const tasks = [...workMap.keys()].map(id => fetchJsonData(id)); await Promise.all(tasks); } function fetchJsonData(id) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: jsonApi + id, responseType: 'json', onload: res => { const data = res.response?.[0]; if (!data) return resolve(); const original = data.genres || []; const replaced = data.genres_replaced || []; // 通过 genre id 匹配,避免顺序问题 const tags = original.map(orig => { const rep = replaced.find(x => x.id === orig.id); return { old: orig.name, new: rep?.name ?? orig.name }; }); tagMap.set(id, tags); resolve(tags); }, onerror: err => reject(err) }); }); } /* ----------------------- Step 3. 加载 AJAX 评分+销量数据 -------------------------*/ function loadAjaxData() { const idStr = [...workMap.keys()].join(','); const url = ajaxApi + idStr; return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url, responseType: 'json', onload: res => resolve(res.response || {}), onerror: err => reject(err) }); }); } /* ----------------------- Step 4. 注入到 DOM -------------------------*/ function injectInfo(json) { workMap.forEach((dom, id) => { const info = json[id]; if (!info) return; const content = dom.querySelector('.work_content'); if (!content) return; const frag = document.createDocumentFragment(); /* ---- 发售日 与 销量/评分 ---- */ const registDiv = createInfoBlock(info); frag.appendChild(registDiv); /* ---- 标签 ---- */ const tags = tagMap.get(id); if (tags?.length) { const tagDiv = document.createElement('dd'); tags.forEach(tag => { tagDiv.appendChild(buildTag(tag.old, tag.new)); }); frag.appendChild(tagDiv); } content.appendChild(frag); }); } function createInfoBlock(info) { const dl_count = info.dl_count ?? 0; const avg = info.rate_average_2dp; const rate_count = info.rate_count; const date = new Date(info.regist_date); const dateStr = formatter.format(date).replace(":", " 时"); const days = Math.floor((Date.now() - date.getTime()) / 86400000); const dd = document.createElement('dd'); const rateHtml = rate_count ? `  评分: ${avg} (${rate_count}) ` : ''; dd.innerHTML = `
发售日:${dateStr}  发售于 ${days} 天前
销量:${dl_count} ${rateHtml}
`; return dd; } /* ----------------------- 生成 Shoelace Tag + Tooltip -------------------------*/ function buildTag(oldName, newName) { const tooltip = document.createElement('sl-tooltip'); tooltip.setAttribute('content', oldName); const tag = document.createElement('sl-tag'); tag.textContent = newName; tag.setAttribute('size', 'small'); tag.setAttribute('pill', ''); tag.setAttribute('variant', oldName === newName ? 'primary' : 'warning'); tooltip.appendChild(tag); return tooltip; } /* ----------------------- Shoelace 注入(带重复检测) -------------------------*/ function importShoelace() { if (document.querySelector('link[href*="shoelace"]')) return; const head = document.head; const css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/themes/light.css'; const script = document.createElement('script'); script.type = 'module'; script.src = 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/shoelace-autoloader.js'; head.appendChild(css); head.appendChild(script); } /* ----------------------- 启动 -------------------------*/ main().catch(console.error); })();