Greasy Fork is available in English.

Torn: Activity Log filter, export & reverse

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

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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);
    }
}