SHA1 Calculator

Adds the ability to handle hash validator for SHA1 files based on Script by Sak32009

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name             SHA1 Calculator
// @namespace   SWScripts
// @version          v1.1
// @description    Adds the ability to handle hash validator for SHA1 files based on Script by Sak32009
// @license          MIT
// @author           BN_LOS
// @match           https://steamdb.info/depot/*/?show_hashes
// @grant             none
// ==/UserScript==

(function() {
    'use strict';

    const addStyle = (css) => {
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    };

    const showToast = (message, type = 'info') => {
        const toast = document.createElement('div');
        toast.classList.add('toast', `alert-${type}`);
        toast.textContent = message;
        document.body.appendChild(toast);
        setTimeout(() => toast.remove(), 3000);
    };

    addStyle(`
        .toast { position: fixed; top: 1rem; right: 1rem; padding: 1rem; border-radius: 0.375rem; opacity: 0; transition: opacity 0.3s; z-index: 10000; }
        .toast.alert-info { background-color: #e2f3f5; color: #0c5460; }
        .toast.alert-error { background-color: #f8d7da; color: #721c24; }
        .status-summary { display: flex; gap: 1rem; margin-top: 1rem; font-size: 1rem; color: #555; }
        #notFoundFilesContainer { margin-top: 1rem; }
    `);

    const fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.id = 'fileInput';
    fileInput.accept = '.sha1';
    fileInput.style.display = 'none';
    document.body.appendChild(fileInput);

    const loadButton = document.createElement('button');
    loadButton.className = 'btn btn-outline mb-4';
    loadButton.textContent = 'Load .SHA1';
    const dtSearchDiv = document.querySelector('.dt-search');
    dtSearchDiv?.insertAdjacentElement('beforebegin', loadButton);

    const statusSummary = document.createElement('div');
    statusSummary.className = 'status-summary';
    statusSummary.innerHTML = '<span>Valid: <span id="validCount">0</span></span><span>Invalid: <span id="invalidCount">0</span></span><span>Not Found: <span id="notFoundCount">0</span></span><span>Total: <span id="totalCount">0</span></span>';
    const dataTableDisplayDiv = document.querySelector('.dataTable_display');
    dataTableDisplayDiv?.insertAdjacentElement('afterend', statusSummary);

    let sha1Data = [];

    const table = document.querySelector('#DataTables_Table_0');
    const th = document.createElement('th');
    th.textContent = 'Status';
    table.querySelector('thead tr')?.appendChild(th);

    const dataTableLengthSelect = document.getElementById('dt-length-0');
    if (dataTableLengthSelect) {
        dataTableLengthSelect.value = '-1';
        dataTableLengthSelect.dispatchEvent(new Event('change'));
    }

    loadButton.addEventListener('click', () => fileInput.click());

    fileInput.addEventListener('change', async (event) => {
        const file = event.target.files[0];
        if (!file || !file.name.endsWith('.sha1')) {
            showToast('Invalid file. Please select a .sha1 file.', 'error');
            return;
        }

        try {
            const content = await file.text();
            sha1Data = [];
            clearTableStatus();
            clearNotInDepotTable();
            updateStatusSummary(0, 0, 0);
            updateDownloadButton([]);
            sha1Data = parseSHA1File(content);
            updateTable();
        } catch (error) {
            showToast('Error reading file.', 'error');
            console.error(error);
        }
    });

    const parseSHA1File = (content) => {
        return content.split('\n')
            .map(line => line.trim())
            .filter(line => line)
            .map(line => {
                const [hash, filename] = line.split(' *');
                return { hash, filename: filename.replace(/[\\\/]/g, '/').trim() };
            });
    };

    const clearTableStatus = () => {
        const rows = table.querySelectorAll('tbody tr');
        rows.forEach(row => {
            if (row.cells.length > 3) {
                row.deleteCell(3);
            }
        });
    };

    const clearNotInDepotTable = () => {
        const notInDepotTable = document.getElementById('DataTables_Table_2_wrapper');
        if (notInDepotTable) {
            notInDepotTable.remove();
        }
    };

    const createNotInDepotTable = (notFoundInPageFiles) => {
        const wrapper = document.getElementById('DataTables_Table_0_wrapper');
        if (!wrapper) return;

        const existingTable2 = document.getElementById('DataTables_Table_2_wrapper');
        if (existingTable2) existingTable2.remove();

        if (notFoundInPageFiles.length === 0) return;

        const table2Wrapper = document.createElement('div');
        table2Wrapper.id = 'DataTables_Table_2_wrapper';
        table2Wrapper.innerHTML = `<h3>Not in Depot</h3>`;

        const table2 = document.createElement('table');
        table2.className = 'table table-bordered table-fixed dataTable mt-4';
        table2.innerHTML = `
            <thead><tr><th>Filename</th><th>Hashcode</th></tr></thead>
            <tbody>${notFoundInPageFiles.map(file => `<tr><td>${file.filename}</td><td>${file.hash}</td></tr>`).join('')}</tbody>
        `;

        table2Wrapper.appendChild(table2);
        wrapper.insertAdjacentElement('afterend', table2Wrapper);
    };


    const updateTable = () => {
        clearTableStatus();
        let validCount = 0;
        let invalidCount = 0;
        let notFoundCount = 0;
        const notFoundFiles = [];

        const rows = table.querySelectorAll('tbody tr');
        rows.forEach(row => {
            const filenameCell = row.cells[0];
            const hashCell = row.cells[1];
            const filename = filenameCell.textContent.trim().replace(/[\\\/]/g, '/');
            const pageHash = hashCell.textContent.replace('***', '').trim();

            const statusCell = document.createElement('td');

            if (!pageHash && filename) {
                statusCell.textContent = 'Folder';
                statusCell.className = 'text-blue-500';
                row.appendChild(statusCell);
                return;
            }

            const sha1Entry = sha1Data.find(entry => entry.filename === filename);

            if (sha1Entry) {
                const startHash = sha1Entry.hash.slice(0, 10);
                const endHash = sha1Entry.hash.slice(-10);
                const pageStartHash = pageHash.slice(0, 10);
                const pageEndHash = pageHash.slice(-10);

                if (startHash === pageStartHash && endHash === pageEndHash) {
                    statusCell.textContent = 'Matched';
                    statusCell.className = 'text-green-500';
                    validCount++;
                } else {
                    statusCell.textContent = 'Unmatched';
                    statusCell.className = 'text-red-500';
                    invalidCount++;
                }
            } else {
                statusCell.textContent = 'Not Found';
                statusCell.className = 'text-gray-500';
                notFoundFiles.push(filename);
                notFoundCount++;
            }

            row.appendChild(statusCell);
        });

        updateStatusSummary(validCount, invalidCount, notFoundCount);
        updateDownloadButton(notFoundFiles);

        const notFoundInPageFiles = sha1Data.filter(sha1File => {
            return !Array.from(table.querySelectorAll('tbody tr td:first-child')).some(cell => {
                return cell.textContent.trim().replace(/[\\\/]/g, '/') === sha1File.filename;
            });
        });

        createNotInDepotTable(notFoundInPageFiles);
    };

    const updateStatusSummary = (valid, invalid, notFound) => {
        document.getElementById('validCount').textContent = valid;
        document.getElementById('invalidCount').textContent = invalid;
        document.getElementById('notFoundCount').textContent = notFound;
        document.getElementById('totalCount').textContent = valid + invalid + notFound;
    };

    const updateDownloadButton = (notFoundFiles) => {
        const buttonContainer = document.getElementById('notFoundFilesContainer');
        if (!notFoundFiles.length) {
            if (buttonContainer) {
                buttonContainer.remove();
            }
        } else {
            if (!buttonContainer) {
                const container = document.createElement('div');
                container.id = 'notFoundFilesContainer';
                container.innerHTML = `<button class="btn btn-outline" id="downloadButton">Download Missing Files</button>`;
                document.body.appendChild(container);

                document.getElementById('downloadButton').addEventListener('click', downloadMissingFiles);
            }
        }
    };

    const downloadMissingFiles = () => {
        const blob = new Blob([notFoundFiles.join('\n')], { type: 'text/plain' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'missing_files.sha1';
        a.click();
        URL.revokeObjectURL(url);
    };

})();