Sorts the YouTube feed so that all scheduled streams come before archived videos.
当前为
// ==UserScript==
// @name YT Feed Sorter
// @namespace YTFeedSorter
// @version 0.6
// @description Sorts the YouTube feed so that all scheduled streams come before archived videos.
// @match *://*.youtube.com/*
// @author KFP
// ==/UserScript==
(function() {
'use strict';
let feedSorted = false;
const feedGridSelector = 'ytd-browse[page-subtype="subscriptions"][role="main"] #contents.ytd-rich-grid-renderer';
const feedListSelector = 'ytd-browse[page-subtype="subscriptions"][role="main"] #contents.ytd-section-list-renderer';
const liveSelector = '.badge-style-type-live-now-alternate';
const soonSelector = 'ytd-toggle-button-renderer';
const sortFeed = feed => {
const isList = feed.classList.contains('ytd-section-list-renderer');
if (isList) {
[...feed.children].sort((a, b) => {
const aInner = a.root.querySelector('ytd-shelf-renderer').root.querySelector('ytd-expanded-shelf-contents-renderer').root.querySelector('ytd-video-renderer');
const bInner = b.root.querySelector('ytd-shelf-renderer').root.querySelector('ytd-expanded-shelf-contents-renderer').root.querySelector('ytd-video-renderer');
const aLive = aInner.root.querySelector('ytd-badge-supported-renderer:not([hidden])')?.root.querySelector(liveSelector);
const aSoon = aInner.root.querySelector(soonSelector);
const bLive = bInner.root.querySelector('ytd-badge-supported-renderer:not([hidden])')?.root.querySelector(liveSelector);
const bSoon = bInner.root.querySelector(soonSelector);
const ai = aLive ? 2 : aSoon ? 1 : 0;
const bi = bLive ? 2 : bSoon ? 1 : 0;
return (ai > bi) ? -1 : (ai < bi) ? 1 : 0;
}).forEach(item => feed.appendChild(item));
} else {
const rows = feed.querySelectorAll('ytd-rich-grid-row');
let currentRowI = 0;
let currentRow = rows[0].root.querySelector('#contents');
let rowLength = currentRow.children.length;
let items = [];
rows.forEach(row => {
const rowItems = row.root.querySelectorAll('ytd-rich-item-renderer');
items = items.concat(rowItems);
});
items.sort((a, b) => {
const aInner = a.root.querySelector('ytd-rich-grid-media');
const bInner = b.root.querySelector('ytd-rich-grid-media');
const aLive = aInner.root.querySelector('.video-badge:not([hidden])')?.root.querySelector(liveSelector);
const aSoon = aInner.root.querySelector(soonSelector);
const bLive = bInner.root.querySelector('.video-badge:not([hidden])')?.root.querySelector(liveSelector);
const bSoon = bInner.root.querySelector(soonSelector);
const ai = aLive ? 2 : aSoon ? 1 : 0;
const bi = bLive ? 2 : bSoon ? 1 : 0;
return (ai > bi) ? -1 : (ai < bi) ? 1 : 0;
}).forEach((item, i) => {
currentRow.appendChild(item);
if (i && ((i + 1) % rowLength === 0)) {
currentRowI++;
if (rows[currentRowI]) {
currentRow = rows[currentRowI].root.querySelector('#contents');
}
}
});
}
};
const gridObserver = new MutationObserver(mutations => {
for (const mut of mutations) {
if (mut.removedNodes?.length) {
setTimeout(() => sortFeed(mut.target), 200);
setTimeout(() => sortFeed(mut.target), 2000);
setTimeout(() => sortFeed(mut.target), 4000);
}
}
});
let listObserverTimer = 0;
const listObserver = new MutationObserver(mutations => {
for (const mut of mutations) {
if (mut.addedNodes?.length) {
const now = Date.now();
if (now - listObserverTimer > 5200) {
setTimeout(() => sortFeed(mut.target), 200);
setTimeout(() => sortFeed(mut.target), 5000);
listObserverTimer = now;
}
}
}
});
let pageChanged = false;
setInterval(() => {
let isList = false;
let feed = document.querySelector(feedGridSelector);
if (!feed) {
feed = document.querySelector(feedListSelector);
if (feed) isList = true;
}
if (feed) {
if (feedSorted) return;
if (isList) {
setTimeout(() => sortFeed(feed), 1);
listObserver.observe(feed, {childList: true});
} else {
if (pageChanged) {
setTimeout(() => sortFeed(feed), 1000);
setTimeout(() => sortFeed(feed), 3000);
setTimeout(() => sortFeed(feed), 6000);
} else {
setTimeout(() => sortFeed(feed), 1);
}
gridObserver.observe(feed, {childList: true});
}
feedSorted = true;
} else if (feedSorted) {
gridObserver.disconnect();
listObserver.disconnect();
pageChanged = true;
feedSorted = false;
}
}, 100);
})();