Sorts the YouTube feed so that all scheduled streams and premieres come before archived videos.
当前为
// ==UserScript==
// @name YT Feed Sorter
// @namespace YTFeedSorter
// @version 0.7.1
// @description Sorts the YouTube feed so that all scheduled streams and premieres come before archived videos.
// @match *://*.youtube.com/*
// @author KFP
// ==/UserScript==
(function() {
'use strict';
const getItemsData = isList => {
try {
const data = {};
if (isList) {
const allItemsData = ytInitialData.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents;
allItemsData.forEach(i => {
const itemData = i.itemSectionRenderer?.contents[0].shelfRenderer?.content.expandedShelfContentsRenderer.items[0].videoRenderer;
if (itemData) data[itemData.videoId] = itemData;
});
} else {
const allItemsData = ytInitialData.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.richGridRenderer.contents.filter(i => !!i.richItemRenderer);
allItemsData.forEach(i => {
const itemData = i.richItemRenderer?.content.videoRenderer;
if (itemData) data[itemData.videoId] = itemData
});
}
return data;
} catch(e) {
return null;
}
};
const isSorted = isList => {
const selector = isList ? 'ytd-browse[role="main"] ytd-section-list-renderer ytd-item-section-renderer' : 'ytd-browse[role="main"] ytd-rich-grid-row ytd-rich-item-renderer';
const allItems = document.querySelectorAll(selector);
let lastType = 0;
let soonFound = 0;
for (let item of allItems) {
const notItem = item.querySelector('#title-container.style-scope.ytd-reel-shelf-renderer');
if (notItem) continue;
const live = item.querySelector('.badge-style-type-live-now-alternate');
const soon = item.querySelector('yt-touch-feedback-shape');
const type = live ? 0 : soon ? 1 : 2;
if (type >= lastType) lastType = type;
else return false;
}
return true;
};
const sortOrder = (a, b, itemsData) => {
const aLive = a.querySelector('.badge-style-type-live-now-alternate');
const aSoon = a.querySelector('yt-touch-feedback-shape');
const bLive = b.querySelector('.badge-style-type-live-now-alternate');
const bSoon = b.querySelector('yt-touch-feedback-shape');
if (itemsData && aSoon && bSoon) {
const aId = a.querySelector('A#thumbnail').href.match(/[A-Za-z0-9_\-]{11}/gi);
const bId = b.querySelector('A#thumbnail').href.match(/[A-Za-z0-9_\-]{11}/gi);
if (!itemsData[aId]?.upcomingEventData || !itemsData[bId]?.upcomingEventData) return 0;
const aTime = itemsData[aId].upcomingEventData.startTime + aId;
const bTime = itemsData[bId].upcomingEventData.startTime + bId;
return (aTime > bTime) ? 1 : -1;
} else {
const ai = aLive ? 2 : aSoon ? 1 : 0;
const bi = bLive ? 2 : bSoon ? 1 : 0;
return (ai > bi) ? -1 : (ai < bi) ? 1 : 0;
}
};
const sortFeed = (feed, itemsData, isList) => {
try {
if (isList) {
[...feed.children].sort((a, b) => {
const aNotItem = (a.tagName.toLowerCase() !== 'ytd-item-section-renderer') || a.querySelector('#title-container.style-scope.ytd-reel-shelf-renderer');
const bNotItem = (b.tagName.toLowerCase() !== 'ytd-item-section-renderer') || b.querySelector('#title-container.style-scope.ytd-reel-shelf-renderer');
if (aNotItem || bNotItem) return aNotItem ? 1 : 0;
return sortOrder(a, b, itemsData);
}).forEach(item => feed.appendChild(item));
} else {
const rows = feed.querySelectorAll('ytd-rich-grid-row');
let currentRowI = 0;
let currentRow = rows[0].querySelector('#contents');
let rowLength = currentRow.children.length;
let items = [];
rows.forEach(row => {
const rowItems = row.querySelectorAll('ytd-rich-item-renderer');
items = items.concat([...rowItems]);
});
items.sort((a, b) => sortOrder(a, b, itemsData)).forEach((item, i) => {
currentRow.appendChild(item);
if (i && ((i + 1) % rowLength === 0)) {
currentRowI++;
if (rows[currentRowI]) {
currentRow = rows[currentRowI].querySelector('#contents');
}
}
});
}
} catch(e) {
console.log('YTFS sortFeed error', e);
}
};
setInterval(() => {
let isList = false;
let feed = document.querySelector('ytd-browse[page-subtype="subscriptions"][role="main"] #contents.ytd-rich-grid-renderer');
if (!feed) {
feed = document.querySelector('ytd-browse[page-subtype="subscriptions"][role="main"] #contents.ytd-section-list-renderer');
if (feed) isList = true;
}
if (feed) {
const sorted = isSorted(isList);
if (!sorted) {
const itemsData = getItemsData(isList);
sortFeed(feed, itemsData, isList);
}
}
}, 200);
})();