Skip to content

Instantly share code, notes, and snippets.

@zeyus
Last active January 2, 2026 08:59
Show Gist options
  • Select an option

  • Save zeyus/dae59e6a4d709bdb8a041a45634450cc to your computer and use it in GitHub Desktop.

Select an option

Save zeyus/dae59e6a4d709bdb8a041a45634450cc to your computer and use it in GitHub Desktop.
Mark + add a border around youtube thumbnails based on video length, disable shorts: Violentmonkey / Greasemonkey / userscript
// ==UserScript==
// @name Long Videos are Best Videos
// @namespace zeyus
// @match https://www.youtube.com/*
// @grant GM.registerMenuCommand
// @grant GM.getValues
// @grant GM.setValues
// @inject-into auto
// @version 2.3
// @author zeyus
// @homepageURL https://www.zeyus.com/
// @supportURL https://github.com/zeyus
// @downloadURL https://gist.github.com/zeyus/dae59e6a4d709bdb8a041a45634450cc/raw/YTLongForm.user.js
// @description A youtube helper script for people who don't like shorts and who like long-form content. It highlights videos over a specified length, and hides the shorts menu item and shorts from the feed.
// ==/UserScript==
// Configuration for video length, and other options
let userConfig = undefined;
async function getConfig() {
if (userConfig) return userConfig;
userConfig = await GM.getValues({
longVideoLengthM: 55,
disableShorts: true,
settingsInitialized: false,
});
console.log(userConfig);
if (!userConfig.settingsInitialized) {
GM.setValues({
longVideoLengthM: 55,
disableShorts: true,
settingsInitialized: true,
});
userConfig.settingsInitialized = true;
}
return userConfig;
}
(async () => {
const c = await getConfig();
const longVideoOnClick = function() {
let longVideoLengthM = prompt("Enter the number of minutes for a video to be considered long", userConfig.longVideoLengthM);
if (longVideoLengthM === null) return;
// try making sure it's a number
if (isNaN(longVideoLengthM)) {
alert("Please enter a valid number");
return;
}
longVideoLengthM = parseFloat(longVideoLengthM);
userConfig.longVideoLengthM = longVideoLengthM;
GM.setValues({
longVideoLengthM: longVideoLengthM,
});
GM.registerMenuCommand("Long video (mins): " + longVideoLengthM, longVideoOnClick, {id: "len"});
}
GM.registerMenuCommand("Long video (mins): " + c.longVideoLengthM, longVideoOnClick, {id: "len"});
let shorts_status = c.disableShorts ? "(✅)" : "(❌)";
const shortsOnClick = async function() {
let disableShorts = confirm("Do you want to disable shorts?");
userConfig.disableShorts = disableShorts;
GM.setValues({
disableShorts: disableShorts,
});
shorts_status = disableShorts ? "(✅)" : "(❌)";
GM.registerMenuCommand("Disable Shorts " + shorts_status, shortsOnClick, {id: "shorts"});
};
GM.registerMenuCommand("Disable Shorts " + shorts_status, shortsOnClick, {id: "shorts"});
})();
const markLongVideo = function(lengthEl) {
const parentAel = lengthEl.closest("a");
lengthEl.classList.add("long-video");
if (parentAel) {
parentAel.style.border = "2px solid red";
}
};
const markShortVideo = function(lengthEl) {
lengthEl.classList.add("short-video");
};
const parseLength = function(lengthStr) {
const timeParts = lengthStr.trim().split(':');
const parsedLength = {
h: 0,
m: 0,
s: 0,
};
if (timeParts.constructor !== Array || timeParts.length > 3 || timeParts.length < 1) {
return parsedLength;
}
let s = parseInt(timeParts.pop());
if (isNaN(s)) return parsedLength;
parsedLength.s = s;
if (timeParts.length === 0) return parsedLength;
let m = parseInt(timeParts.pop());
if (isNaN(m)) return parsedLength;
parsedLength.m = m;
if (timeParts.length === 0) return parsedLength;
let h = parseInt(timeParts.pop());
if (isNaN(h)) return parsedLength;
parsedLength.h = h;
return parsedLength;
};
const updateLongVideos = async function() {
// This selector avoids re-checking already parsed videos
const c = await getConfig();
const videoLengths = document.querySelectorAll(
".yt-thumbnail-overlay-badge-view-model .yt-badge-shape__text:not(.long-video):not(.short-video)"
);
console.debug("Found " + videoLengths.length + " unchecked videos.");
// loop through unchecked videos
for (var i = 0; i < videoLengths.length; i++) {
const videoLength = videoLengths[i].innerText;
const parsedLength = parseLength(videoLength);
const parsedLengthM = parsedLength.m + parsedLength.h * 60;
// check the length and mark it as long or short
if (parsedLengthM >= c.longVideoLengthM) {
markLongVideo(videoLengths[i]);
} else {
markShortVideo(videoLengths[i]);
}
}
};
const hideShorts = async function() {
const c = await getConfig();
if (!c.disableShorts) return;
// fist hide shorts menu item
const sectionMenuEl = document.querySelector("#sections > ytd-guide-section-renderer");
// the one that contains an a with the title "Shorts" is the one to remove
if (sectionMenuEl) {
const shortsEl = sectionMenuEl.querySelector("a[title='Shorts']");
if (shortsEl) {
try {
shortsEl.closest("ytd-guide-entry-renderer").remove();
} catch (e) {
console.error("Error removing shorts menu item", e);
}
}
}
// now hide shorts videos in the feed
function getShorts() {
return document.querySelectorAll("ytd-rich-section-renderer [is-shorts]") || [];
}
while (getShorts().length > 0) {
getShorts().forEach((shortEl) => {
// remove parent ytd-rich-section-renderer
try {
shortEl.closest("ytd-rich-section-renderer").remove();
} catch (e) {
console.error("Error removing shorts video", e);
}
});
}
}
window.addEventListener("load", async function() {
console.debug("setting updateLongVideos to run every 5 seconds.");
// just run it every 5 seconds
try {
await hideShorts();
}
catch (e) {
console.error("Error hiding shorts", e);
}
try {
await updateLongVideos();
} catch (e) {
console.error("Error updating long videos", e);
}
setInterval(hideShorts, 5000);
setInterval(updateLongVideos, 5000);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment