NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Remove Streamed YouTube Videos and Highlight Specific Channels
// @version 1.1
// @description Remove streamed content from subscriptions page and highlight videos from specific channels
// @author nascent
// @match https://www.youtube.com/feed/subscriptions
// @icon https://www.google.com/s2/favicons?domain=youtube.com
// @updateURL https://openuserjs.org/meta/nascent/Remove_Streamed_YouTube_Videos_and_Highlight_Specific_Channels.meta.js
// @downloadURL https://openuserjs.org/src/scripts/nascent/Remove_Streamed_YouTube_Videos.user.js
// @license GPL-3.0-or-later
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// ==/UserScript==
/*jshint esversion: 6 */
(function() {
'use strict';
// Settings
let channelsToHighlight = GM_getValue('channelsToHighlight', ['markiplier', 'jacksepticeye']);
let highlightColor = GM_getValue('highlightColor', '#faf8cf');
let removeStreamed = GM_getValue('removeStreamed', true);
let hideWatched = GM_getValue('hideWatched', false);
function saveSettings() {
GM_setValue('channelsToHighlight', channelsToHighlight);
GM_setValue('highlightColor', highlightColor);
GM_setValue('removeStreamed', removeStreamed);
GM_setValue('hideWatched', hideWatched);
}
GM_addStyle(`
#channel-manager {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
border: 1px solid #ccc;
padding: 20px;
z-index: 9999;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
max-width: 400px;
width: 90%;
}
#channel-manager textarea {
width: 100%;
height: 100px;
margin-bottom: 10px;
}
#channel-manager .controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
#channel-manager .controls > div {
display: flex;
align-items: center;
}
#channel-manager .controls label {
margin-right: 5px;
}
#channel-manager .button-row {
display: flex;
justify-content: space-between;
}
#close-button, #apply-button {
padding: 5px 10px;
}
`);
const observer = new MutationObserver(mutations => {
for (let mutation of mutations) {
for (let node of mutation.addedNodes) {
if (node.nodeName === 'YTD-RICH-ITEM-RENDERER') {
handleRenderer(node);
setTimeout(() => highlightChannel(node), 500);
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
function isWatchedVideo(renderer) {
const progressBar = renderer.querySelector('ytd-thumbnail-overlay-resume-playback-renderer #progress');
return progressBar !== null;
}
function handleRenderer(renderer) {
if (hideWatched && isWatchedVideo(renderer)) {
const videoTitle = renderer.querySelector('#video-title')?.textContent;
console.log('Removing watched video:', videoTitle);
renderer.remove();
return;
}
if (removeStreamed) {
const metaBlock = renderer.querySelector('ytd-video-meta-block');
if (metaBlock) {
const items = metaBlock.querySelectorAll('.inline-metadata-item');
for (let item of items) {
if (/^Streamed/.test(item.textContent)) {
const videoTitle = renderer.querySelector('#video-title')?.textContent;
console.log('Removing streamed video:', videoTitle);
renderer.remove();
return;
}
}
}
}
}
function highlightChannel(renderer) {
const channelNameElement = renderer.querySelector('#text.ytd-channel-name') ||
renderer.querySelector('#channel-info #text-container yt-formatted-string');
if (channelNameElement) {
const channelName = channelNameElement.textContent.trim();
if (channelsToHighlight.includes(channelName.toLowerCase())) {
const detailsContainer = renderer.querySelector('#details') ||
renderer.querySelector('#meta');
if (detailsContainer) {
detailsContainer.style.backgroundColor = highlightColor;
}
}
}
}
function initialRun() {
const initialLoad = document.querySelectorAll('ytd-rich-item-renderer');
initialLoad.forEach(node => {
handleRenderer(node);
setTimeout(() => highlightChannel(node), 500);
});
}
setTimeout(initialRun, 1000);
function createChannelManager() {
const div = document.createElement('div');
div.id = 'channel-manager';
div.innerHTML = `
<h3>Manage Highlighted Channels</h3>
<textarea id="channel-textarea" placeholder="Enter channel names, one per line"></textarea>
<div class="controls">
<div>
<label for="highlight-color">Highlight Color:</label>
<input type="color" id="highlight-color" value="${highlightColor}">
</div>
<div>
<label for="remove-streamed">
<input type="checkbox" id="remove-streamed" ${removeStreamed ? 'checked' : ''}>
Remove Streamed Content
</label>
</div>
<div>
<label for="hide-watched">
<input type="checkbox" id="hide-watched" ${hideWatched ? 'checked' : ''}>
Hide Watched Videos
</label>
</div>
</div>
<div class="button-row">
<button id="close-button">Close</button>
<button id="apply-button">Apply</button>
</div>
`;
document.body.appendChild(div);
const channelTextarea = div.querySelector('#channel-textarea');
const colorInput = div.querySelector('#highlight-color');
const removeStreamedCheckbox = div.querySelector('#remove-streamed');
const hideWatchedCheckbox = div.querySelector('#hide-watched');
const applyButton = div.querySelector('#apply-button');
const closeButton = div.querySelector('#close-button');
// Populate textarea with current channels
channelTextarea.value = channelsToHighlight.join('\n');
applyButton.addEventListener('click', () => {
// Update channels
channelsToHighlight = channelTextarea.value
.split('\n')
.map(channel => channel.trim().toLowerCase())
.filter(channel => channel !== '');
// Update settings
highlightColor = colorInput.value;
removeStreamed = removeStreamedCheckbox.checked;
hideWatched = hideWatchedCheckbox.checked;
saveSettings();
div.remove();
location.reload(); // Reload the page to apply changes
});
closeButton.addEventListener('click', () => {
div.remove(); // Simply close the dialog without saving changes
});
}
GM_registerMenuCommand("Customize", createChannelManager);
})();