Linux do Level Enhanced

Enhanced script to track progress towards next trust level on linux.do with added search functionality, adjusted posts read limit, and a breathing icon animation.

当前为 2024-03-10 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Linux do Level Enhanced
// @namespace    http://tampermonkey.net/
// @version      0.0.4
// @description  Enhanced script to track progress towards next trust level on linux.do with added search functionality, adjusted posts read limit, and a breathing icon animation.
// @author       Hua, Reno
// @match        https://linux.do/*
// @icon         https://www.google.com/s2/favicons?domain=linux.do
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const StyleManager = {
        injectStyles: function() {
            const styleSheet = document.createElement('style');
            styleSheet.type = 'text/css';
            styleSheet.innerText = `
                @keyframes breathAnimation {
                    0%, 100% {
                        transform: scale(1);
                        box-shadow: 0 0 5px rgba(0,0,0,0.5);
                    }
                    50% {
                        transform: scale(1.1);
                        box-shadow: 0 0 10px rgba(0,0,0,0.7);
                    }
                }
                .breath-animation {
                    animation: breathAnimation 4s ease-in-out infinite;
                }
                .minimized {
                    width: 50px !important;
                    height: 50px !important;
                    border-radius: 50% !important;
                    padding: 0 !important;
                    overflow: hidden;
                    cursor: pointer;
                }
                .button:hover {
                    background-color: #f0f0f0;
                }
            `;
            document.head.appendChild(styleSheet);
        }
    };

    const DataManager = {
        ABOUT_DATA_URL: `https://linux.do/about.json`,
        USER_DATA_URL_PREFIX: `https://linux.do/u/`,
        USER_DATA_URL_SUFFIX: `/summary.json`,
        FETCH_REQUEST_OPTIONS: {
            headers: {
                "Accept": "application/json",
                "User-Agent": "Mozilla/5.0"
            },
            method: "GET",
        },
        levelRequirements: {
            0: { 'topics_entered': 5, 'posts_read_count': 30, 'time_read': 600 },
            1: { 'days_visited': 15, 'likes_given': 1, 'likes_received': 1, 'post_count': 3, 'topics_entered': 20, 'posts_read_count': 100, 'time_read': 3600 },
            2: { 'days_visited': 50, 'likes_given': 30, 'likes_received': 20, 'post_count': 10, 'topics_entered': 0, 'posts_read_count': 0 }
        },
        levelDescriptions: {
            0: "游客", 1: "基本用户", 2: "成员", 3: "活跃用户", 4: "领导者"
        },

        async fetchAboutData() {
            try {
                const response = await fetch(this.ABOUT_DATA_URL, this.FETCH_REQUEST_OPTIONS);
                if (!response.ok) throw new Error(`HTTP 错误!状态:${response.status}`);
                return await response.json();
            } catch (error) {
                console.error("获取关于页面数据失败:", error);
                UIManager.displayError("获取关于页面数据失败");
                return null;
            }
        },

        async fetchUserData(username) {
            try {
                const response = await fetch(`${this.USER_DATA_URL_PREFIX}${username}${this.USER_DATA_URL_SUFFIX}`, this.FETCH_REQUEST_OPTIONS);
                if (!response.ok) throw new Error(`HTTP 错误!状态:${response.status}`);
                return await response.json();
            } catch (error) {
                console.error("获取用户数据失败:", error);
                UIManager.displayError("获取用户数据失败");
                return null;
            }
        }
    };

    const UIManager = {
        popup: null,
        content: null,
        searchBox: null,
        searchButton: null,
        minimizeButton: null,

        createPopup: function() {
            this.popup = document.createElement('div');
            this.popup.classList.add('linuxDoLevelPopup');
            this.popup.id = 'linuxDoLevelPopup';
            this.setStyle(this.popup, {
                position: 'fixed',
                bottom: '20px',
                right: '20px',
                width: '250px',
                height: 'auto',
                backgroundColor: 'white',
                boxShadow: '0 0 10px rgba(0,0,0,0.5)',
                padding: '15px',
                zIndex: '10000',
                fontSize: '14px',
                borderRadius: '5px',
            });

            this.content = document.createElement('div');
            this.content.id = 'linuxDoLevelPopupContent';
            this.content.innerHTML = '欢迎使用 Linux do 等级增强插件';
            this.popup.appendChild(this.content);

            this.searchBox = document.createElement('input');
            this.searchBox.type = 'text';
            this.searchBox.placeholder = '请输入用户名...';
            this.setStyle(this.searchBox, { width: '100%', marginTop: '10px' });
            this.searchBox.id = 'linuxDoUserSearch';
            this.popup.appendChild(this.searchBox);

            this.searchButton = document.createElement('button');
            this.searchButton.textContent = '搜索';
            this.searchButton.classList.add('button');
            this.setStyle(this.searchButton, { width: '100%', marginTop: '10px' });
            this.popup.appendChild(this.searchButton);

            this.minimizeButton = document.createElement('button');
            this.minimizeButton.textContent = '隐藏';
            this.minimizeButton.classList.add('button');
            this.setStyle(this.minimizeButton, {
                position: 'absolute',
                top: '5px',
                right: '5px',
                zIndex: '10001',
                background: 'transparent',
                border: 'none',
                cursor: 'pointer',
                borderRadius: '50%',
                textAlign: 'center',
                lineHeight: '40px',
                width: '40px',
                height: '40px',
            });
            this.popup.appendChild(this.minimizeButton);

            document.body.appendChild(this.popup);
        },

        updatePopupContent: function(userSummary, user, status) {
            if (this.content && userSummary && user) {
                let content = `<strong>信任等级:</strong>${DataManager.levelDescriptions[user.trust_level]}<br><strong>升级进度:</strong><br>`;

                if (user.trust_level === 3) {
                    content += `联系管理员以升级到领导者<br>`;
                } else if (user.trust_level === 4) {
                    content += `您已是最高信任等级<br>`;
                } else {
                    const requirements = DataManager.levelRequirements[user.trust_level];
                    if (user.trust_level === 2) {
                        requirements['posts_read_count'] = Math.min(Math.floor(status.posts_30_days / 4), 20000);
                        requirements['topics_entered'] = Math.min(Math.floor(status.topics_30_days / 4), 500);
                    }

                    Object.entries(requirements).forEach(([key, val]) => {
                        const currentVal = userSummary[key] || 0;
                        const color = currentVal >= val ? "green" : "red";
                        content += `${this.translateStat(key)}: <span style="color: ${color};">${currentVal} / ${val}</span><br>`;
                    });
                }

                this.content.innerHTML = content;
            }
        },

        togglePopupSize: function() {
            if (this.popup.classList.contains('minimized')) {
                // Maximizing
                this.popup.classList.remove('minimized');
                this.popup.classList.remove('breath-animation');
                this.popup.style.width = '250px';
                this.popup.style.height = 'auto';
                this.content.style.display = 'block';
                this.searchBox.style.display = 'block';
                this.searchButton.style.display = 'block';
                this.minimizeButton.textContent = '隐藏';
            } else {
                // Minimizing
                this.popup.classList.add('minimized');
                this.popup.classList.add('breath-animation');
                this.popup.style.width = '50px';
                this.popup.style.height = '50px';
                this.content.style.display = 'none';
                this.searchBox.style.display = 'none';
                this.searchButton.style.display = 'none';
                this.minimizeButton.textContent = '';
            }
        },

        displayError: function(message) {
            if (this.content) {
                this.content.innerHTML = `<strong>错误:</strong>${message}`;
            }
        },

        setStyle: function(element, styles) {
            Object.assign(element.style, styles);
        },

        translateStat: function(stat) {
            const translations = {
                'days_visited': '访问天数',
                'likes_given': '给出的赞',
                'likes_received': '收到的赞',
                'post_count': '帖子数量',
                'posts_read_count': '阅读的帖子数',
                'topics_entered': '进入的主题数',
                'time_read': '阅读时间'
            };

            return translations[stat] || stat;
        }
    };

    const EventHandler = {
        handleSearch: async function() {
            const username = UIManager.searchBox.value.trim();
            if (username) {
                const aboutData = await DataManager.fetchAboutData();
                const userData = await DataManager.fetchUserData(username);
                if (userData && aboutData) {
                    const userSummary = userData.user_summary;
                    const user = userData.users[0];
                    const status = aboutData.about.stats;
                    UIManager.updatePopupContent(userSummary, user, status);
                }
            }
        },

        initEventListeners: function() {
            UIManager.searchButton.addEventListener('click', this.handleSearch.bind(this));
            UIManager.minimizeButton.addEventListener('click', UIManager.togglePopupSize.bind(UIManager));
        }
    };

    const LevelEnhancement = {
        init: function() {
            StyleManager.injectStyles();
            UIManager.createPopup();
            EventHandler.initEventListeners();
        }
    };

    LevelEnhancement.init();
})();