Greasy Fork is available in English.
显示 linux.do 基础积分,支持手动计算差值
当前为
// ==UserScript==
// @name Linux.do Credit Display
// @namespace http://tampermonkey.net/
// @version 2.1
// @description 显示 linux.do 基础积分,支持手动计算差值
// @author You
// @match https://linux.do/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @connect credit.linux.do
// ==/UserScript==
(function() {
'use strict';
const TRANSACTIONS_API = 'https://credit.linux.do/api/v1/order/transactions';
const LEADERBOARD_URL = 'https://linux.do/leaderboard';
const STORAGE_KEY = 'linux_do_credit_cache';
// 获取今天的时间范围
function getTodayRange() {
const now = new Date();
const y = now.getFullYear();
const m = String(now.getMonth() + 1).padStart(2, '0');
const d = String(now.getDate()).padStart(2, '0');
return {
startTime: `${y}-${m}-${d}T00:00:00+08:00`,
endTime: `${y}-${m}-${d}T23:59:59+08:00`
};
}
// 从 remark 解析积分: "社区积分从 1877 更新到 1938,变化 61" -> 1938
function parseScore(remark) {
const match = remark.match(/更新到\s*(\d+)/);
return match ? parseInt(match[1], 10) : null;
}
// 获取今日基础积分
function fetchBaseScore() {
return new Promise((resolve, reject) => {
const { startTime, endTime } = getTodayRange();
GM_xmlhttpRequest({
method: 'POST',
url: TRANSACTIONS_API,
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ page: 1, page_size: 20, startTime, endTime }),
onload: (res) => {
try {
const data = JSON.parse(res.responseText);
const orders = data.data?.orders || [];
for (const item of orders) {
if (item.remark?.includes('社区积分')) {
const score = parseScore(item.remark);
if (score !== null) {
resolve(score);
return;
}
}
}
reject(new Error('未找到积分记录'));
} catch (e) {
reject(e);
}
},
onerror: reject
});
});
}
// 检查缓存是否有效(当天有效)
function getCachedScore() {
const cache = GM_getValue(STORAGE_KEY, null);
if (cache && cache.date === new Date().toDateString()) {
return cache.score;
}
return null;
}
// 保存缓存
function setCachedScore(score) {
GM_setValue(STORAGE_KEY, { score, date: new Date().toDateString() });
}
// 创建 UI
function createUI(baseScore) {
const container = document.createElement('div');
container.id = 'linux-do-credit';
container.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
background: #fff;
color: #333;
padding: 10px 14px;
border-radius: 8px;
font-size: 14px;
z-index: 9999;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
display: flex;
flex-direction: column;
gap: 8px;
`;
container.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px; cursor: pointer;" id="credit-base">
📊 历史积分: <span id="base-score">${baseScore}</span>
</div>
<div style="display: flex; align-items: center; gap: 6px;">
<input id="current-input" type="number" placeholder="当前积分"
style="width: 80px; padding: 4px 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;">
<span id="diff-result" style="font-weight: bold;">--</span>
</div>
`;
document.body.appendChild(container);
// 点击跳转排行榜(当前 tab)
document.getElementById('credit-base').onclick = () => {
window.location.href = LEADERBOARD_URL;
};
// 输入计算差值
const input = document.getElementById('current-input');
const diffResult = document.getElementById('diff-result');
input.addEventListener('input', () => {
const current = parseInt(input.value, 10);
if (!isNaN(current)) {
const diff = current - baseScore;
const sign = diff >= 0 ? '+' : '';
diffResult.textContent = `${sign}${diff}`;
diffResult.style.color = diff >= 0 ? '#22c55e' : '#ef4444';
} else {
diffResult.textContent = '--';
diffResult.style.color = '#999';
}
});
}
// 显示错误状态
function showError() {
const container = document.createElement('div');
container.style.cssText = `
position: fixed; bottom: 20px; right: 20px;
background: #ef4444; color: white; padding: 10px 14px;
border-radius: 8px; font-size: 14px; z-index: 9999;
`;
container.textContent = '📊 获取积分失败';
document.body.appendChild(container);
}
// 初始化
async function init() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
return;
}
// 尝试使用缓存
const cached = getCachedScore();
if (cached !== null) {
createUI(cached);
return;
}
// 请求新数据
try {
const score = await fetchBaseScore();
setCachedScore(score);
createUI(score);
} catch (e) {
console.error('获取积分失败:', e);
showError();
}
}
init();
})();