NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Better Youtube
// @namespace http://tampermonkey.net/
// @version 0.17
// @description Better Youtube
// @author You
// @match https://www.youtube.com/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// @license MIT
// @updateURL https://openuserjs.org/meta/asteriksme/Better_Youtube.meta.js
// @downloadURL https://openuserjs.org/install/asteriksme/Better_Youtube.user.js
// ==/UserScript==
/* jshint esversion: 11 */
(function() {
'use strict';
const getRelativeTimeString = (date, lang = navigator.language) => {
const timeMs = typeof date === "number" ? date : date.getTime();
const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
const units = ["second", "minute", "hour", "day", "week", "month", "year"];
const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
const rtf = new Intl.RelativeTimeFormat(lang, { numeric: "auto" });
return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
};
let running = false;
const addTimestamps = async () => {
if (running || !document.querySelector(".ytp-endscreen-content a .ytp-videowall-still-info-author")) {
return;
}
running = true;
await Promise.all(
document.querySelector(".ytp-endscreen-content").querySelectorAll("a:not([data-timestamp-added])").map((tag) =>
fetch(tag.href, {
method: 'get',
}).then((response) => response.text()).then((res) => {
const date = new Date(res.match(/publishDate":"([^"]*)"/)[1]);
tag.querySelector(".ytp-videowall-still-info-author").innerHTML += ` • ${date.toLocaleDateString()} (${getRelativeTimeString(date)})`;
tag.setAttribute("data-timestamp-added", "");
})
)
);
running = false;
};
const changeVideosPerRow = (changes, observer) => {
const MIN_SIZE = 5;
const renderers = "ytd-rich-item-renderer, .video_card";
const itemsPerRow = "--ytd-rich-grid-items-per-row";
document.querySelectorAll(renderers).forEach((renderer) => {
if (parseInt(getComputedStyle(renderer).getPropertyValue(itemsPerRow), 10) < MIN_SIZE) {
renderer.style.setProperty(itemsPerRow, MIN_SIZE.toString());
}
});
};
const removeUselessSection = (changes, observer) => {
const renderer = "ytd-rich-section-renderer";
const sections = document.querySelectorAll(renderer);
if (sections.length < 2) {
return;
}
sections.forEach((section, index) => index > 0 && setTimeout(() => section.remove()));
};
const removeChannelTrailer = (changes, observer) => {
document.querySelector('[role="main"][page-subtype="channels"] ytd-channel-video-player-renderer video')?.remove();
};
(new MutationObserver(addTimestamps)).observe(document, {childList: true, subtree: true});
(new MutationObserver(changeVideosPerRow)).observe(document, {childList: true, subtree: true});
(new MutationObserver(removeUselessSection)).observe(document, {childList: true, subtree: true});
(new MutationObserver(removeChannelTrailer)).observe(document, {childList: true, subtree: true});
let hideWatched = false;
let scheduled = false;
const ICON_EYE = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8M1.173 8a13 13 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5s3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5s-3.879-1.168-5.168-2.457A13 13 0 0 1 1.172 8z"/>
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0"/>
</svg>`;
const ICON_EYE_SLASH = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye-slash" viewBox="0 0 16 16">
<path d="M13.359 11.238C15.06 9.72 16 8 16 8s-3-5.5-8-5.5a7 7 0 0 0-2.79.588l.77.771A6 6 0 0 1 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755q-.247.248-.517.486z"/>
<path d="M11.297 9.176a3.5 3.5 0 0 0-4.474-4.474l.823.823a2.5 2.5 0 0 1 2.829 2.829zm-2.943 1.299.822.822a3.5 3.5 0 0 1-4.474-4.474l.823.823a2.5 2.5 0 0 0 2.829 2.829"/>
<path d="M3.35 5.47q-.27.24-.518.487A13 13 0 0 0 1.172 8l.195.288c.335.48.83 1.12 1.465 1.755C4.121 11.332 5.881 12.5 8 12.5c.716 0 1.39-.133 2.02-.36l.77.772A7 7 0 0 1 8 13.5C3 13.5 0 8 0 8s.939-1.721 2.641-3.238l.708.709zm10.296 8.884-12-12 .708-.708 12 12z"/>
</svg>`;
const htmlShowWatched = `${ICON_EYE_SLASH}<span style="margin-left:6px;font-size:12px;">Hide watched</span>`;
const htmlHideWatched = `${ICON_EYE}<span style="margin-left:6px;font-size:12px;">Show watched</span>`;
function ensureToggleButton() {
if (document.getElementById('by-hide-watched-btn')) return;
const btn = document.createElement('button');
btn.id = 'by-hide-watched-btn';
btn.type = 'button';
btn.innerHTML = htmlShowWatched;
btn.title = 'Hide watched videos';
Object.assign(btn.style, {
position: 'fixed',
display: 'flex',
gap: 4,
alignItems: 'center',
top: '13px',
right: '245px',
zIndex: 999999,
fontSize: '18px',
borderRadius: '6px',
padding: '6px 10px',
border: '1px solid #666',
background: '#222',
color: '#fff',
cursor: 'pointer',
opacity: '0.85'
});
btn.addEventListener('mouseenter', () => { btn.style.opacity = '1'; });
btn.addEventListener('mouseleave', () => { btn.style.opacity = '0.85'; });
btn.addEventListener('click', () => {
hideWatched = !hideWatched;
btn.innerHTML = hideWatched ? htmlHideWatched : htmlShowWatched;
btn.title = hideWatched ? 'Show watched videos' : 'Hide watched videos';
process();
});
document.body.appendChild(btn);
}
function tagAndStyleWatched() {
const MIN_PROGRESS_PERCENT = 15;
document.querySelectorAll('yt-thumbnail-overlay-progress-bar-view-model, ytd-thumbnail-overlay-resume-playback-renderer').forEach(pb => {
const seg = pb.querySelector('.ytThumbnailOverlayProgressBarHostWatchedProgressBarSegment, .ytd-thumbnail-overlay-resume-playback-renderer');
let qualifies = false;
if (seg) {
const styleAttr = seg.getAttribute('style') || '';
const m = styleAttr.match(/width\s*:\s*([\d.]+)%/i);
if (m) {
const pct = parseFloat(m[1]);
if (!isNaN(pct) && pct >= MIN_PROGRESS_PERCENT) {
qualifies = true;
}
}
}
const thumbVM = pb.closest('yt-thumbnail-view-model');
const rich = pb.closest('ytd-rich-item-renderer');
if (!rich) return;
if (qualifies) {
rich.setAttribute('data-better-youtube-watched', '1');
if (thumbVM) {
const img = thumbVM.querySelector('.ytThumbnailViewModelImage');
if (img && !img.dataset._betterYoutubeStyled) {
img.style.filter = 'grayscale(100%) contrast(50%)';
img.dataset._betterYoutubeStyled = '1';
}
}
} else {
rich.removeAttribute('data-better-youtube-watched');
if (thumbVM) {
const img = thumbVM.querySelector('.ytThumbnailViewModelImage');
if (img && img.dataset._betterYoutubeStyled) {
img.style.filter = '';
delete img.dataset._betterYoutubeStyled;
}
}
}
});
}
function applyHideShow() {
const watched = document.querySelectorAll('ytd-rich-item-renderer[data-better-youtube-watched="1"]');
watched.forEach(rich => {
rich.style.display = hideWatched ? 'none' : '';
});
}
function removeFirstColumnAttr() {
document.querySelectorAll('ytd-rich-item-renderer[is-in-first-column]').forEach(el => {
el.removeAttribute('is-in-first-column');
});
}
function process() {
tagAndStyleWatched();
removeFirstColumnAttr();
applyHideShow();
}
function scheduleProcess() {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(() => {
scheduled = false;
process();
});
}
const observer = new MutationObserver(scheduleProcess);
observer.observe(document, { childList: true, subtree: true });
const init = () => {
ensureToggleButton();
process();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
setInterval(removeFirstColumnAttr, 5000);
})();