Greasy Fork

Greasy Fork is available in English.

Linux.do Credit Display

显示 linux.do 基础积分,支持手动计算差值

当前为 2026-02-04 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
})();