YouTube Music: sort by play count

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

当前为 2025-03-27 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube Music: sort by play count
// @namespace    https://github.com/KenKaneki73985
// @match        https://music.youtube.com/* 
// @version      1.0.1
// @license      MIT
// @description  Truly sort songs from an artist's page by play count from highest to lowest.
// @author       Ken Kaneki 
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    
    // ===== CONFIGURATION OPTIONS =====
    const NOTIFICATION_CONFIG = {
        // Positioning for "Sorting in Process" notification
        inProcessNotification: {
            top: '80%',    // Vertical position (can use %, px, etc.)
            right: '38%',  // Horizontal position (can use %, px, etc.)
            fontSize: '17px'
        },
        // Positioning for "Sorting Complete" notification
        sortingCompleteNotification: {
            top: '80%',    // Different vertical position 
            right: '45%',  // Horizontal position
            fontSize: '17px'
        }
    };
    
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        // Create a style for the notification
        const style = document.createElement('style');
        style.textContent = `
            #auto-dismiss-notification {
                position: fixed;
                color: white;
                padding: 15px;
                border-radius: 5px;
                z-index: 9999;
                transition: opacity 0.5s ease-out;
            }
            #auto-dismiss-notification.sorting-in-progress {
                background-color: rgba(0, 100, 0, 0.7); /* Green */
            }
            #auto-dismiss-notification.sorting-complete {
                background-color: rgba(82, 82, 255, 0.7); /* Blue */
            }`;
        document.head.appendChild(style);

        let SORT_SONGS_BTN = document.createElement('button')
        SORT_SONGS_BTN.innerHTML ='<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>'
        
        SORT_SONGS_BTN.style.border = "none"
        SORT_SONGS_BTN.style.position = 'absolute'
        SORT_SONGS_BTN.style.left = '87%' // works in 125/150%
        SORT_SONGS_BTN.style.top = '2.5%'
        SORT_SONGS_BTN.style.padding = '0px'
        SORT_SONGS_BTN.style.background = "none"
        SORT_SONGS_BTN.style.zIndex = '9999'
        SORT_SONGS_BTN.addEventListener('click', () => {
            // Show message immediately
            MESSAGE_SORTING_IN_PROCESS();
            
            // Delay sorting to ensure message is visible
            setTimeout(() => {
                SORT_SONGS();
                
                // Show sorting complete message after sorting
                setTimeout(() => {
                    MESSAGE_SORTING_COMPLETE();
                }, 500);  // Small delay to ensure sorting is visually complete
            }, 50);  // Small delay to ensure message rendering
        });
        
        document.body.appendChild(SORT_SONGS_BTN)
    }
    
    // Function to convert play count string to number
    function parsePlayCount(playString) {
        playString = playString.replace(' plays', '').trim();
        
        const multipliers = {
            'B': 1000000000,
            'M': 1000000,
            'K': 1000
        };
        
        const match = playString.match(/^(\d+(?:\.\d+)?)\s*([BMK])?$/);
        
        if (!match) return 0;
        
        const number = parseFloat(match[1]);
        const multiplier = match[2] ? multipliers[match[2]] : 1;
        
        return number * multiplier;
    }
    
    function SORT_SONGS(){
        const PLAYLIST_SHELF_DIV = document.querySelector('div.ytmusic-playlist-shelf-renderer:nth-child(3)');
        
        if (PLAYLIST_SHELF_DIV) {
            // Clone the original children to preserve event listeners
            const topLevelChildren = Array.from(PLAYLIST_SHELF_DIV.children);
            
            const songInfo = [];
            
            topLevelChildren.forEach((child, index) => {
                const titleElement = child.querySelector('div:nth-child(5) > div:nth-child(1) > yt-formatted-string:nth-child(1) > a:nth-child(1)');
                const playsElement = child.querySelector('div:nth-child(5) > div:nth-child(3) > yt-formatted-string:nth-child(2)');
                
                const songDetails = {
                    element: child,
                    id: `${index + 1}`,
                    title: titleElement ? titleElement.textContent.trim() : 'Title not found',
                    plays: playsElement ? playsElement.textContent.trim() : 'Plays not found',
                    playCount: playsElement ? parsePlayCount(playsElement.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
            PLAYLIST_SHELF_DIV.replaceChildren(...songInfo.map(song => song.element));
            
            // Modify song ranks without recreating elements
            songInfo.forEach((song, index) => {
                song.element.id = `${index + 1}`;
            });
            
            console.log("Success: Sorted By Play Count");
        } else {
            alert('error: Playlist shelf div not found');
        }
    }

    function MESSAGE_SORTING_IN_PROCESS(){
        // Remove any existing notification
        const EXISTING_NOTIFICATION = document.getElementById('auto-dismiss-notification');
        if (EXISTING_NOTIFICATION) {
            EXISTING_NOTIFICATION.remove();
        }

        // Create new notification element
        const notification = document.createElement('div');
        notification.id = 'auto-dismiss-notification';
        notification.classList.add('sorting-in-progress');
        notification.textContent = "Sorting in Process... Wait a few seconds"

        // Apply configuration
        notification.style.top = NOTIFICATION_CONFIG.inProcessNotification.top;
        notification.style.right = NOTIFICATION_CONFIG.inProcessNotification.right;
        notification.style.fontSize = NOTIFICATION_CONFIG.inProcessNotification.fontSize;

        // Append to body
        document.body.appendChild(notification);

        // Auto-dismiss after 3 seconds
        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => {
                notification.remove();
            }, 500); // matches transition time
        }, 3000);
    }

    function MESSAGE_SORTING_COMPLETE(){
        // Remove any existing notification
        const EXISTING_NOTIFICATION = document.getElementById('auto-dismiss-notification');
        if (EXISTING_NOTIFICATION) {
            EXISTING_NOTIFICATION.remove();
        }

        // Create new notification element
        const notification = document.createElement('div');
        notification.id = 'auto-dismiss-notification';
        notification.classList.add('sorting-complete');
        notification.textContent = "Sorting Complete"

        // Apply configuration
        notification.style.top = NOTIFICATION_CONFIG.sortingCompleteNotification.top;
        notification.style.right = NOTIFICATION_CONFIG.sortingCompleteNotification.right;
        notification.style.fontSize = NOTIFICATION_CONFIG.sortingCompleteNotification.fontSize;

        // Append to body
        document.body.appendChild(notification);

        // Auto-dismiss after 3 seconds
        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => {
                notification.remove();
            }, 500); // matches transition time
        }, 3000);
    }
})();