🏠 Home 

YT Feed Sorter

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.5
// @description  Sorts the YouTube feed so that all scheduled streams and premieres come before archived videos.
// @match        *://*.youtube.com/*
// @grant        none
// @author       KFP
// ==/UserScript==
(function() {
'use strict';
const interceptFeedUpdate = () => {
const {fetch: origFetch} = window;
window.fetch = async (...args) => {
const url = args[0].url
const response = await origFetch(...args);
response.clone().json().then(data => {
if (url.match('/browse?')) {
if (data.contents?.twoColumnBrowseR###ltsRenderer) {
if (!ytInitialData.contents) ytInitialData.contents = {};
ytInitialData.contents.twoColumnBrowseR###ltsRenderer = data.contents.twoColumnBrowseR###ltsRenderer;
} else if (data.onResponseReceivedActions?.[0]?.appendContinuationItemsAction?.continuationItems) {
const added = data.onResponseReceivedActions[0].appendContinuationItemsAction.continuationItems;
const exists = ytInitialData.contents?.twoColumnBrowseR###ltsRenderer?.tabs?.[0]?.tabRenderer?.content;
if (exists?.richGridRenderer) {
exists.richGridRenderer.contents = exists.richGridRenderer.contents.concat(added);
} else if (exists?.sectionListRenderer) {
exists.sectionListRenderer.contents = exists.sectionListRenderer.contents.concat(added);
}
}
}
}).catch(e => console.error(e));
return response;
};
};
const getItemsData = isList => {
try {
const data = {};
if (isList) {
const allItemsData = ytInitialData.contents.twoColumnBrowseR###ltsRenderer.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.twoColumnBrowseR###ltsRenderer.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 itemSelector = isList ? 'ytd-browse[role="main"] ytd-section-list-renderer ytd-item-section-renderer' : 'ytd-browse[role="main"] ytd-rich-item-renderer';
const soonSelector = isList ? ' ytd-expanded-shelf-contents-renderer yt-touch-feedback-shape' : ' yt-touch-feedback-shape';
const soonCounter = document.querySelectorAll(itemSelector + soonSelector).length;
const allItems = document.querySelectorAll(itemSelector);
let lastType = 0;
let soonFound = 0;
for (let item of allItems) {
const notItem = item.querySelector('#title-container.style-scope.ytd-reel-shelf-renderer');
const isShort = item.closest('ytd-rich-section-renderer');
if (notItem || isShort) 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;
if (soon) {
if (++soonFound >= soonCounter) break;
}
}
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 ? 0 : aSoon ? 1 : 2;
const bi = bLive ? 0 : bSoon ? 1 : 2;
return (ai < bi) ? -1 : (ai > bi) ? 1 : 0;
}
};
const sortFeed = (feed, isList) => {
const itemsData = getItemsData(isList);
try {
if (isList) {
const firtsHeader = feed.children[0].querySelector('#title-container')
const manageBtn = firtsHeader.querySelector('#subscribe-button');
const layoutBtn = firtsHeader.querySelector('#menu');
[...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 : -1;
else return sortOrder(a, b, itemsData);
}).forEach(item => feed.appendChild(item));
const newFirtsHeader = feed.children[0].querySelector('#title-container')
const newManageBtn = newFirtsHeader.querySelector('#subscribe-button');
const newLayoutBtn = newFirtsHeader.querySelector('#menu');
newFirtsHeader.appendChild(manageBtn);
newFirtsHeader.appendChild(layoutBtn);
firtsHeader.appendChild(newManageBtn);
firtsHeader.appendChild(newLayoutBtn);
} else {
let itemWpappers = feed.querySelectorAll('ytd-rich-item-renderer');
let items = feed.querySelectorAll('ytd-rich-item-renderer > #content');
itemWpappers = [...itemWpappers].filter(el => !el.closest('ytd-rich-section-renderer'));
items = [...items].filter(el => !el.closest('ytd-rich-section-renderer'));
items.sort((a, b) => sortOrder(a, b, itemsData)).forEach((item, i) => {
itemWpappers[i].appendChild(item);
});
}
} catch(e) {
console.log('YTFS sortFeed error', e);
}
};
let inProcess = false;
const checkFeed = () => {
if (inProcess) return;
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) {
inProcess = true;
sortFeed(feed, isList);
inProcess = false;
}
}
};
interceptFeedUpdate();
setInterval(checkFeed, 500);
checkFeed();
})();