s0urce.io Target List Sorter

Adds sorting functionality to the target list in s0urce.io

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         s0urce.io Target List Sorter
// @namespace    s0urce.io Target List Sorter
// @version      0.1
// @description  Adds sorting functionality to the target list in s0urce.io
// @author       NoT BoT
// @match        https://s0urce.io/*
// @icon         https://s0urce.io/icons/s0urce.svg
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    let currentSort = {
        field: null,
        direction: 'asc'
    };

    function addSortingControls() {
        const existingControls = document.querySelector('.sort-controls');
        let style = document.getElementById("STYYYYYLES");
        if (!style) {
            style = document.createElement('style');
            style.id = "STYYYYYLES";
            style.innerText = `
#list > .wrapper:not(.npc-premium) {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-right: 10px
}

#list > .wrapper:not(.npc-premium) > div{
  order: 4;
}
#list > .wrapper:not(.npc-premium) > .icon.flag{
  order: 5;
}
#list > .wrapper:not(.npc-premium) >.badge{
  order: 2;
}
#list > .wrapper:not(.npc-premium) > img.icon[src*="premium.svg"] {
  order: 3;
  margin-left: auto;
}`;
            document.head.appendChild(style);
        }

        if (existingControls) return;

        const sortingControls = `
            <div class="sort-controls" style="display: flex; gap: 5px; margin-bottom: 5px; flex-wrap: wrap;">
                <div class="sort svelte-1cv9i3z" data-sort="username" style="padding: 4px 8px; border-radius: 2px; background-color: var(--color-darkgrey); cursor: pointer;">Name</div>
                <div class="sort svelte-1cv9i3z" data-sort="username-length" style="padding: 4px 8px; border-radius: 2px; background-color: var(--color-darkgrey); cursor: pointer;">Length</div>
                <div class="sort svelte-1cv9i3z" data-sort="level" style="padding: 4px 8px; border-radius: 2px; background-color: var(--color-darkgrey); cursor: pointer;">Lvl</div>
                <div class="sort svelte-1cv9i3z" data-sort="country" style="padding: 4px 8px; border-radius: 2px; background-color: var(--color-darkgrey); cursor: pointer;">Flag</div>
                <div class="sort-reset svelte-1cv9i3z" style="padding: 4px 8px; border-radius: 2px; background-color: var(--color-darkgrey); cursor: pointer;">Reset</div>
            </div>
        `;

        const targetWindow = document.querySelector('.window:has([src="icons/targetList.svg"])');
        if (!targetWindow) return;

        const listElement = targetWindow.querySelector('#list');
        if (!listElement) return;

        listElement.insertAdjacentHTML('beforebegin', sortingControls);

        targetWindow.querySelectorAll('.sort').forEach(button => {
            button.addEventListener('click', function () {
                const sortBy = this.getAttribute('data-sort');

                if (currentSort.field === sortBy) {
                    currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
                } else {
                    currentSort.field = sortBy;
                    currentSort.direction = 'asc';
                }

                sortTargetList(sortBy, currentSort.direction);

                localStorage.setItem('lastTargetSort', sortBy);
                localStorage.setItem('lastSortDirection', currentSort.direction);

                targetWindow.querySelectorAll('.sort, .sort-reset').forEach(btn => {
                    btn.style.backgroundColor = 'var(--color-darkgrey)';
                    btn.textContent = btn.textContent.replace(' ▲', '').replace(' ▼', '');
                });
                this.style.backgroundColor = 'var(--color-blue)';

                const directionIndicator = currentSort.direction === 'asc' ? ' ▲' : ' ▼';
                this.textContent = this.textContent.replace(' ▲', '').replace(' ▼', '') + directionIndicator;
            });
        });

        const resetButton = targetWindow.querySelector('.sort-reset');
        if (resetButton) {
            resetButton.addEventListener('click', function () {
                currentSort.field = null;
                currentSort.direction = 'asc';

                localStorage.removeItem('lastTargetSort');
                localStorage.removeItem('lastSortDirection');

                resetTargetList(targetWindow);

                targetWindow.querySelectorAll('.sort, .sort-reset').forEach(btn => {
                    btn.style.backgroundColor = 'var(--color-darkgrey)';
                    btn.textContent = btn.textContent.replace(' ▲', '').replace(' ▼', '');
                });

                this.style.backgroundColor = 'var(--color-blue)';
            });
        }

        const lastSort = localStorage.getItem('lastTargetSort');
        const lastDirection = localStorage.getItem('lastSortDirection') || 'asc';
        if (lastSort) {
            currentSort.field = lastSort;
            currentSort.direction = lastDirection;
            sortTargetList(lastSort, lastDirection);

            const activeButton = targetWindow.querySelector(`.sort[data-sort="${lastSort}"]`);
            if (activeButton) {
                activeButton.style.backgroundColor = 'var(--color-blue)';
                const directionIndicator = lastDirection === 'asc' ? ' ▲' : ' ▼';
                activeButton.textContent = activeButton.textContent.replace(' ▲', '').replace(' ▼', '') + directionIndicator;
            }
        }

        observeListChanges();
    }

    function resetTargetList(targetWindow) {
        const listElement = targetWindow.querySelector('#list');
        if (!listElement) return;

        const items = Array.from(listElement.querySelectorAll('.wrapper'));
        items.forEach(item => {
            item.style.order = '';
        });

        listElement.style.display = '';
        listElement.style.flexDirection = '';
        listElement.style.height = '';
        listElement.style.maxHeight = '';
        listElement.style.overflow = 'auto';
    }

    function sortTargetList(sortBy, direction = 'asc') {
        const targetWindow = document.querySelector('.window:has([src="icons/targetList.svg"])');
        if (!targetWindow) return;

        const listElement = targetWindow.querySelector('#list');
        if (!listElement) return;

        const scrollTop = listElement.scrollTop;

        let all = listElement.querySelectorAll('.wrapper');
        for (let i = 0; i < all.length; i++) {
            all[i].style.flexShrink = '0';
            all[i].style.paddingRight = '10px';
        }

        const items = Array.from(listElement.querySelectorAll('.wrapper:not(.npc, .npc-premium, [style="width: 100%; height: 1px; background-color: var(--color-lightgrey); margin: 15px 0px;"]'));

        items.forEach(item => {
            item.style.order = '';
        });

        const sortedItems = items.map((item, index) => ({
            item,
            index
        }));

        const measureElement = document.createElement('span');
        measureElement.style.visibility = 'hidden';
        measureElement.style.position = 'absolute';
        measureElement.style.whiteSpace = 'nowrap';
        document.body.appendChild(measureElement);

        function getTextWidth(text, element) {
            const usernameElement = element.querySelector('.username');
            if (usernameElement) {
                const styles = window.getComputedStyle(usernameElement);
                measureElement.style.font = styles.font;
                measureElement.style.fontSize = styles.fontSize;
                measureElement.style.fontFamily = styles.fontFamily;
                measureElement.style.fontWeight = styles.fontWeight;
            }

            measureElement.textContent = text;
            return measureElement.getBoundingClientRect().width;
        }

        sortedItems.sort((a, b) => {
            let result = 0;

            if (sortBy === 'username') {
                const usernameA = a.item.querySelector('.username')?.textContent.trim() || '';
                const usernameB = b.item.querySelector('.username')?.textContent.trim() || '';
                result = usernameA.localeCompare(usernameB);
            }
            else if (sortBy === 'username-length') {
                const usernameA = a.item.querySelector('.username')?.textContent.trim() || '';
                const usernameB = b.item.querySelector('.username')?.textContent.trim() || '';

                const widthA = getTextWidth(usernameA, a.item);
                const widthB = getTextWidth(usernameB, b.item);

                result = widthA - widthB;
            }
            else if (sortBy === 'level') {
                const levelA = parseInt(a.item.querySelector('div')?.textContent.trim()) || 0;
                const levelB = parseInt(b.item.querySelector('div')?.textContent.trim()) || 0;
                result = levelB - levelA;
            }
            else if (sortBy === 'country') {
                const flagA = a.item.querySelector('.flag');
                const flagB = b.item.querySelector('.flag');
                const countryA = flagA ? flagA.getAttribute('alt')?.replace(' Flag', '') || 'ZZ' : 'ZZ';
                const countryB = flagB ? flagB.getAttribute('alt')?.replace(' Flag', '') || 'ZZ' : 'ZZ';
                result = countryA.localeCompare(countryB);
            }

            if (sortBy === 'level') {
                return direction === 'desc' ? -result : result;
            } else {
                return direction === 'desc' ? -result : result;
            }
        });

        document.body.removeChild(measureElement);

        sortedItems.forEach((item, newIndex) => {
            item.item.style.order = newIndex;
        });

        listElement.style.display = 'flex';
        listElement.style.flexDirection = 'column';

        const parentContainer = listElement.parentElement;
        if (parentContainer) {
            const parentStyle = window.getComputedStyle(parentContainer);
            const parentHeight = parentStyle.height;

            listElement.style.height = parentHeight;
            listElement.style.maxHeight = 'none';
            listElement.style.overflow = 'auto';

            items.forEach(item => {
                item.style.flexShrink = '0';
            });
        }

        listElement.scrollTop = scrollTop;
    }

    function observeListChanges() {
        const targetWindow = document.querySelector('.window:has([src="icons/targetList.svg"])');
        if (!targetWindow) return;

        const listElement = targetWindow.querySelector('#list');
        if (!listElement) return;

        const observer = new MutationObserver(() => {
            if (currentSort.field) {
                sortTargetList(currentSort.field, currentSort.direction);
            }
        });

        observer.observe(listElement, { childList: true, subtree: true });
    }

    function checkForTargetList() {
        const targetWindow = document.querySelector('.window:has([src="icons/targetList.svg"])');
        if (targetWindow) {
            addSortingControls();
        }
    }

    setInterval(checkForTargetList, 1000);

    document.addEventListener('click', function (e) {
        if (e.target.closest('[src="icons/targetList.svg"]')) {
            setTimeout(addSortingControls, 100);
        }
    });
})();