hololyzer 排序工具

让 hololyzer 的超级留言清单支持姓名排序功能

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name hololyzer Sort Tool
// @name:zh hololyzer 排序工具
// @name:zh-TW hololyzer 排序工具
// @name:zh-HK hololyzer 排序工具
// @name:zh-CN hololyzer 排序工具
// @name:zh-SG hololyzer 排序工具
// @name:en hololyzer Sort Tool
// @name:ja hololyzerのソートツール
// @name:ko hololyzer 정렬 도구
// @name:fr Outil de tri hololyzer
// @name:es Herramienta de ordenación hololyzer
// @name:ar أداة ترتيب هولوليزر
// @name:de hololyzer-Sortierwerkzeug
// @name:hi होलोलाइज़र सॉर्ट टूल
// @name:ru Инструмент сортировки hololyzer
// @name:pt Ferramenta de classificação hololyzer
// @name:sw Chombo cha utaratibu wa hololyzer
// @name:sr Alat za sortiranje hololajzera
// @name:hr Alat za sortiranje hololajzera
// @name:it Strumento di ordinamento hololyzer
// @name:ms Alat penyusunan hololyzer
// @name:id Alat pengurutan hololyzer
// @name:nl hololyzer sorteertool
// @name:fa ابزار مرتب سازی hololyzer
// @namespace          https://github.com/kevin823lin
// @version            0.3
// @description Add support for sorting hololyzer's super chat list by name.
// @description:zh 讓 hololyzer 的超級留言清單支援姓名排序功能
// @description:zh-TW 讓 hololyzer 的超級留言清單支援姓名排序功能
// @description:zh-HK 讓 hololyzer 的超級留言清單支援姓名排序功能
// @description:zh-CN 让 hololyzer 的超级留言清单支持姓名排序功能
// @description:zh-SG 让 hololyzer 的超级留言清单支持姓名排序功能
// @description:en Add support for sorting hololyzer's super chat list by name.
// @description:ja hololyzerのスーパーチャットリストを名前でソートする機能を追加する
// @description:ko hololyzer의 슈퍼 챗 목록을 이름별로 정렬하는 기능
// @description:fr Ajouter la prise en charge du tri de la liste de super chats hololyzer par nom.
// @description:es Agregar soporte para ordenar la lista de super chats de hololyzer por nombre.
// @description:ar إضافة دعم لترتيب قائمة الدردشة الخارقة لـ hololyzer حسب الاسم.
// @description:de Unterstützung zum Sortieren der Super-Chat-Liste von hololyzer nach Namen hinzufügen.
// @description:hi नाम द्वारा होलोलाइजर के सुपर चैट सूची को सॉर्ट करने के लिए समर्थन जोड़ें।
// @description:ru Добавить поддержку сортировки списка супер-чатов hololyzer по имени.
// @description:pt Adicionar suporte para classificar a lista de superchats do hololyzer por nome.
// @description:sw Ongeza msaada wa kupanga orodha ya gumzo kuu la hololyzer kwa jina.
// @description:sr Dodajte podršku za sortiranje super-čet liste hololyzer-a po imenu.
// @description:hr Dodajte podršku za sortiranje super chat liste hololyzer po imenu.
// @description:it Aggiungere il supporto per ordinare l'elenco dei super chat di hololyzer per nome.
// @description:ms Tambah sokongan untuk mengurutkan senarai sembang super hololyzer mengikut nama.
// @description:id Tambahkan dukungan untuk mengurutkan daftar super chat hololyzer berdasarkan nama.
// @description:nl Voeg ondersteuning toe voor het sorteren van de superchatlijst van hololyzer op naam.
// @description:fa پشتیبانی از مرتب سازی لیست چت های فوق العاده hololyzer بر اساس نام.
// @author             kevin823lin
// @match              https://www.hololyzer.net/*/superchat/*
// @icon               https://www.google.com/s2/favicons?domain=hololyzer.net
// @grant              none
// @date               2023-03-08
// ==/UserScript==
/*Translate and optimize with ChatGPT*/

(function () {
    'use strict';

    // Your code here...
    init();

    function i18n(name, param) {
        const lang = navigator.appName == "Netscape" ? navigator.language : navigator.userLanguage;
        let config = {};
        switch (lang) {
            case "zh":
            case "zh-TW":
            case "zh-HK":
                config = {
                    sortByTime: "時間排序",
                    sortByName: "姓名排序",
                    copyTable: "複製表格",
                    copyFailed: "複製失敗"
                };
                break;
            case "zh-CN":
            case "zh-SG":
                config = {
                    sortByTime: "时间排序",
                    sortByName: "姓名排序",
                    copyTable: "复制表格",
                    copyFailed: "复制失败"
                };
                break;
            case "en":
                config = {
                    sortByTime: "Sort by time",
                    sortByName: "Sort by name",
                    copyTable: "Copy table",
                    copyFailed: "Copy failed"
                };
                break;
            case "ja":
                config = {
                    sortByTime: "時間で並び替え",
                    sortByName: "名前で並び替え",
                    copyTable: "表をコピーする",
                    copyFailed: "コピーに失敗しました"
                };
                break;

            case "ko":
                config = {
                    sortByTime: "시간순 정렬",
                    sortByName: "이름순 정렬",
                    copyTable: "표 복사하기",
                    copyFailed: "복사 실패"
                };
                break;
            case "fr":
                config = {
                    sortByTime: "Trier par temps",
                    sortByName: "Trier par nom",
                    copyTable: "Copier le tableau",
                    copyFailed: "Copie échouée"
                };
                break;
            case "es":
                config = {
                    sortByTime: "Ordenar por tiempo",
                    sortByName: "Ordenar por nombre",
                    copyTable: "Copiar tabla",
                    copyFailed: "Error al copiar"
                };
                break;
            case "ar":
                config = {
                    sortByTime: "ترتيب حسب الوقت",
                    sortByName: "ترتيب حسب الاسم",
                    copyTable: "نسخ الجدول",
                    copyFailed: "فشل النسخ"
                };
                break;
            case "de":
                config = {
                    sortByTime: "Nach Zeit sortieren",
                    sortByName: "Nach Name sortieren",
                    copyTable: "Tabelle kopieren",
                    copyFailed: "Kopieren fehlgeschlagen"
                };
                break;
            case "hi":
                config = {
                    sortByTime: "समय के अनुसार क्रमबद्ध करें",
                    sortByName: "नाम के अनुसार क्रमबद्ध करें",
                    copyTable: "टेबल कॉपी करें",
                    copyFailed: "कॉपी असफल"
                };
                break;
            case "ru":
                config = {
                    sortByTime: "Сортировать по времени",
                    sortByName: "Сортировать по имени",
                    copyTable: "Копировать таблицу",
                    copyFailed: "Ошибка копирования"
                };
                break;
            case "pt":
                config = {
                    sortByTime: "Ordenar por hora",
                    sortByName: "Ordenar por nome",
                    copyTable: "Copiar tabela",
                    copyFailed: "Falha ao copiar"
                };
                break;
            case "sw":
                config = {
                    sortByTime: "Sort by Time",
                    sortByName: "Sort by Name",
                    copyTable: "Copy Table",
                    copyFailed: "Kushindwa nakala"
                };
                break;
            case "sr":
            case "hr":
            case "it":
                config = {
                    sortByTime: "Sortiraj po vremenu",
                    sortByName: "Sortiraj po imenu",
                    copyTable: "Kopiraj tabelu",
                    copyFailed: "Kopiranje nije uspelo"
                };
                break;
            case "ms":
            case "id":
                config = {
                    sortByTime: "Susun mengikut masa",
                    sortByName: "Susun mengikut nama",
                    copyTable: "Salin Jadual",
                    copyFailed: "Salinan gagal"
                };
                break;
            case "nl":
                config = {
                    sortByTime: "Sorteren op tijd",
                    sortByName: "Sorteren op naam",
                    copyTable: "Tabel kopiëren",
                    copyFailed: "Kopiëren mislukt"
                };
                break;
            case "fa":
                config = {
                    sortByTime: "مرتب سازی بر اساس زمان",
                    sortByName: "مرتب سازی بر اساس نام",
                    copyTable: "رونوشت جدول",
                    copyFailed: "کپی ناموفق"
                };
                break;
            default:
                config = {
                    sortByTime: "Sort by time",
                    sortByName: "Sort by name",
                    copyTable: "Copy table",
                    copyFailed: "Copy failed"
                };
                break;
        }
        return config[name] ? config[name].replace("#t#", param) : name;
    }

    async function init() {
        document.body.dataset.sortBy = "time";
        insertButton();
        await waitElementsLoaded('table[border]');
        const { tbody, newTbody } = getTbodyAndFakeTbody();
        insertNoByBame(newTbody);
        replaceTbody(tbody, newTbody);
    }

    function insertButton() {
        const sortByTimeBtn = document.createElement('button');
        const sortByNameBtn = document.createElement('button');
        const copyTableBtn = document.createElement('button');
        sortByTimeBtn.innerText = i18n('sortByTime');
        sortByNameBtn.innerText = i18n('sortByName');
        copyTableBtn.innerText = i18n('copyTable');
        sortByTimeBtn.addEventListener("click", function () {
            if (document.body.dataset.sortBy !== "time") {
                document.body.dataset.sortBy = "time";
                main("time");
            }
        });
        sortByNameBtn.addEventListener("click", async function () {
            if ((document.body.dataset.sortBy) !== "name") {
                document.body.dataset.sortBy = "name";
                main("name");
            }
        });
        copyTableBtn.addEventListener("click", function () {
            copyTable();
        });
        document.body.insertAdjacentElement('afterbegin', copyTableBtn);
        document.body.insertAdjacentElement('afterbegin', sortByNameBtn);
        document.body.insertAdjacentElement('afterbegin', sortByTimeBtn);
    }

    function insertNoByBame(tbody) {
        const ths = [...tbody.querySelectorAll("th")];
        const trs = [...tbody.querySelectorAll("tr:has(>td):nth-child(odd)")];

        const insertIndex = ((index) => index === -1 ? 0 : index)(ths.findIndex(th => /^(n|N)o$/.test(th.innerText)));
        const sortIndex = ((index) => index === -1 ? 6 : index)(ths.findIndex(th => /name|チャンネル名/.test(th.innerText)));

        const sortedTrs = sortTrs(trs, sortIndex);

        let count = 0;
        const countList = sortedTrs.map((tr, i) => {
            const preName = sortedTrs[i - 1]?.element.children[sortIndex]?.childNodes[0].textContent;
            const name = tr.element.children[sortIndex]?.childNodes[0].textContent;
            return preName !== name ? ++count : count;
        });

        sortedTrs.forEach((tr, i) => {
            const insertEle = tr.element.insertCell(insertIndex + 1);
            insertEle.innerText = countList[i];
            insertEle.setAttribute('rowspan', 2);
            insertEle.style.textAlign = "right";
        });

        const parentRow = tbody.querySelector("tr");
        const childrenEle = parentRow.children[insertIndex + 1];
        const insertEle = document.createElement("th");
        insertEle.innerText = "no by name";
        insertEle.setAttribute('rowspan', 2);
        parentRow.insertBefore(insertEle, childrenEle);
    }

    async function main(sortBy) {
        const { tbody, newTbody } = getTbodyAndFakeTbody();
        sort(newTbody, sortBy);
        replaceTbody(tbody, newTbody);
    }

    function sort(tbody, sortBy) {
        const trs = [...tbody.querySelectorAll("tr:has(>td):nth-child(odd)")];

        let sortIndex;

        switch (sortBy) {
            case "time":
                sortIndex = ((index) => index === -1 ? 0 : index)([...tbody.querySelectorAll('th')].findIndex(th => /^(n|N)o$/.test(th.innerText)));
                break;
            case "name":
                sortIndex = ((index) => index === -1 ? 1 : index)([...tbody.querySelectorAll('th')].findIndex(th => /no by name/.test(th.innerText)));
                break;
        }
        const sortedTrs = sortTrs(trs, sortIndex, true);

        sortedTrs.forEach(item => { tbody.appendChild(item.element); tbody.appendChild(item.nextElementSibling) });
    }

    function sortTrs(trs, sortIndex, num = false) {
        return trs.map(tr => ({
            element: tr,
            previousElementSibling: tr.previousElementSibling,
            nextElementSibling: tr.nextElementSibling,
            key: tr.children[sortIndex].innerText
        })).sort((a, b) => num ? (a.key - b.key) : a.key.localeCompare(b.key, 'ja'));
    }

    function getTbodyAndFakeTbody() {
        const tbody = document.querySelector('table[border] > tbody');

        const clonedTbody = tbody.cloneNode(true);
        const fragment = new DocumentFragment();
        fragment.append(clonedTbody);

        const newTbody = fragment.querySelector('tbody')

        return { tbody, newTbody };
    }

    function replaceTbody(tbody, newTbody) {
        tbody.replaceWith(newTbody);
    }

    function copyTable() {
        copyElement(document.querySelector('table[border] > tbody'));
    }

    function copyElement(ele) {
        if (document.createRange && window.getSelection) {
            const sel = window.getSelection();
            const oldRange = Array.from({ length: sel.rangeCount }, (_, i) => sel.getRangeAt(i));
            const copyRange = document.createRange();
            sel.removeAllRanges();
            try {
                copyRange.selectNode(ele);
                sel.addRange(copyRange);
            } catch (e) {
                copyRange.selectNodeContents(ele);
                sel.addRange(copyRange);
            }
            navigator.clipboard.writeText(sel.toString());
            sel.removeAllRanges();
            oldRange.forEach(range => { sel.addRange(range) });
        } else {
            alert(i18n('copyFailed'));
        }
    }

    function waitElementsLoaded(...eles) {
        return Promise.all(eles.map(ele => {
            return new Promise(async resolve => {
                while (!document.querySelector(ele)) {
                    await wait(100);
                }
                resolve();
            });
        }));
    }

    function wait(ms) {
        try {
            return new Promise(r => setTimeout(r, ms));
        } catch (e) {
            console.error(`wait: ${e}`);
        }
    }
})();