Torn Logs Enhancer

Enhances the Torn Log page by allowing the user to select and copy logs with a total cost calculation. Currently supports the Bounty Place category. More for future work.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Torn Logs Enhancer
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Enhances the Torn Log page by allowing the user to select and copy logs with a total cost calculation. Currently supports the Bounty Place category. More for future work.
// @author       Hesper [2924630]
// @match        https://www.torn.com/page.php?sid=log*
// @grant        GM_setClipboard
// @license      MIT
// ==/UserScript==
(function () {
    'use strict';

    let currentLogType = null; 

    function initializeForLogType(logType) {
        if (logType === '6700') {
            console.log('Initializing for bounty place logs...');
            setupBountyPlaceLogs();
        }
        // Add more log types here in the future
    }

    function setupBountyPlaceLogs() {
        const style = document.createElement('style');
        style.textContent = `
            #floating-torn-log-enhancer {
                position: fixed;
                top: 300px;
                right: 0px;
                background-color: #333;
                color: #fff;
                border: 1px solid #ccc;
                padding: 5px;
                z-index: 1000;
                width: 30px;
                height: 25px;
                font-size: 1em;
                border-radius: 5px 0px 0px 5px;
                overflow: hidden;
                transition: all 0.3s ease;
                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
                cursor: pointer;
            }

            #floating-torn-log-enhancer #collapsed-view {
                display: flex;
                align-items: center;
                justify-content: space-between;
                height: 100%;
                font-size: 1em;
            }

            #floating-torn-log-enhancer #expanded-view {
                display: none;
                flex-direction: column;
                gap: 2px;
            }

            #floating-torn-log-enhancer button {
                margin-top: 10px;
                padding: 5px 10px;
                border: 1px solid #fff;
                background-color: #444;
                color: #fff;
                border-radius: 3px;
                cursor: pointer;
            }


            #log-list {
                margin-top: 10px;
                overflow-y: auto;
                max-height: 150px;
                scrollbar-width: thin; /* For Firefox */
                scrollbar-color: #fff #333; /* For Firefox */
            }

            /* For WebKit browsers (Chrome, Edge, Safari) */
            #log-list::-webkit-scrollbar {
                width: 8px;
            }

            #log-list::-webkit-scrollbar-track {
                background: #333;
            }

            #log-list::-webkit-scrollbar-thumb {
                background: #fff;
                border-radius: 4px;
            }

            #log-list::-webkit-scrollbar-thumb:hover {
                background: #ccc;
            }
        `;
        document.head.appendChild(style);

        const floatingDiv = document.createElement('div');
        floatingDiv.id = 'floating-torn-log-enhancer';
        floatingDiv.innerHTML = `
            <div id="collapsed-view">
                <span>◀</span> <span id="selected-count-collapsed">0</span>
            </div>
            <div id="expanded-view">
                <div>
                    <strong>Selected Logs:</strong> <span id="selected-count">0</span>
                </div>
                <div style="display: flex; justify-content: space-between; align-items: center;">
                    <span><strong>Total:</strong> $<span id="total-bounties">0</span></span>
                    <span>▶</span>
                </div>
                <div>
                    <strong>Category:</strong> Bounty place
                </div>
                <button id="copy-button">Copy Logs</button>
                <div id="log-list"></div>
            </div>
        `;
        document.body.appendChild(floatingDiv);

        const collapsedView = document.getElementById('collapsed-view');
        const expandedView = document.getElementById('expanded-view');
        const selectedCountCollapsed = document.getElementById('selected-count-collapsed');
        const selectedCount = document.getElementById('selected-count');
        const totalBounties = document.getElementById('total-bounties');
        const copyButton = document.getElementById('copy-button');
        const logList = document.getElementById('log-list');

        floatingDiv.addEventListener('click', () => {
            if (expandedView.style.display === 'none') {
                expandedView.style.display = 'flex';
                collapsedView.style.display = 'none';
                floatingDiv.style.width = '200px'; // Expand width
                floatingDiv.style.height = 'auto'; // Adjust height
            } else {
                expandedView.style.display = 'none';
                collapsedView.style.display = 'flex';
                floatingDiv.style.width = '30px'; // Collapse width
                floatingDiv.style.height = '25px'; // Collapse height
            }
        });

        function updateCollapsedViewCount() {
            selectedCountCollapsed.textContent = selectedLogs.length;
        }

        function updateLogList() {
            const sortedLogs = selectedLogs.sort((a, b) => {
                const dateRegex = /(\d{2}:\d{2}:\d{2}) - (\d{2}\/\d{2}\/\d{2})/;
                const dateA = new Date(a.match(dateRegex)[2] + ' ' + a.match(dateRegex)[1]);
                const dateB = new Date(b.match(dateRegex)[2] + ' ' + b.match(dateRegex)[1]);
                return dateB - dateA; // Descending order
            });

            logList.innerHTML = sortedLogs.map(log => `<div>${log}</div>`).join('');
            updateCollapsedViewCount();
        }

        let selectedLogs = [];
        let total = 0;

        function addCheckboxesToLogs(logEntries) {
            logEntries.forEach((row) => {
                const timeElement = row.querySelector('.time___CjMrZ');
                const logTextElement = row.querySelector('.log-text');
                if (!timeElement || !logTextElement || timeElement.querySelector('input[type="checkbox"]')) return;

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.style.marginLeft = '10px';
                checkbox.addEventListener('change', () => {
                    const timeText = timeElement.textContent.trim();
                    const logText = logTextElement.textContent.trim();
                    const costMatch = logText.match(/cost of \$([\d,]+)/);

                    if (checkbox.checked) {
                        selectedLogs.push(`${timeText} ${logText}`);
                        if (costMatch) {
                            total += parseInt(costMatch[1].replace(/,/g, ''), 10);
                        }
                    } else {
                        selectedLogs = selectedLogs.filter(log => log !== `${timeText} ${logText}`);
                        if (costMatch) {
                            total -= parseInt(costMatch[1].replace(/,/g, ''), 10);
                        }
                    }

                    selectedCount.textContent = selectedLogs.length;
                    totalBounties.textContent = total.toLocaleString();
                    updateLogList();
                });

                timeElement.appendChild(checkbox);
            });
        }

        // Copy logs to clipboard
        copyButton.addEventListener('click', () => {
            const logsText = selectedLogs.join('\n') + `\n\nTotal: $${total.toLocaleString()}`;
            GM_setClipboard(logsText);
            alert('Logs copied to clipboard!');
        });

        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    const newRows = Array.from(mutation.addedNodes).filter(node => node.tagName === 'TR');
                    addCheckboxesToLogs(newRows);
                }
            });
        });

        const waitForTable = setInterval(() => {
            const logTable = document.querySelector('table');
            if (logTable) {
                observer.observe(logTable, { childList: true, subtree: true });
                const initialLogEntries = logTable.querySelectorAll('tr');
                addCheckboxesToLogs(initialLogEntries);
                clearInterval(waitForTable);
            }
        }, 500);
    }

    function monitorUrlChanges() {
        let previousUrl = null;

        const checkUrl = () => {
            const currentUrl = window.location.href;

            if (currentUrl !== previousUrl) {
                previousUrl = currentUrl;

                const urlParams = new URLSearchParams(window.location.search);
                const logType = urlParams.get('log');

                if (logType && logType !== currentLogType) {
                    const existingDiv = document.getElementById('floating-torn-log-enhancer');
                    if (existingDiv) {
                        existingDiv.remove();
                    }
                    currentLogType = logType;
                    initializeForLogType(logType);
                }
            }
        };

        // Initial check then monitor for changes
        checkUrl();
        setInterval(checkUrl, 500);
    }

    monitorUrlChanges();
})();