您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
(我已经安装了用户样式管理器,让我安装!)
// ==UserScript==
// @name YT Music: sort by play count
// @match https://music.youtube.com/*
// @grant none
// @namespace https://github.com/KenKaneki73985
// @version 1.0.3
// @license MIT
// @description Truly sort songs from an artist's page by play count from highest to lowest.
// @author Ken Kaneki
// ==/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
SORTING_COMPLETE_Notification: {
top: '80%', // Different vertical position
right: '45%', // Horizontal position
fontSize: '17px'
},
// Positioning for "Sorting Complete" notification
ALREADY_SORTED_Notification: {
top: '80%', // Different vertical position
right: '40%', // 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 */
}
#auto-dismiss-notification.already-sorted {
background-color: rgba(255, 165, 0, 0.7); /* Orange */
}`;
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 = '89%' // 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', () => {
// Check if playlist is already sorted
if (IS_PLAYLIST_SORTED()) {
MESSAGE_ALREADY_SORTED();
return;
}
// 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;
}
// Check if playlist is already sorted by play count
function IS_PLAYLIST_SORTED() {
const PLAYLIST_SHELF_DIV = document.querySelector('div.ytmusic-playlist-shelf-renderer:nth-child(3)');
if (PLAYLIST_SHELF_DIV) {
const children = Array.from(PLAYLIST_SHELF_DIV.children);
const playCounts = children.map(child => {
const playsElement = child.querySelector('div:nth-child(5) > div:nth-child(3) > yt-formatted-string:nth-child(2)');
return playsElement ? parsePlayCount(playsElement.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 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 Progress... 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.SORTING_COMPLETE_Notification.top;
notification.style.right = NOTIFICATION_CONFIG.SORTING_COMPLETE_Notification.right;
notification.style.fontSize = NOTIFICATION_CONFIG.SORTING_COMPLETE_Notification.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_ALREADY_SORTED(){
// 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('already-sorted');
notification.textContent = "Playlist Already Sorted by Play Count"
// Apply configuration
notification.style.top = NOTIFICATION_CONFIG.ALREADY_SORTED_Notification.top;
notification.style.right = NOTIFICATION_CONFIG.ALREADY_SORTED_Notification.right;
notification.style.fontSize = NOTIFICATION_CONFIG.ALREADY_SORTED_Notification.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);
}
})();