Linux Do 个人活动信息查询

获取你Linux do 行为信息

目前为 2024-06-30 提交的版本。查看 最新版本

// ==UserScript==
// @name         Linux Do 个人活动信息查询
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  获取你Linux do 行为信息
// @author       Unique
// @match        https://linux.do/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    let username = '';
    let hideTimeout;
    let isQuerying = false;

    // Constants for local storage keys
    const STORAGE_KEY_COUNTS = 'timings_counts';
    const STORAGE_KEY_DATE = 'timings_date';
    const STORAGE_KEY_TOPIC = 'topic_count';

    // Utility function to check if a timestamp is from today
    function isToday(timestamp) {
        const now = new Date();
        const date = new Date(timestamp);
        return date.toDateString() === now.toDateString();
    }

    // Utility function to check if a timestamp is older than today
    function isOlderThanToday(timestamp) {
        const now = new Date();
        const date = new Date(timestamp);
        return date < new Date(now.getFullYear(), now.getMonth(), now.getDate());
    }

    // Utility function to delay execution
    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // Function to fetch user actions with pagination and count today's actions
    async function countTodaysActions(username, filter, uniqueTopicIds = false) {
        let offset = 0;
        let actionCount = 0;
        let uniqueTopicCount = 0;
        let hasMoreData = true;
        const topicIds = new Set();
        let firstAction = '';

        while (hasMoreData) {
            const url = `https://linux.do/user_actions.json?offset=${offset}&limit=500&username=${username}&filter=${filter}`;
            try {
                const response = await fetch(url);
                const data = await response.json();
                const userActions = data.user_actions;

                // Check if there's no more data
                if (userActions.length === 0) {
                    hasMoreData = false;
                    break;
                }
                firstAction = userActions[0];
                console.log(firstAction);


                // Filter today's actions and update the count
                const todaysActions = userActions.filter(action => isToday(action.created_at));
                actionCount += todaysActions.length;

                if (uniqueTopicIds) {
                    todaysActions.forEach(action => {
                        if (!topicIds.has(action.topic_id)) {
                            topicIds.add(action.topic_id);
                            uniqueTopicCount++;
                        }
                    });
                }

                // Check if the earliest action is older than today to stop further requests
                const oldestAction = userActions[userActions.length - 1];
                if (isOlderThanToday(oldestAction.created_at)) {
                    hasMoreData = false;
                    break;
                }

                // Increment the offset for the next batch
                offset += 500;

                // Delay before the next request
                await delay(600);
            } catch (error) {
                console.error(`Error fetching user actions with filter ${filter}:`, error);
                hasMoreData = false;
                break;
            }
        }

        return { actionCount, uniqueTopicCount,firstAction};
    }

    // Function to fetch reactions received with pagination and count today's reactions
    async function countTodaysReactionsReceived(username) {
        let beforeReactionUserId = null;
        let reactionCount = 0;
        let hasMoreData = true;

        while (hasMoreData) {
            let url = `https://linux.do/discourse-reactions/posts/reactions-received.json?username=${username}`;
            if (beforeReactionUserId) {
                url += `&before_reaction_user_id=${beforeReactionUserId}`;
            }
            try {
                const response = await fetch(url);
                const data = await response.json();
                const reactionsReceived = data;

                // Check if there's no more data
                if (reactionsReceived.length === 0) {
                    hasMoreData = false;
                    break;
                }

                // Filter today's reactions and update the count
                const todaysReactions = reactionsReceived.filter(reaction => isToday(reaction.created_at));
                reactionCount += todaysReactions.length;

                // Check if the earliest reaction is older than today to stop further requests
                const oldestReaction = reactionsReceived[reactionsReceived.length - 1];
                if (isOlderThanToday(oldestReaction.created_at)) {
                    hasMoreData = false;
                    break;
                }

                // Update beforeReactionUserId for the next batch
                beforeReactionUserId = oldestReaction.user_id;

                // Delay before the next request
                await delay(400);
            } catch (error) {
                console.error('Error fetching reactions received:', error);
                hasMoreData = false;
                break;
            }
        }

        return reactionCount;
    }


    async function getTotalUsers() {
        const response = await fetch('https://linux.do/about.json');
        const data = await response.json();
        return data.about.stats.users_count;
    }

    async function getUsersPerPage() {
        const response = await fetch('https://linux.do/leaderboard/1.json?page=0&period=all');
        const data = await response.json();
        return data.users.length;
    }

    async function getPageData(page) {
        const response = await fetch(`https://linux.do/leaderboard/1.json?page=${page}&period=all`);
        return await response.json();
    }

    async function findUserPosition(targetName, gamificationScore) {
        const totalUsers = await getTotalUsers();
        const usersPerPage = await getUsersPerPage();
        const totalPages = Math.ceil(totalUsers / usersPerPage);
        let left = 0;
        let right = totalPages - 1;
        let position = "未查询到";

        while (left <= right) {
            const mid = Math.floor((left + right) / 2);
            const data = await getPageData(mid);

            if (data.users.length === 0) {
                console.log('User not found.');
                break;
            }

            const firstUserTotalScore = data.users[0].total_score;
            const lastUserTotalScore = data.users[data.users.length - 1].total_score;

            if (gamificationScore > firstUserTotalScore) {
                right = mid - 1;
            } else if (gamificationScore < lastUserTotalScore) {
                left = mid + 1;
            } else {
                // Linear search on the current page
                for (let i = 0; i < data.users.length; i++) {
                    if (data.users[i].username === targetName) {
                        position = data.users[i].position;
                        console.log(`User: ${data.users[i].username}, Position: ${position}`);
                        return position;
                    }
                }

                // Continue searching previous pages for the same score
                let tempPage = mid - 1;
                while (tempPage >= 0) {
                    const tempData = await getPageData(tempPage);
                    for (let i = tempData.users.length - 1; i >= 0; i--) {
                        if (tempData.users[i].total_score !== gamificationScore) {
                            tempPage = -1;
                            break;
                        }
                        if (tempData.users[i].username === targetName) {
                            position = tempData.users[i].position;
                            console.log(`User: ${tempData.users[i].username}, Position: ${position}`);
                            return position;
                        }
                    }
                    tempPage--;
                }

                // Continue searching next pages for the same score
                tempPage = mid + 1;
                while (tempPage < totalPages) {
                    const tempData = await getPageData(tempPage);
                    for (let i = 0; i < tempData.users.length; i++) {
                        if (tempData.users[i].total_score !== gamificationScore) {
                            tempPage = totalPages;
                            break;
                        }
                        if (tempData.users[i].username === targetName) {
                            position = tempData.users[i].position;
                            console.log(`User: ${tempData.users[i].username}, Position: ${position}`);
                            return position;
                        }
                    }
                    tempPage++;
                }
                break;
            }

            // Add delay of 0.1 seconds to prevent too many requests in a short time
            await new Promise(resolve => setTimeout(resolve, 100));
        }

        return position;
    }



    // Function to fetch reactions given with pagination and count today's reactions given
    async function countTodaysReactionsGiven(username) {
        let beforeReactionUserId = null;
        let reactionCount = 0;
        let hasMoreData = true;

        while (hasMoreData) {
            let url = `https://linux.do/discourse-reactions/posts/reactions.json?username=${username}`;
            if (beforeReactionUserId) {
                url += `&before_reaction_user_id=${beforeReactionUserId}`;
            }
            try {
                const response = await fetch(url);
                const data = await response.json();
                const reactionsGiven = data;

                // Check if there's no more data
                if (reactionsGiven.length === 0) {
                    hasMoreData = false;
                    break;
                }

                // Filter today's reactions and update the count
                const todaysReactions = reactionsGiven.filter(reaction => isToday(reaction.created_at));
                reactionCount += todaysReactions.length;

                // Check if the earliest reaction is older than today to stop further requests
                const oldestReaction = reactionsGiven[reactionsGiven.length - 1];
                if (isOlderThanToday(oldestReaction.created_at)) {
                    hasMoreData = false;
                    break;
                }

                // Update beforeReactionUserId for the next batch
                beforeReactionUserId = oldestReaction.user_id;

                // Delay before the next request
                await delay(400);
            } catch (error) {
                console.error('Error fetching reactions given:', error);
                hasMoreData = false;
                break;
            }
        }

        return reactionCount;
    }

    async function checkUserOnline(username) {
        try {
            const csrfToken = getCsrfToken();
            const url = `https://linux.do/u/${username}/card.json`;
            // 构建请求头
            const headers = new Headers();
            // 添加需要的请求头,例如认证信息等
            headers.append('Accept', 'application/json, text/javascript, */*; q=0.01');
            headers.append('Discourse-Logged-In', 'true');
            headers.append('Discourse-Present', 'true');
            headers.append('Referer', 'https://linux.do');
            headers.append('Sec-Ch-Ua', '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"');
            headers.append('Sec-Ch-Ua-Mobile', '?0');
            headers.append('Sec-Ch-Ua-Platform', '"Windows"');
            headers.append('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36');
            headers.append('X-Csrf-Token', csrfToken);
            headers.append('X-Requested-With', 'XMLHttpRequest');

            // 发送请求
            const response = await fetch(url, {
                method: 'GET',
                headers: headers,
            });

            const userData = await response.json();
            const lastSeenTime = new Date(userData.user.last_seen_at);
            const currentTime = new Date();
            const timeDifference = currentTime - lastSeenTime;
            const minutesDifference = timeDifference / (1000 * 60);

            // 用户在线状态
            const isOnline = minutesDifference <= 5;

            // 用户点数
            const gamificationScore = userData.user.gamification_score;

            return {
                isOnline,
                gamificationScore
            };
        } catch (error) {
            console.error("Error checking user online status:", error);
            return {
                isOnline: false,
                gamificationScore: null
            };
        }
    }


    // Function to get the CSRF token
    function getCsrfToken() {
        const csrfTokenMeta = document.querySelector('meta[name="csrf-token"]');
        return csrfTokenMeta ? csrfTokenMeta.content : '';
    }

    // Function to fetch the username
    const getUsername = async () => {
        if (username === '') {
            // Construct headers with CSRF token
            const headers = new Headers();
            headers.append('X-Csrf-Token', getCsrfToken());
            // Make the request with CSRF token
            const response = await fetch('https://linux.do/my/summary.json', {
                method: 'GET',
                headers: headers
            });
            const newURL = response.url;
            const urlObj = new URL(newURL);
            const pathParts = urlObj.pathname.split('/');
            username = pathParts[2];
        }
    };

    // Function to count today's likes given, replies made (in distinct topics), likes received, reactions received, and reactions given and display the result
    async function countAllTodaysActions(queryUsername) {
        isQuerying = true; // Set querying flag
        button.innerText = '查询...';
        button.disabled = true; // Disable the button

        const user = queryUsername || username;
        const likesGiven = await countTodaysActions(user, 1); // Assuming filter 1 is for likes given
        await delay(300);
        const repliesMadeData = await countTodaysActions(user, 5, true); // Assuming filter 5 is for replies made, unique topics
        await delay(300);

        let message = `
        ❤️ 送出爱心: ${likesGiven.actionCount}<br>
        💬 回复帖子: ${repliesMadeData.actionCount}<br>
        🗂️ 回复话题: ${repliesMadeData.uniqueTopicCount}
        `;
        if (queryUsername) {
            const { isOnline, gamificationScore } = await checkUserOnline(queryUsername);
            await delay(100);
            const position = await findUserPosition(queryUsername, gamificationScore);

            message += `
            <br>📟 佬友状态: ${isOnline ? '在线🙉' : '离线🙈'}
            <br>🏅 冲浪排名: ${position}
            <br>🏄 最后冲浪: <a href="https://linux.do/t/topic/${repliesMadeData.firstAction.topic_id}/${repliesMadeData.firstAction.post_number}">${repliesMadeData.firstAction.title}</a>
            `
            ;
        }

        if (!queryUsername) {
            const likesReceived = await countTodaysActions(user, 2); // Assuming filter 2 is for likes received
            await delay(300);
            const reactionsReceived = await countTodaysReactionsReceived(user); // For reactions received
            await delay(300);
            const reactionsGiven = await countTodaysReactionsGiven(user); // For reactions given

            // Load the stored data
            const timingsCount = parseInt(localStorage.getItem(STORAGE_KEY_COUNTS), 10) || 0;
            const timingsTotalTime = parseInt(localStorage.getItem('timeSpent'), 10) || 0;
            let storedTopics = (() => { try { return JSON.parse(localStorage.getItem(STORAGE_KEY_TOPIC)) || []; } catch(e) { return []; }})();


            // 将总时间转换为小时、分钟和秒
            const hours = Math.floor(timingsTotalTime / 3600);
            const minutes = Math.floor((timingsTotalTime % 3600) / 60);
            const seconds = timingsTotalTime % 3600 % 60;

            message += `
            <br>🥰 收到爱心: ${likesReceived.actionCount}<br>
            🤩 收到回应: ${reactionsReceived}<br>
            👏 给出回应: ${reactionsGiven}<br>
            📖 阅读话题: ${storedTopics.length}<br>
            ⏱️ 阅读帖子: ${timingsCount}<br>
            🕒 停留时间: ${hours}时${minutes}分${seconds}秒
            `;
        }

        resultContainer.innerHTML = message; // Set the message
        resultContainer.style.display = 'block'; // Ensure the result container is visible
        isQuerying = false; // Reset querying flag
        button.innerText = '查询'; // Change button text back to "查询"
        button.disabled = false; // Re-enable the button
    }

    // Add input field for querying other users and result container
    const inputContainer = document.createElement('div');
    inputContainer.style.position = 'fixed';
    inputContainer.style.bottom = '-300px';
    inputContainer.style.left = '10px';
    inputContainer.style.transition = 'bottom 0.3s ease-in-out';
    inputContainer.style.backgroundColor = 'white';
    inputContainer.style.padding = '15px';
    inputContainer.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
    inputContainer.style.borderRadius = '10px';
    inputContainer.style.fontFamily = 'Arial, sans-serif';
    inputContainer.style.fontSize = '14px';
    inputContainer.style.color = '#333';

    const input = document.createElement('input');
    input.type = 'text';
    input.placeholder = '输入用户名';
    input.style.padding = '10px';
    input.style.marginRight = '10px';
    input.style.width = '160px';
    input.style.border = '1px solid #ccc';
    input.style.borderRadius = '5px';
    inputContainer.appendChild(input);

    const button = document.createElement('button');
    button.innerText = '查询';
    button.style.padding = '10px 15px';
    button.style.border = 'none';
    button.style.backgroundColor = '#007BFF';
    button.style.color = '#fff';
    button.style.borderRadius = '5px';
    button.style.cursor = 'pointer';
    button.onmouseover = () => button.style.backgroundColor = '#0056b3';
    button.onmouseout = () => button.style.backgroundColor = '#007BFF';
    button.onclick = () => {
        const queryUsername = input.value.trim();
        countAllTodaysActions(queryUsername);
        hideContainer(); // Hide the container
    };
    inputContainer.appendChild(button);

    const resultContainer = document.createElement('div');
    resultContainer.style.marginTop = '20px';
    resultContainer.style.padding = '20px';
    resultContainer.style.width = '180px';
    resultContainer.style.border = '1px solid #ccc';
    resultContainer.style.borderRadius = '10px';
    resultContainer.style.backgroundColor = '#f9f9f9';
    resultContainer.style.display = 'none';
    inputContainer.appendChild(resultContainer);

    const closeButton = document.createElement('button');
    closeButton.innerText = '清除';
    closeButton.style.display = 'block';
    closeButton.style.marginTop = '20px';
    closeButton.style.padding = '10px 15px';
    closeButton.style.border = 'none';
    closeButton.style.backgroundColor = '#dc3545';
    closeButton.style.color = '#fff';
    closeButton.style.borderRadius = '5px';
    closeButton.style.cursor = 'pointer';
    closeButton.onmouseover = () => closeButton.style.backgroundColor = '#c82333';
    closeButton.onmouseout = () => closeButton.style.backgroundColor = '#dc3545';
    closeButton.onclick = () => {
        resultContainer.style.display = 'none';
        input.value = ''; // Clear the input field
        hideContainer(); // Hide the container
    };
    inputContainer.appendChild(closeButton);

    document.body.appendChild(inputContainer);

    // Function to show the container
    const showContainer = () => {
        clearTimeout(hideTimeout);
        inputContainer.style.bottom = '10px';
    };

    // Function to hide the container
    const hideContainer = () => {
        if (!isQuerying && !inputContainer.matches(':hover')) {
            hideTimeout = setTimeout(() => {
                inputContainer.style.bottom = '-500px';
            }, 2000);
        }
    };

    // Event listener for mouse movement
    document.addEventListener('mousemove', (e) => {
        if (e.clientY > window.innerHeight - 50 && e.clientX < 50) {
            showContainer();
            hideTimeout = setTimeout(() => {
                hideContainer();
            }, 2000); // 2秒后隐藏容器
        }
    });

    // Event listener for mouse over and out
    inputContainer.addEventListener('mouseover', () => {
        clearTimeout(hideTimeout);
    });

    inputContainer.addEventListener('mouseout', () => {
        hideTimeout = setTimeout(() => {
            hideContainer();
        }, 2000);
    });

    // Fetch the username on script load
    getUsername();

    // Check if the stored date is today
    const storedDate = localStorage.getItem(STORAGE_KEY_DATE);
    const now = new Date();
    if (!storedDate || isOlderThanToday(storedDate)) {
        // Reset the counts and times if the stored date is not today
        localStorage.setItem(STORAGE_KEY_COUNTS, '0');
        localStorage.setItem(STORAGE_KEY_TOPIC,[]);
        localStorage.setItem(STORAGE_KEY_DATE, now.toISOString());
        localStorage.setItem('timeSpent',0);
    }

    // Function to handle and monitor timings request
    function handleTimingsRequest(count, topicId) {
        const now = new Date();
        const todayStr = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;

        // Load the stored data
        let storedCounts = parseInt(localStorage.getItem(STORAGE_KEY_COUNTS), 10) || 0;
        const storedDate = localStorage.getItem(STORAGE_KEY_DATE) || '';
        let storedTopics = (() => { try { return JSON.parse(localStorage.getItem(STORAGE_KEY_TOPIC)) || []; } catch(e) { return []; }})();

        // Check if the stored date is today
        if (storedDate !== todayStr) {
            // If not, reset the stored data
            storedCounts = 0;
            storedTopics = [];
        }

        if (!storedTopics.includes(topicId)) {
            storedTopics.push(topicId);
        }
        // Update the stored data with the new values
        storedCounts += count;

        // Store the updated data
        localStorage.setItem(STORAGE_KEY_COUNTS, storedCounts);
        localStorage.setItem(STORAGE_KEY_DATE, todayStr);
        localStorage.setItem(STORAGE_KEY_TOPIC, JSON.stringify(storedTopics));

        // Display the stored data
        console.log(`Today's timings count: ${storedCounts}`);
    }


    (function() {
        // Save the original XMLHttpRequest open and send methods
        const originalXHROpen = XMLHttpRequest.prototype.open;
        const originalXHRSend = XMLHttpRequest.prototype.send;

        // Overwrite the open method
        XMLHttpRequest.prototype.open = function(...args) {
            const url = args[1];
            this._url = url;
            if (url === '/topics/timings') {
                // Record start time for the request
                const xhr = this;
                const startTime = performance.now();

                // Listen for request completion
                xhr.addEventListener('readystatechange', function() {
                    if (xhr.readyState === XMLHttpRequest.DONE) {
                        const endTime = performance.now();
                        const duration = endTime - startTime;
                    }
                });
            }

            // Call the original open method
            return originalXHROpen.apply(this, args);
        };

        // Overwrite the send method
        XMLHttpRequest.prototype.send = function(body) {
            // Process the request body if it's the correct URL
            if (this._url === '/topics/timings') {
                processRequestBody(body);
            }

            // Call the original send method
            return originalXHRSend.call(this, body);
        };

        // Process request body to extract timing data
        function processRequestBody(body) {
            if (typeof body === 'string') {
                try {
                    const params = new URLSearchParams(body);
                    let timings = 0;
                    let topicTime = 0;
                    let topicId = 0;
                    let topicCount = 0;

                    for (const [key, value] of params.entries()) {
                        if (key.startsWith('timings[')) {
                            timings += parseInt(value);
                            topicCount+=1;
                        }
                        if (key.startsWith('topic_time')) {
                            topicTime = parseInt(value);
                        }
                        if (key.startsWith('topic_id')) {
                            topicId = parseInt(value);
                        }
                    }
                    const count = topicCount;
                    handleTimingsRequest(count,topicId);

                } catch (error) {
                    console.error('Error processing form data:', error);
                }
            } else {
                console.error('Unknown request body type:', body);
            }
        }
    })();
    let timer;
    let timeSpent = parseInt(localStorage.getItem('timeSpent')) || 0;

    function updateLocalStorage() {
        localStorage.setItem('timeSpent', timeSpent);
    }

    function startTimer() {
        timer = setInterval(() => {
            timeSpent += 1;
            updateLocalStorage();
        }, 1000);
    }

    function stopTimer() {
        clearInterval(timer);
    }

    document.addEventListener('visibilitychange', () => {
        if (document.visibilityState === 'visible') {
            startTimer();
        } else {
            stopTimer();
        }
    });

    window.addEventListener('load', () => {
        startTimer();
    });

    window.addEventListener('beforeunload', () => {
        stopTimer();
        updateLocalStorage();
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址