Steampy 一键开冲!

按 Ctrl+ 单击特定图片 URL 模式时,会在后台打开一个新标签页,其中包含相应的 Steam 应用程序页面,并阻止默认单击操作 ,可以知道浏览过的游戏,并支持导出

// ==UserScript==
// @name         Steampy 一键开冲!
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  按 Ctrl+ 单击特定图片 URL 模式时,会在后台打开一个新标签页,其中包含相应的 Steam 应用程序页面,并阻止默认单击操作 ,可以知道浏览过的游戏,并支持导出
// @match        https://steampy.com/*
// @author       zhxilo
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Add CSS for the viewed indicator, buttons, instructions, and collapsible panel
    GM_addStyle(`
        .viewed-indicator {
            position: absolute;
            top: 5px;
            left: 5px;
            background-color: red;
            color: black;
            padding: 2px 5px;
            border-radius: 3px;
            font-size: 12px;
            z-index: 1000;
            font-weight: bold;
        }
        #scriptControls {
            position: fixed;
            top: 10px;
            right: 10px;
            z-index: 10000;
            background-color: white;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        #scriptControls button {
            margin: 5px;
            padding: 5px 10px;
        }
        #instructions {
            display: none;
            margin-top: 10px;
            padding: 10px;
            background-color: #f0f0f0;
            border-radius: 5px;
        }
        #toggleInstructions {
            cursor: pointer;
            color: blue;
            text-decoration: underline;
        }
        #collapseToggle {
            cursor: pointer;
            user-select: none;
        }
        #controlPanel {
            margin-top: 10px;
        }
        .hidden {
            display: none;
        }
    `);

    // Load viewed games from GM_getValue
    let viewedGames = GM_getValue('viewedGames', {});
    let misclickPrevention = GM_getValue('misclickPrevention', false);

    // Function to get appId from image URL
    function getAppIdFromUrl(url) {
        const match = url.match(/\/apps\/(\d+)\//);
        return match ? match[1] : null;
    }

    // Function to toggle viewed status
    function toggleViewedStatus(appId, imgContainer) {
        if (viewedGames[appId]) {
            delete viewedGames[appId];
            imgContainer.querySelector('.viewed-indicator')?.remove();
        } else {
            viewedGames[appId] = true;
            addViewedIndicator(imgContainer);
        }
        GM_setValue('viewedGames', viewedGames);
    }

    // Function to add viewed indicator
    function addViewedIndicator(container) {
        if (!container.querySelector('.viewed-indicator')) {
            const indicator = document.createElement('div');
            indicator.className = 'viewed-indicator';
            indicator.textContent = '已浏览';
            container.style.position = 'relative';
            container.appendChild(indicator);
        }
    }

    // Apply viewed indicators
    function applyViewedIndicators() {
        document.querySelectorAll('img').forEach(img => {
            const appId = getAppIdFromUrl(img.src);
            if (appId) {
                const container = img.closest('.item-container') || img.parentElement;
                if (viewedGames[appId]) {
                    addViewedIndicator(container);
                } else {
                    container.querySelector('.viewed-indicator')?.remove();
                }
            }
        });
    }

    // Function to export viewed games
    function exportViewedGames() {
        const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(viewedGames));
        const downloadAnchorNode = document.createElement('a');
        downloadAnchorNode.setAttribute("href", dataStr);
        downloadAnchorNode.setAttribute("download", "steampy_viewed_games.json");
        document.body.appendChild(downloadAnchorNode);
        downloadAnchorNode.click();
        downloadAnchorNode.remove();
    }

    // Function to import viewed games
    function importViewedGames() {
        const input = document.createElement('input');
        input.type = 'file';
        input.onchange = e => {
            const file = e.target.files[0];
            const reader = new FileReader();
            reader.readAsText(file, 'UTF-8');
            reader.onload = readerEvent => {
                const content = readerEvent.target.result;
                viewedGames = JSON.parse(content);
                GM_setValue('viewedGames', viewedGames);
                applyViewedIndicators();
            }
        }
        input.click();
    }

    // Function to open all unviewed games
    function openAllUnviewed() {
        document.querySelectorAll('img').forEach(img => {
            const appId = getAppIdFromUrl(img.src);
            if (appId && !viewedGames[appId]) {
                const steamAppUrl = `https://store.steampowered.com/app/${appId}`;
                window.open(steamAppUrl, '_blank', 'noopener,noreferrer');
                toggleViewedStatus(appId, img.closest('.item-container') || img.parentElement);
            }
        });
    }

    // Function to toggle misclick prevention
    function toggleMisclickPrevention() {
        misclickPrevention = !misclickPrevention;
        GM_setValue('misclickPrevention', misclickPrevention);
        document.getElementById('misclickPreventionStatus').textContent = misclickPrevention ? '开启' : '关闭';
    }

    // Add script control buttons and instructions
    function addScriptControls() {
        const controlsContainer = document.createElement('div');
        controlsContainer.id = 'scriptControls';

        const collapseToggle = document.createElement('div');
        collapseToggle.id = 'collapseToggle';
        collapseToggle.textContent = '▼ 控制面板';
        collapseToggle.onclick = () => {
            const controlPanel = document.getElementById('controlPanel');
            controlPanel.classList.toggle('hidden');
            collapseToggle.textContent = controlPanel.classList.contains('hidden') ? '▶ 控制面板' : '▼ 控制面板';
        };

        const controlPanel = document.createElement('div');
        controlPanel.id = 'controlPanel';

        const refreshButton = document.createElement('button');
        refreshButton.textContent = '刷新标记';
        refreshButton.onclick = applyViewedIndicators;

        const exportButton = document.createElement('button');
        exportButton.textContent = '导出已浏览';
        exportButton.onclick = exportViewedGames;

        const importButton = document.createElement('button');
        importButton.textContent = '导入已浏览';
        importButton.onclick = importViewedGames;

        const openAllButton = document.createElement('button');
        openAllButton.textContent = '打开所有未浏览';
        openAllButton.onclick = openAllUnviewed;

        const misclickPreventionToggle = document.createElement('button');
        misclickPreventionToggle.textContent = '防误触模式:';
        misclickPreventionToggle.onclick = toggleMisclickPrevention;

        const misclickPreventionStatus = document.createElement('span');
        misclickPreventionStatus.id = 'misclickPreventionStatus';
        misclickPreventionStatus.textContent = misclickPrevention ? '开启' : '关闭';

        const toggleInstructions = document.createElement('span');
        toggleInstructions.id = 'toggleInstructions';
        toggleInstructions.textContent = '显示使用说明';
        toggleInstructions.onclick = () => {
            const instructions = document.getElementById('instructions');
            if (instructions.style.display === 'none') {
                instructions.style.display = 'block';
                toggleInstructions.textContent = '隐藏使用说明';
            } else {
                instructions.style.display = 'none';
                toggleInstructions.textContent = '显示使用说明';
            }
        };

        const instructions = document.createElement('div');
        instructions.id = 'instructions';
        instructions.innerHTML = `
            <p>使用说明:</p>
            <ul>
                <li>Ctrl+左键点击:打开Steam商店页面并标记为已浏览</li>
                <li>Alt+左键点击:切换已浏览/未浏览状态</li>
                <li>刷新标记:手动刷新已浏览标记</li>
                <li>导出已浏览:下载已浏览游戏的数据文件</li>
                <li>导入已浏览:上传之前导出的数据文件</li>
                <li>打开所有未浏览:在新标签页中打开所有未浏览的游戏</li>
                <li>防误触模式:开启时,只有脚本定义的操作生效</li>
            </ul>
        `;
        instructions.style.display = 'none';

        controlPanel.appendChild(refreshButton);
        controlPanel.appendChild(exportButton);
        controlPanel.appendChild(importButton);
        controlPanel.appendChild(openAllButton);
        controlPanel.appendChild(document.createElement('br'));
        controlPanel.appendChild(misclickPreventionToggle);
        controlPanel.appendChild(misclickPreventionStatus);
        controlPanel.appendChild(document.createElement('br'));
        controlPanel.appendChild(toggleInstructions);
        controlPanel.appendChild(instructions);

        controlsContainer.appendChild(collapseToggle);
        controlsContainer.appendChild(controlPanel);

        document.body.appendChild(controlsContainer);
    }

    // Main click event listener
    document.addEventListener('click', function(event) {
        if (misclickPrevention && !event.ctrlKey && !event.altKey) {
            return; // If misclick prevention is on and neither Ctrl nor Alt is pressed, do nothing
        }

        const img = event.target.closest('img');
        if (img) {
            const appId = getAppIdFromUrl(img.src);
            if (appId) {
                const container = img.closest('.item-container') || img.parentElement;
                if (event.ctrlKey) {
                    event.preventDefault();
                    event.stopPropagation();
                    const steamAppUrl = `https://store.steampowered.com/app/${appId}`;
                    window.open(steamAppUrl, '_blank', 'noopener,noreferrer');
                    toggleViewedStatus(appId, container);
                } else if (event.altKey) {
                    event.preventDefault();
                    event.stopPropagation();
                    toggleViewedStatus(appId, container);
                }
            }
        }
    }, true);

    // Use MutationObserver to handle dynamically loaded content
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.type === 'childList') {
                applyViewedIndicators();
            }
        });
    });

    observer.observe(document.body, { childList: true, subtree: true });

    // Initialize
    applyViewedIndicators();
    addScriptControls();

    // Set up adaptive auto-refresh
    let startTime = Date.now();
    let intervalId;

    function adaptiveRefresh() {
        const elapsedTime = Date.now() - startTime;
        if (elapsedTime < 60000) {  // First minute
            applyViewedIndicators();
            intervalId = setTimeout(adaptiveRefresh, 10000);  // Every 10 seconds
        } else {
            applyViewedIndicators();
            intervalId = setInterval(applyViewedIndicators, 30000);  // Every 30 seconds
        }
    }

    adaptiveRefresh();
})();