Torn: Activity Log filter, export & reverse

Export only visually filtered items and reverse the layout view on the Torn Log Page

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Torn: Activity Log filter, export & reverse
// @namespace    lugburz.activity_log_filter_export
// @version      0.6.0
// @description  Export only visually filtered items and reverse the layout view on the Torn Log Page
// @author       Lugburz & Community
// @match        https://www.torn.com/*
// @run-at       document-start
// @grant        unsafeWindow
// @grant        GM_registerMenuCommand
// ==/UserScript==

var exportAll = false;
var logpageLogs = [];

function addInterceptor() {
    if (unsafeWindow.__logInterceptorAdded) return;
    unsafeWindow.__logInterceptorAdded = true;

    const originalFetch = unsafeWindow.fetch;
    unsafeWindow.fetch = async function(...args) {
        const response = await originalFetch.apply(this, args);
        const url = args[0] ? (typeof args[0] === 'string' ? args[0] : args[0].url) : '';

        if (url && url.indexOf('page.php?sid=activityLogUserData') > -1) {
            try {
                const text = await response.clone().text();
                const json = JSON.parse(text);
                if (json && json.log) {
                    for (const l of json.log) {
                        if (!logpageLogs.some(existing => existing.ID === l.ID)) {
                            logpageLogs.push(l);
                        }
                    }
                    setTimeout(addScriptButtons, 300);
                }
            } catch (e) { console.error(e); }
        }
        return response;
    };
}

function downloadCsv(csv) {
    const myblob = new Blob([csv], {type: 'text/csv;charset=utf-8;'});
    const myurl = window.URL.createObjectURL(myblob);
    const ancorTag = document.createElement('a');
    ancorTag.href = myurl;
    ancorTag.download = 'filtered_activity_log.csv';
    document.body.appendChild(ancorTag);
    ancorTag.click();
    document.body.removeChild(ancorTag);
}

function scrapeVisiblePageLogs() {
    if (typeof $ === 'undefined') return '';
    let csv = 'time,category,text\n';

    const logRows = $('#activity-log-root').find('tr, [class*=logRow___], [class*=row___]');

    logRows.each((index, el) => {
        if ($(el).is(':hidden') || $(el).attr('class')?.includes('title')) return;

        const timeEl = $(el).find('td[class^=time], [class*=time___]');
        const textEl = $(el).find('span.log-text, [class*=text___]');

        if (textEl.length > 0) {
            let id = textEl.attr('id') ? textEl.attr('id').replace('text-', '') : null;
            let category = "Filtered Log";

            if (id) {
                const networkMatch = logpageLogs.find(log => log.ID === id);
                if (networkMatch) {
                    category = networkMatch.category;
                }
            }

            const timeText = timeEl.text().trim() || "N/A";
            const logText = textEl.text().trim().replace(/\n/g, ' ').replace(/"/g, '""');

            csv += [`"${timeText}"`, category, `"${logText}"`].join(',') + '\n';
        }
    });
    return csv;
}

function exportActivityLog() {
    if (!exportAll) {
        const scrapedCsv = scrapeVisiblePageLogs();
        const rowCount = scrapedCsv.split('\n').length - 2;

        if (rowCount > 0) {
            downloadCsv(scrapedCsv);
        } else {
            alert("No visible logs found. Please make sure you are on the Log page and logs are currently displayed.");
        }
        return;
    }

    if (logpageLogs.length === 0) {
        alert("Please wait for the Log Page data to finish loading.");
        return;
    }
    let csv = 'time,category,text\n';
    for (const l of logpageLogs) {
        const time = new Date(l.time * 1000);
        csv += [`"${time.toUTCString()}"`, l.category, `"${l.text.replace(/\n/g, ' ').replace(/"/g, '""')}"`].join(',') + '\n';
    }
    downloadCsv(csv);
}

function reverseLogView() {
    if (typeof $ === 'undefined') return;

    // Find the container holding the rows (works for both old tables and modern list dividers)
    const container = $('#activity-log-root').find('tbody, [class*=logWrapper___]');

    if (container.length > 0) {
        const rows = container.children('tr, [class*=logRow___], [class*=row___]').not('[class*=title]');
        container.append(rows.get().reverse());
    }
}

function addScriptButtons() {
    if (typeof $ === 'undefined' || $('#exportLogsBtn').length > 0) return;

    let buttonClass = 'button___37oQ0';
    let wrapper = $('#activity-log-root').find('[class*=buttonsWrapper], [class*=filterWrapper], [class*=container___]');

    if (!wrapper.length) {
        wrapper = $('#activity-log-root').find('h4').first();
    }

    // Export Button
    const exportBtn = `<button id="exportLogsBtn" class="${buttonClass}" style="line-height: 24px; padding: 0 12px; background-color: var(--default-blue-color, #337ab7); color: #fff; border: none; border-radius: 4px; margin-left: 10px; cursor: pointer;" title="Export filtered logs as CSV" type="button">Export Filtered CSV</button>`;

    // Reverse Button
    const reverseBtn = `<button id="reverseLogsBtn" class="${buttonClass}" style="line-height: 24px; padding: 0 12px; background-color: #4caf50; color: #fff; border: none; border-radius: 4px; margin-left: 10px; cursor: pointer;" title="Reverse log order layout" type="button">Reverse View</button>`;

    wrapper.append(exportBtn);
    wrapper.append(reverseBtn);

    $('#exportLogsBtn').on('click', () => {
        exportActivityLog();
    });

    $('#reverseLogsBtn').on('click', () => {
        reverseLogView();
    });
}

addInterceptor();

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
} else {
    init();
}

function init() {
    if (typeof $ !== 'undefined') {
        GM_registerMenuCommand('Export Filtered Logs', exportActivityLog);
        GM_registerMenuCommand('Reverse Log View', reverseLogView);

        setInterval(() => {
            if ($('#activity-log-root').length && !$('#exportLogsBtn').length) {
                addScriptButtons();
            }
        }, 1000);
    } else {
        setTimeout(init, 300);
    }
}