Linux.do 浏览助手

都是一些简简单单的功能

目前为 2024-11-28 提交的版本。查看 最新版本

// ==UserScript==
// @name         Linux.do 浏览助手
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  都是一些简简单单的功能
// @author       LiNFERS
// @match        https://linux.do/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    // 工具函数:设置样式
    function setElementStyle(element, styles) {
        for (const [key, value] of Object.entries(styles)) {
            element.style[key] = value;
        }
    }

    // 创建浏览记录按钮
    const btn = document.createElement('button');
    btn.innerHTML = '最近浏览';
    setElementStyle(btn, {
        position: 'fixed',
        bottom: '140px',
        right: '20px',
        zIndex: '9999',
        padding: '8px 15px',
        background: 'rgba(255, 255, 255, 0.6)',
        color: '#333',
        border: 'none',
        borderRadius: '20px',
        cursor: 'pointer',
        backdropFilter: 'blur(5px)',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
        transition: 'all 0.3s ease',
        width: '90px',
    });
    document.body.appendChild(btn);

    // 创建随机看帖按钮
    const randomBtn = document.createElement('button');
    randomBtn.innerHTML = '随机看帖';
    setElementStyle(randomBtn, {
        position: 'fixed',
        bottom: '80px',
        right: '20px',
        zIndex: '9999',
        padding: '8px 15px',
        background: 'rgba(255, 255, 255, 0.6)',
        color: '#333',
        border: 'none',
        borderRadius: '20px',
        cursor: 'pointer',
        backdropFilter: 'blur(5px)',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
        transition: 'all 0.3s ease',
        width: '90px',
    });
    document.body.appendChild(randomBtn);

    // 创建插眼按钮
    const markBtn = document.createElement('button');
    markBtn.innerHTML = '插眼';
    setElementStyle(markBtn, {
        position: 'fixed',
        bottom: '20px',
        right: '20px',
        zIndex: '9999',
        padding: '8px 15px',
        background: 'rgba(255, 255, 255, 0.6)',
        color: '#333',
        border: 'none',
        borderRadius: '20px',
        cursor: 'pointer',
        backdropFilter: 'blur(5px)',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
        transition: 'all 0.3s ease',
        width: '90px',
    });
    document.body.appendChild(markBtn);

    // 创建返回按钮
    const backBtn = document.createElement('button');
    backBtn.innerHTML = '←';
    setElementStyle(backBtn, {
        position: 'fixed',
        bottom: '140px',
        right: '130px',
        zIndex: '9999',
        width: '35px',
        height: '35px',
        background: 'rgba(255, 255, 255, 0.6)',
        color: '#333',
        border: 'none',
        borderRadius: '50%',
        cursor: 'pointer',
        backdropFilter: 'blur(5px)',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
        transition: 'all 0.3s ease',
        fontSize: '16px',
    });
    document.body.appendChild(backBtn);

    // 创建回到顶部按钮
    const topBtn = document.createElement('button');
    topBtn.innerHTML = '↑';
    setElementStyle(topBtn, {
        position: 'fixed',
        bottom: '80px',
        right: '130px',
        zIndex: '9999',
        width: '35px',
        height: '35px',
        background: 'rgba(255, 255, 255, 0.6)',
        color: '#333',
        border: 'none',
        borderRadius: '50%',
        cursor: 'pointer',
        backdropFilter: 'blur(5px)',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
        transition: 'all 0.3s ease',
        fontSize: '16px',
        display: 'block', // 始终显示
    });
    document.body.appendChild(topBtn);

    // 创建到达底部按钮
    const bottomBtn = document.createElement('button');
    bottomBtn.innerHTML = '↓';
    setElementStyle(bottomBtn, {
        position: 'fixed',
        bottom: '20px',
        right: '130px',
        zIndex: '9999',
        width: '35px',
        height: '35px',
        background: 'rgba(255, 255, 255, 0.6)',
        color: '#333',
        border: 'none',
        borderRadius: '50%',
        cursor: 'pointer',
        backdropFilter: 'blur(5px)',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
        transition: 'all 0.3s ease',
        fontSize: '16px',
    });
    document.body.appendChild(bottomBtn);

    // 按钮透明度控制
    const buttons = [btn, randomBtn, markBtn, backBtn, topBtn, bottomBtn];
    let fadeTimeout;

    function fadeButtons() {
        buttons.forEach(button => {
            button.style.opacity = '0.3';
        });
    }

    function resetButtons() {
        buttons.forEach(button => {
            button.style.opacity = '1';
        });
        if (fadeTimeout) {
            clearTimeout(fadeTimeout);
        }
        fadeTimeout = setTimeout(fadeButtons, 3000);
    }

    // 监听鼠标移动
    document.addEventListener('mousemove', (e) => {
        // 检查鼠标是否在任何按钮附近
        const isNearButtons = buttons.some(button => {
            const rect = button.getBoundingClientRect();
            const buffer = 50; // 扩大检测范围
            return e.clientX >= rect.left - buffer &&
                   e.clientX <= rect.right + buffer &&
                   e.clientY >= rect.top - buffer &&
                   e.clientY <= rect.bottom + buffer;
        });

        if (isNearButtons) {
            resetButtons();
        }
    });

    // 初始设置淡出定时器
    resetButtons();

    // 插眼功能
    markBtn.onclick = async () => {
        const path = window.location.pathname;
        const postId = path.match(/\/t\/topic\/(\d+)/)?.[1] || path.match(/\/topic\/(\d+)/)?.[1];
        if (!postId) {
            alert('请在帖子页面使用此功能');
            return;
        }

        try {
            // 获取 CSRF token
            const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
            if (!csrfToken) {
                throw new Error('无法获取 CSRF token');
            }

            // 发送回复请求
            const response = await fetch('https://linux.do/posts', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-Token': csrfToken,
                },
                body: JSON.stringify({
                    raw: 'LiNFERSmark~~~',
                    topic_id: postId,
                }),
                credentials: 'include'
            });

            if (!response.ok) {
                throw new Error('回复失败');
            }

            alert('插眼成功!');
            // 刷新页面以显示新回复
            window.location.reload();
        } catch (error) {
            alert('插眼失败:' + error.message);
        }
    };

    // 随机看帖功能
    randomBtn.onclick = async () => {
        // 获取最新帖子列表
        const response = await fetch('https://linux.do/latest.json');
        const data = await response.json();

        if (data.topic_list && data.topic_list.topics.length > 0) {
            // 从帖子列表中随机选择一个
            const topics = data.topic_list.topics;
            const randomIndex = Math.floor(Math.random() * topics.length);
            const randomTopic = topics[randomIndex];

            // 导航到随机选择的帖子
            window.location.href = `https://linux.do/t/topic/${randomTopic.id}`;
        }
    };

    // 回到顶部功能
    topBtn.onclick = () => {
        window.scrollTo({
            top: 0,
            behavior: 'smooth'
        });
    };

    // 到达底部功能
    bottomBtn.onclick = () => {
        window.scrollTo({
            top: document.documentElement.scrollHeight,
            behavior: 'smooth'
        });
    };

    // 返回功能
    backBtn.onclick = () => {
        history.back();
    };

    // 创建弹窗
    const popup = document.createElement('div');
    setElementStyle(popup, {
        display: 'none',
        position: 'fixed',
        bottom: '80px',
        right: '220px',
        width: '300px',
        maxHeight: '400px',
        background: 'rgba(255, 255, 255, 0.95)',
        borderRadius: '15px',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
        zIndex: '9999',
        overflowY: 'auto',
        padding: '10px',
        backdropFilter: 'blur(10px)',
        opacity: '0',
        transform: 'translateX(20px)',
        transition: 'opacity 0.3s ease, transform 0.3s ease',
    });
    document.body.appendChild(popup);

    let lastRecordedId = ''; // 记录最后一次记录的帖子 ID

    function recordVisit() {
        setTimeout(() => {
            const path = window.location.pathname;
            const postId = path.match(/\/t\/topic\/(\d+)/)?.[1] || path.match(/\/topic\/(\d+)/)?.[1];
            if (!postId) return;

            const title = document.querySelector('h1')?.textContent || document.title;
            const url = window.location.href;

            if (postId === lastRecordedId) return; // 避免重复记录
            lastRecordedId = postId;

            let history = GM_getValue('postHistory', []);

            // 检查是否存在相同帖子 ID
            const existingIndex = history.findIndex(item => item.id === postId);
            if (existingIndex !== -1) {
                history.splice(existingIndex, 1); // 删除重复记录
            }

            history.unshift({
                id: postId,
                title: title,
                url: url,
                time: new Date().toISOString(),
            });

            const maxRecords = Math.min(GM_getValue('maxRecords', 10), 20);
            if (history.length > maxRecords) {
                history = history.slice(0, maxRecords);
            }

            GM_setValue('postHistory', history);
        }, 1000);
    }

    function showHistory() {
        const history = GM_getValue('postHistory', []);
        popup.innerHTML = '';

        // 设置区域
        const settingsDiv = document.createElement('div');
        setElementStyle(settingsDiv, {
            marginBottom: '10px',
            padding: '5px',
            borderBottom: '1px solid #eee',
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
        });

        const settingsLabel = document.createElement('label');
        settingsLabel.textContent = '记录条数: ';
        const settingsInput = document.createElement('input');
        settingsInput.type = 'number';
        settingsInput.min = 1;
        settingsInput.max = 20;
        settingsInput.value = GM_getValue('maxRecords', 10);
        setElementStyle(settingsInput, {
            width: '60px',
            margin: '0 5px',
            padding: '3px',
            borderRadius: '4px',
            border: '1px solid #ddd',
        });
        settingsInput.onchange = (e) => {
            let value = parseInt(e.target.value, 10);
            value = Math.min(20, Math.max(1, value));
            e.target.value = value;
            GM_setValue('maxRecords', value);
        };

        const clearBtn = document.createElement('button');
        clearBtn.textContent = '清空记录';
        setElementStyle(clearBtn, {
            padding: '5px 10px',
            background: 'rgba(255, 68, 68, 0.9)',
            color: 'white',
            border: 'none',
            borderRadius: '15px',
            cursor: 'pointer',
            transition: 'all 0.3s ease',
        });
        clearBtn.onclick = () => {
            if (confirm('确定要清空所有浏览记录吗?')) {
                GM_setValue('postHistory', []);
                showHistory();
            }
        };

        settingsDiv.appendChild(settingsLabel);
        settingsDiv.appendChild(settingsInput);
        settingsDiv.appendChild(clearBtn);
        popup.appendChild(settingsDiv);

        if (history.length === 0) {
            popup.innerHTML += '<p style="text-align:center;color:#666;">暂无浏览记录</p>';
            return;
        }

        // 展示记录
        const ul = document.createElement('ul');
        setElementStyle(ul, {
            listStyle: 'none',
            margin: '0',
            padding: '0',
        });

        history.forEach((item, index) => {
            const li = document.createElement('li');
            setElementStyle(li, {
                padding: '10px',
                borderBottom: '1px solid #eee',
                cursor: 'pointer',
                transition: 'all 0.3s ease',
                borderRadius: '8px',
                display: 'flex',
                justifyContent: 'space-between',
                alignItems: 'center',
            });

            const time = new Date(item.time).toLocaleString();
            const titleDiv = document.createElement('div');
            titleDiv.innerHTML = `
                <div style="font-size:14px;margin-bottom:5px;">${item.title}</div>
                <div style="font-size:12px;color:#666;">${time}</div>
            `;
            titleDiv.style.flex = '1';
            titleDiv.onclick = () => {
                window.location.href = item.url;
            };

            const deleteBtn = document.createElement('button');
            deleteBtn.textContent = '删除';
            setElementStyle(deleteBtn, {
                padding: '5px 10px',
                background: 'rgba(255, 68, 68, 0.9)',
                color: 'white',
                border: 'none',
                borderRadius: '8px',
                cursor: 'pointer',
                marginLeft: '10px',
            });
            deleteBtn.onclick = (e) => {
                e.stopPropagation(); // 防止触发跳转
                if (confirm(`确定删除记录 "${item.title}" 吗?`)) {
                    history.splice(index, 1);
                    GM_setValue('postHistory', history);
                    showHistory();
                }
            };

            li.appendChild(titleDiv);
            li.appendChild(deleteBtn);
            ul.appendChild(li);
        });

        popup.appendChild(ul);
    }

    // 弹窗显示控制
    let isPopupVisible = false;
    btn.onclick = () => {
        isPopupVisible = !isPopupVisible;
        popup.style.display = isPopupVisible ? 'block' : 'none';
        if (isPopupVisible) {
            showHistory();
            // 添加一个小延迟以确保过渡动画生效
            setTimeout(() => {
                popup.style.opacity = '1';
                popup.style.transform = 'translateX(0)';
            }, 10);
        } else {
            popup.style.opacity = '0';
            popup.style.transform = 'translateX(20px)';
            // 等待过渡动画完成后隐藏弹窗
            setTimeout(() => {
                if (!isPopupVisible) {
                    popup.style.display = 'none';
                }
            }, 300);
        }
    };

    // 点击外部关闭弹窗
    document.addEventListener('click', (e) => {
        if (!popup.contains(e.target) && e.target !== btn) {
            if (isPopupVisible) {
                isPopupVisible = false;
                popup.style.opacity = '0';
                popup.style.transform = 'translateX(20px)';
                setTimeout(() => {
                    if (!isPopupVisible) {
                        popup.style.display = 'none';
                    }
                }, 300);
            }
        }
    });

    // 初始记录
    recordVisit();

    // 监听 URL 变化
    let lastUrl = location.href;
    new MutationObserver(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            recordVisit();
        }
    }).observe(document.body, { childList: true, subtree: true });
})();

QingJ © 2025

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