Greasy Fork is available in English.
为 DLsite 购物车添加评分、销量、发售日、标签等信息
当前为
// ==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
// ==/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
? `<span> 评分:</span>
<span class="rate">${avg}</span>
<span>(${rate_count})</span>
<sl-rating readonly value="${avg}"></sl-rating>`
: '';
dd.innerHTML = `
<div class="registDate">
<span>发售日:</span><span>${dateStr}</span><span> 发售于 ${days} 天前</span>
</div>
<div class="countData" style="display:flex;align-items:center;">
<span>销量:</span><span>${dl_count}</span>
${rateHtml}
</div>
`;
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/[email protected]/cdn/themes/light.css';
const script = document.createElement('script');
script.type = 'module';
script.src = 'https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/shoelace-autoloader.js';
head.appendChild(css);
head.appendChild(script);
}
/* -----------------------
启动
-------------------------*/
main().catch(console.error);
})();