每日自动缓存商户数据,并在用户卡片弹出时,为富可敌国显示其评分信息,并适配网站暗色模式。
当前为
// ==UserScript==
// @name Linux.do 富可敌国评分展示
// @namespace http://tampermonkey.net/
// @version 1.0.2
// @license GNU GPLv3
// @description 每日自动缓存商户数据,并在用户卡片弹出时,为富可敌国显示其评分信息,并适配网站暗色模式。
// @author haorwen
// @match *://linux.do/*
// @connect rate.linux.do
// @grant GM_xmlhttpRequest
// @grant GM_log
// ==/UserScript==
(function() {
'use strict';
// --- 全局变量与工具函数 ---
const LAST_FETCH_DATE_KEY = 'ld_merchant_last_fetch_date';
const MERCHANT_DATA_KEY = 'ld_merchant_ratings_data';
let premiumTopicAuthor = null;
/**
* 获取指定名称的cookie值
* @param {string} name cookie名称
* @returns {string|null} cookie值或null
*/
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
}
/**
* 检测当前是否为暗色模式
* @returns {boolean}
*/
function isDarkModeDetected() {
// 条件1: 系统偏好设置
const systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
// 条件2: 网站强制设置的cookie
const cookieForceDark = getCookie('forced_color_mode') === 'dark';
return systemPrefersDark || cookieForceDark;
}
// --- Part 1: 数据获取与缓存 ---
// ... (这部分代码没有变化)
function getTodayDateString() {
return new Date().toISOString().split('T')[0];
}
function extractUsernameFromAvatar(avatarUrl) {
if (!avatarUrl || typeof avatarUrl !== 'string') return null;
try {
const parts = avatarUrl.split('/');
return parts.length > 3 ? parts[parts.length - 3] : null;
} catch (error) { console.error('从头像URL提取用户名失败:', avatarUrl, error); return null; }
}
function fetchAndStoreMerchantData() {
GM_log('开始获取商户评价数据...');
const apiUrl = 'https://rate.linux.do/api/merchant?page=1&size=100&order_by=average_rating&order_direction=desc';
GM_xmlhttpRequest({
method: 'GET', url: apiUrl, headers: { "Accept": "application/json, text/plain, */*" },
onload: function(response) {
if (response.status === 200) {
try {
const result = JSON.parse(response.responseText);
if (result.success && result.data && Array.isArray(result.data.data)) {
const merchants = result.data.data;
const merchantDataToStore = {};
merchants.forEach(merchant => {
const username = extractUsernameFromAvatar(merchant.avatar_url);
if (username) {
merchantDataToStore[username.toLowerCase()] = {
id: merchant.id, name: merchant.name, like_count: merchant.like_count,
dislike_count: merchant.dislike_count, average_rating: merchant.average_rating, rating_count: merchant.rating_count,
};
}
});
localStorage.setItem(MERCHANT_DATA_KEY, JSON.stringify(merchantDataToStore));
localStorage.setItem(LAST_FETCH_DATE_KEY, getTodayDateString());
GM_log(`成功缓存了 ${Object.keys(merchantDataToStore).length} 条商户数据 (键已小写化)。`);
} else { GM_log('API响应格式不正确:', result.message); }
} catch (error) { GM_log('解析API响应时出错:', error); }
} else { GM_log(`获取数据失败,HTTP状态码: ${response.status}`); }
},
onerror: function(error) { GM_log('网络请求失败:', error); }
});
}
function dailyCheckAndFetch() {
if (localStorage.getItem(LAST_FETCH_DATE_KEY) !== getTodayDateString()) {
GM_log(`首次加载,准备更新商户数据。`);
fetchAndStoreMerchantData();
} else { GM_log(`今日已缓存商户数据。`); }
}
// --- Part 2: 页面监控与信息注入 ---
function checkForPremiumTag() {
const premiumTag = document.querySelector('a[data-tag-name="高级推广"]');
if (premiumTag) {
const authorElement = document.querySelector('.topic-post.regular:first-of-type a[data-user-card]');
if (authorElement) {
const authorUsername = authorElement.getAttribute('data-user-card');
if (authorUsername) {
const lowerCaseAuthor = authorUsername.toLowerCase();
if (premiumTopicAuthor !== lowerCaseAuthor) {
premiumTopicAuthor = lowerCaseAuthor;
GM_log(`[高级推广] 已记录作者 (小写): ${premiumTopicAuthor}`);
}
}
return;
}
}
if (premiumTopicAuthor && !premiumTag) { premiumTopicAuthor = null; }
}
function handleUserCard(cardElement) {
if (!cardElement) return;
const oldInfo = cardElement.querySelector('.merchant-rating-info');
if (oldInfo) oldInfo.remove();
const usernameElement = cardElement.querySelector('.names__secondary.username');
if (!usernameElement) return;
const username = usernameElement.textContent.trim().toLowerCase();
const isMerchant = cardElement.classList.contains('group-g-merchant');
const isRichTitle = Array.from(cardElement.querySelectorAll('.names__secondary')).some(el => el.textContent.trim() === '富可敌国');
const isPremiumAuthor = !!(username && premiumTopicAuthor && username === premiumTopicAuthor);
if (!isMerchant && !isRichTitle && !isPremiumAuthor) return;
GM_log(`检测到目标用户 [${username}] 的卡片。`);
const allMerchantData = JSON.parse(localStorage.getItem(MERCHANT_DATA_KEY) || '{}');
const merchantInfo = allMerchantData[username];
if (!merchantInfo) {
GM_log(`本地缓存中未找到 [${username}] 的评分数据。`);
return;
}
// [!!!] 核心变更:根据暗色模式设置不同的样式
const isDark = isDarkModeDetected();
GM_log(`暗色模式检测: ${isDark}`);
const bgColor = isDark ? '#3a3a3a' : '#f9f9f9';
const borderColor = isDark ? '#555555' : '#e9e9e9';
const textColor = isDark ? '#e0e0e0' : '#222';
const linkStyle = `color: ${textColor}; text-decoration: none; display:contents;`;
const ratingDiv = document.createElement('div');
ratingDiv.className = 'card-row merchant-rating-info';
ratingDiv.style.cssText = `
padding: 8px 12px; margin: 10px 18px 0; border: 1px solid ${borderColor}; border-radius: 5px;
background-color: ${bgColor}; font-size: 0.9em; display: flex; justify-content: space-around;
flex-wrap: wrap; gap: 10px; text-align: center;
`;
ratingDiv.innerHTML = `
<a href="https://rate.linux.do/merchant/${merchantInfo.id}" target="_blank" title="点击查看详情" style="${linkStyle}">
<span>⭐ <strong>${merchantInfo.average_rating.toFixed(1)}</strong> (${merchantInfo.rating_count}人)</span>
<span>👍 <strong>${merchantInfo.like_count}</strong></span>
<span>👎 <strong>${merchantInfo.dislike_count}</strong></span>
</a>
`;
const targetRow = cardElement.querySelector('.card-row.metadata-row');
if (targetRow) {
targetRow.insertAdjacentElement('beforebegin', ratingDiv);
GM_log(`已为 [${username}] 成功注入评分信息。`);
}
}
// --- Part 3: 主逻辑与启动 ---
function main() {
dailyCheckAndFetch();
const waitForElement = (selector, callback) => {
const el = document.querySelector(selector);
if (el) { callback(el); return; }
const obs = new MutationObserver((mutations, observer) => {
const targetEl = document.querySelector(selector);
if (targetEl) { observer.disconnect(); callback(targetEl); }
});
obs.observe(document.body, { childList: true, subtree: true });
};
waitForElement('#d-menu-portals', (portalElement) => {
GM_log("商户信息增强脚本已启动 (v1.0.2 by haorwen)");
checkForPremiumTag();
const observer = new MutationObserver((mutationsList) => {
checkForPremiumTag();
for (const mutation of mutationsList) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const target = mutation.target.closest('#user-card');
if (target && target.classList.contains('show')) {
queueMicrotask(() => handleUserCard(target));
}
} else if (mutation.type === 'childList') {
for (const node of mutationsList) {
if (node.nodeType === 1 && node.querySelector) {
const card = node.querySelector('#user-card.show');
if (card) { queueMicrotask(() => handleUserCard(card)); }
}
}
}
}
});
observer.observe(portalElement, {
childList: true, subtree: true, attributes: true, attributeFilter: ['class']
});
});
}
main();
})();