🎉 欢迎访问GreasyFork.Org 镜像站!本镜像站由公众号【爱吃馍】搭建,用于分享脚本。联系邮箱📮

Greasy fork 爱吃馍镜像

YT Music: sort by play count

Truly sort songs from an artist's page by play count from highest to lowest.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

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

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

// ==UserScript==
// @name         YT Music: sort by play count
// @match        https://music.youtube.com/* 
// @run-at       document-end
// @grant        window.onurlchange
// @version      1.0.29
// @license      MIT
// @description  Truly sort songs from an artist's page by play count from highest to lowest.
// @namespace    https://github.com/KenKaneki73985
// @author       Ken Kaneki 
// ==/UserScript==

// user_script = "moz-extension://762e4395-b145-4620-8dd9-31bf09e052de/options.html#nav=2c66e48e-5eb3-4d04-8486-d2a9bc2306d8+editor"
// reload_ID

(function() {
    'use strict'
    let SORT_TOGGLE = true
    PAGE_READY_ACTIONS()

    async function PAGE_READY_ACTIONS(){
        
        LoadUtils()
        await new Promise(resolve => setTimeout(resolve, 1000))

        gen_ADD_SVG("fixed", '2.5%', '85.5%', SortByPlayCount, '<svg width="30px" height="30px" fill="#0080ff" viewBox="0 0 24 24" id="sort-ascending" data-name="Flat Line" xmlns="http://www.w3.org/2000/svg" class="icon flat-line" stroke="#0080ff"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><polyline id="primary" points="10 15 6 19 2 15" style="fill: none; stroke: #0080ff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></polyline><path id="primary-2" data-name="primary" d="M6,19V4M20,16H15m5-5H13m7-5H10" style="fill: none; stroke: #0080ff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path></g></svg>')
        InitializeToggleButton()
        CheckUrl()
    }

    function LoadUtils(){
    
        let GithubScripts = [
            'https://raw.githack.com/KenKaneki73985/javascript-utils/refs/heads/main/general.js',
            'https://raw.githack.com/KenKaneki73985/javascript-utils/refs/heads/main/show_GUI.js',
            'https://raw.githack.com/KenKaneki73985/javascript-utils/refs/heads/main/countdown.js'
        ]

        GithubScripts.forEach(LoadGithubScript)

        function LoadGithubScript(GithubScript){
            let ScriptElement = document.createElement('script')
            ScriptElement.src = GithubScript
            document.head.appendChild(ScriptElement)
        }
    }

    // ─── SCROLL EVENT ─────────────
    addEventListener('wheel', () => {
        if (!SORT_TOGGLE){
            return // <---
        }

        SortByPlayCount()
    })

    // ─── URL CHANGE EVENT ─────────────
    addEventListener('urlchange', CheckUrl)

    async function CheckUrl() {

        // ▬▬▬ YOUR IN ARTIST PAGE ▬▬▬▬▬▬▬▬▬▬▬▬▬
        if (location.href.includes("music.youtube.com/channel")) {

            if (!SORT_TOGGLE){
                return // <---
            }

            show_GUI("Sorting...", "GUI_v1", "green", 0, "y80", 17, 3600000)
            await WaitTextToExist("Show all")
            FindTextElement("Show all").click()

            await WaitElementToExist('div.ytmusic-playlist-shelf-renderer:nth-child(3)') // songs container
            SortByPlayCount()
        } 
        
        // ▬▬▬ NOT IN ARTIST PAGE. WILL NOT SORT BY PLAY COUNT ▬▬▬▬▬▬▬▬▬▬▬▬▬
        else {
            // show_GUI("⛔ not in artist page. did not sort play count (CheckUrl - SortByPlayCount)", "GUI_v1", "blue", 0, "y80", 17, 3000)
            log("⛔ not in artist page. did not sort play count (CheckUrl - SortByPlayCount)")
        }
    }

    function SortByPlayCount(){

        // NOTE: this should not be here
        // if (!SORT_TOGGLE){
        //     return // <---
        // }

        // ─── IF NO "TOP SONGS" TEXT, DONT SORT ─────────────
        if (!document.body.innerText.includes("Top songs")){
            log("❌ did not sort count. not in artist page (body: Top songs)")
            return // <---
        }

        if (IsPlaylistSorted()) {
            log("👍 already sorted by play count")
            return // <---
        }

        // RUN BELOW IF...
        // - IN ARTIST PAGE ("Top songs" text exist)
        // - NOT ALREADY SORTED

        // ArtistChannelSongsContainer  = <div id="contents" class="style-scope ytmusic-playlist-shelf-renderer">
        let ArtistChannelSongsContainer = document.querySelector('div.ytmusic-playlist-shelf-renderer:nth-child(3)')

        if (ArtistChannelSongsContainer){

            // Clone the original children to preserve event listeners
            // ".children" returns HTMLCollection (array like)
            // Array.from() method returns an array from any object with a length property, or any iterable object.

            let TopChildren_arr = Array.from(ArtistChannelSongsContainer.children) // convert HTMLcollection to array
            
            let SongInfo = []
            
            TopChildren_arr.forEach(PushSongDetails)

            function PushSongDetails(SingleTopChild, index){

                // TitleElement = <a class="yt-simple-endpoint style-scope yt-formatted-string" spellcheck="false" href="watch?v=IST-GfqUwDA&amp;list=OLAK5uy_nwcZ3NjxrKg26DJNgPPaENYvVuVM82VXk">How to save a life</a>
                let TitleElement     = SingleTopChild.querySelector('div:nth-child(5) > div:nth-child(1) > yt-formatted-string:nth-child(1) > a:nth-child(1)')
                // PlayCountElement = <yt-formatted-string class="flex-column style-scope ytmusic-responsive-list-item-renderer" ellipsis-truncate="" respect-html-dir="" role="listitem" aria-label="200 million plays" ellipsis-truncate-styling="" title="200M plays">200M plays</yt-formatted-string>
                let PlayCountElement = SingleTopChild.querySelector('div:nth-child(5) > div:nth-child(3) > yt-formatted-string:nth-child(2)')
                
                let SongDetails = {
                    element: SingleTopChild,
                    id: `${index + 1}`,
                    title: TitleElement ? TitleElement.textContent.trim() : 'Title not found',
                    plays: PlayCountElement ? PlayCountElement.textContent.trim() : 'Plays not found',
                    playCount: PlayCountElement ? ParsePlayCount(PlayCountElement.textContent.trim()) : 0
                }
                
                SongInfo.push(SongDetails)
            }
            
            // SORT songs by play count (highest to lowest)
            SongInfo.sort((a, b) => b.playCount - a.playCount);
            
            // Use replaceChildren to preserve original event listeners
            ArtistChannelSongsContainer.replaceChildren(...SongInfo.map(song => song.element));
            
            // Modify song ranks without recreating elements
            SongInfo.forEach((song, index) => {
                song.element.id = `${index + 1}`;
            });
            
            show_GUI("Sorting Complete", "GUI_v1", "blue", 0, "y80", 17, 3000)
            log("☑️ success: sorted by play count")
        } 
        
        else {
            log("❌ error: not found ArtistChannelSongsContainer (sort by play count)");
        }
    }

    // Function to convert play count string to number
    function ParsePlayCount(playString) {
        playString = playString.replace(' plays', '').trim();
        
        let multipliers = {
            'B': 1000000000,
            'M': 1000000,
            'K': 1000
        };
        
        let match = playString.match(/^(\d+(?:\.\d+)?)\s*([BMK])?$/);
        
        if (!match) return 0;
        
        let number = parseFloat(match[1]);
        let multiplier = match[2] ? multipliers[match[2]] : 1;
        
        return number * multiplier;
    }
    
    // Check if playlist is already sorted by play count
    function IsPlaylistSorted() {

        let ArtistChannelSongsContainer = document.querySelector('div.ytmusic-playlist-shelf-renderer:nth-child(3)');
        
        if (ArtistChannelSongsContainer) {
            let TopChildren_arr = Array.from(ArtistChannelSongsContainer.children);
            
            let PlayCounts = TopChildren_arr.map(PlayCountAction)

            function PlayCountAction(SingleChild){
                let PlayCountElement = SingleChild.querySelector('div:nth-child(5) > div:nth-child(3) > yt-formatted-string:nth-child(2)');
                return PlayCountElement ? ParsePlayCount(PlayCountElement.textContent.trim()) : 0;
            }
            
            // Check if play counts are in descending order
            for (let i = 1; i < PlayCounts.length; i++) {
                if (PlayCounts[i] > PlayCounts[i - 1]) {
                    return false; // Not sorted
                }
            }
            
            return true // Already sorted
        }
        
        return false
    }


    function CREATE_TOGGLE_BUTTON() {
        // Create button container
        let buttonContainer = document.createElement('div');
        buttonContainer.style.position = 'fixed';
        buttonContainer.style.top = '3.2%';
        buttonContainer.style.left = '78%'; // "ATSORT"
        buttonContainer.style.zIndex = '9999';
        buttonContainer.style.backgroundColor = 'black';
        buttonContainer.style.padding = '3px 8px';
        buttonContainer.style.borderRadius = '8px';
        buttonContainer.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
        buttonContainer.style.display = 'flex';
        buttonContainer.style.alignItems = 'center';
        buttonContainer.style.gap = '0px';
        
        // Create label
        let label = document.createElement('span');
        label.textContent = 'AUTO SORT';

        // FONT STYLE
        label.style.fontFamily = 'SEGOE UI'; // changed from 'Arial, sans-serif'
        label.style.fontSize = '10px';
        // label.style.fontWeight = 'bold';
        label.style.color = 'white';
        
        // Create SVG toggle
        let svgNS = "http://www.w3.org/2000/svg";
        let svg = document.createElementNS(svgNS, "svg");
        svg.setAttribute("width", "30");
        svg.setAttribute("height", "10");
        svg.setAttribute("viewBox", "0 0 60 30");
        svg.style.cursor = "pointer";
        
        // Create toggle track
        let track = document.createElementNS(svgNS, "rect");
        track.setAttribute("x", "0");
        track.setAttribute("y", "0");
        track.setAttribute("rx", "15");
        track.setAttribute("ry", "15");
        track.setAttribute("width", "60");
        track.setAttribute("height", "30");
        track.setAttribute("fill", SORT_TOGGLE ? "#888" : "#ccc");
        
        // Create toggle circle/thumb
        let circle = document.createElementNS(svgNS, "circle");
        circle.setAttribute("cx", SORT_TOGGLE ? "45" : "15");
        circle.setAttribute("cy", "15");
        circle.setAttribute("r", "12");
        // circle.setAttribute("fill", "white");
        circle.setAttribute("fill", "rgba(65, 65, 255, 0.7)"); // blue circle
        
        // Add elements to SVG
        svg.appendChild(track);
        svg.appendChild(circle);
        
        // Add click event to SVG
        svg.addEventListener('click', function() {
            SORT_TOGGLE = !SORT_TOGGLE;
            track.setAttribute("fill", SORT_TOGGLE ? "#888" : "#ccc");
            circle.setAttribute("cx", SORT_TOGGLE ? "45" : "15");
            SAVE_TOGGLE_STATE();
        });
        
        // Assemble button container
        buttonContainer.appendChild(label);
        buttonContainer.appendChild(svg);
        
        // Add container to document
        document.body.appendChild(buttonContainer);
        
        // Return references to elements that need to be updated
        return { track, circle };
    }
    
    // ─── SAVE / LOAD TOGGLE STATE ─────────────
    function SAVE_TOGGLE_STATE() {
        localStorage.setItem('sort-toggle-state', SORT_TOGGLE);
    }
    
    function LOAD_TOGGLE_STATE() {
        let savedState = localStorage.getItem('sort-toggle-state');
        if (savedState !== null) {
            SORT_TOGGLE = savedState === 'true';
        }
    }
    
    function InitializeToggleButton() {
        LOAD_TOGGLE_STATE()    // Load state first
        CREATE_TOGGLE_BUTTON() // Then create button with correct state
    }
})();

// async function FindShowAllButton() {
    
//     let ShowAll_button = FindTextElement("Show all")

//     // ─── CLICK "SHOW ALL SONGS" BUTTON ─────────────
//     if (ShowAll_button){

//         ShowAll_button.click()
//         log("☑️ success: clicked ShowAll_button")

//         // ───>> MOVED TO Artist Page 
//         show_GUI("Sorting...", "GUI_v1", "green", 0, "y80", 17, 3000)

//         // await WaitTextToExist("Top songs") // seems no need
//         // SortByPlayCount()
//         return true
//     } 
    
//     // ─── NOT FOUND "SHOW ALL SONGS" BUTTON ─────────────
//     else if (!ShowAll_button) {
//         log("❌ error: not found ShowAll_button")

//         return false

//         // ─── IF IN ARTIST PLAYLIST, SORT BY PLAY COUNT ─────────────
//         // if (document.body.innerText.includes("Top songs")){
//         //     log('👍 not found ShowAll_button but will sort since already in artist playlist page')
//         //     SortByPlayCount()
//         // }
//     }
// }