// ==UserScript==
// @name YouTube No-Shorts
// @name:zh-CN YouTube No-Shorts
// @name:zh-TW YouTube No-Shorts
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Hide all Shorts/Short Videos, older browsers are not supported. Added toggle function, memory function and optional icon display.
// @description:zh-CN 隐藏所有 Shorts/短视频,不支持旧浏览器。添加了开关功能记忆功能和可选图标显示。
// @description:zh-TW 隐藏所有 Shorts/短视频,不支持旧浏览器。添加了开关功能记忆功能和可选图标显示。
// @author dogchild
// @match https://www.youtube.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-start
// @license MIT
// ==/UserScript==
(function () {
"use strict";
// 调试模式开关,设为false可减少日志输出
const DEBUG = false;
// 调试日志函数
function debugLog(message) {
if (DEBUG) console.log(`[Anti-Shorts] ${message}`);
}
// 检测浏览器语言并返回相应的菜单命令文本
function getMenuCommandTexts() {
const browserLang = navigator.language || '';
debugLog(`检测到浏览器语言: ${browserLang}`);
const isChinese = browserLang.startsWith('zh');
return isChinese ? {
enableIcon: "启动图标",
disableIcon: "禁用图标"
} : {
enableIcon: "Show icon",
disableIcon: "Hide icon"
};
}
// 获取菜单命令文本
const menuTexts = getMenuCommandTexts();
// 检查油猴选项是否启用
const enableAntiShorts = GM_getValue("enableAntiShorts", true);
// 如果选项关闭,直接应用CSS隐藏shorts并不显示按钮
if (!enableAntiShorts) {
debugLog("油猴选项已关闭,默认隐藏所有Shorts");
// 立即应用CSS规则
const style = document.createElement("style");
style.id = "__anti_shorts_css_forced";
style.textContent = `
ytd-rich-grid-media:has(a[href*="/shorts/"]),
ytd-video-renderer:has(a[href*="/shorts/"]),
ytd-grid-video-renderer:has(a[href*="/shorts/"]),
ytd-rich-item-renderer:has(a[href*="/shorts/"]),
ytd-compact-video-renderer:has(a[href*="/shorts/"]),
ytd-playlist-video-renderer:has(a[href*="/shorts/"]),
ytd-rich-grid-row:has(a[href*="/shorts/"]),
grid-shelf-view-model:has(ytm-shorts-lockup-view-model),
grid-shelf-view-model:has(ytm-shorts-lockup-view-model-v2),
ytd-reel-shelf-renderer,
ytd-rich-shelf-renderer[is-shorts],
ytd-shelf-renderer {
display: none !important;
}
`;
// 使用更早的插入方式
if (document.head) {
document.head.appendChild(style);
} else {
// 如果head还不存在,等待它可用
const observer = new MutationObserver(() => {
if (document.head) {
document.head.appendChild(style);
observer.disconnect();
}
});
observer.observe(document, { childList: true, subtree: true });
}
// 注册(不可用)菜单命令,允许用户重新启用图标
GM_registerMenuCommand(menuTexts.enableIcon, () => {
GM_setValue("enableAntiShorts", true);
location.reload();
});
return; // 不继续执行其余代码
}
// CSS规则,用于隐藏Shorts
const cssHas = `
ytd-rich-grid-media:has(a[href*="/shorts/"]),
ytd-video-renderer:has(a[href*="/shorts/"]),
ytd-grid-video-renderer:has(a[href*="/shorts/"]),
ytd-rich-item-renderer:has(a[href*="/shorts/"]),
ytd-compact-video-renderer:has(a[href*="/shorts/"]),
ytd-playlist-video-renderer:has(a[href*="/shorts/"]),
ytd-rich-grid-row:has(a[href*="/shorts/"]),
grid-shelf-view-model:has(ytm-shorts-lockup-view-model),
grid-shelf-view-model:has(ytm-shorts-lockup-view-model-v2),
ytd-reel-shelf-renderer,
ytd-rich-shelf-renderer[is-shorts],
ytd-shelf-renderer {
display: none !important;
}
`;
// 创建样式元素
const styleElement = document.createElement("style");
styleElement.id = "__anti_shorts_css";
styleElement.textContent = cssHas;
// 按钮样式和通知样式(合并到一个样式元素中)
const uiStyleElement = document.createElement("style");
uiStyleElement.textContent = `
#anti-shorts-toggle {
margin-left: 6px;
padding: 0 8px;
height: 28px;
border-radius: 14px;
border: 1px solid transparent;
background-color: transparent;
font-size: 13px;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
transition: all 0.2s;
white-space: nowrap;
min-width: 85px;
justify-content: center;
color: var(--yt-spec-text-primary);
}
#anti-shorts-toggle:hover {
background-color: var(--yt-spec-badge-chip-background-hover);
}
#anti-shorts-toggle.on {
background-color: transparent;
color: var(--yt-spec-text-primary);
}
#anti-shorts-toggle.on:hover {
background-color: var(--yt-spec-badge-chip-background-hover);
}
.anti-shorts-notification {
position: fixed;
bottom: 20px;
right: 20px;
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
z-index: 9999;
opacity: 0;
transition: opacity 0.3s;
}
.anti-shorts-notification.show {
opacity: 1;
}
`;
// 缓存和状态管理
const state = {
button: null,
notification: null,
settings: {},
lastUrl: location.href,
buttonCheckInterval: null,
spaObserver: null,
domObserver: null,
pageType: null,
isInitialized: false
};
// 等待head元素可用
function waitForHead(callback) {
if (document.head) {
callback();
} else {
const observer = new MutationObserver(() => {
if (document.head) {
observer.disconnect();
callback();
}
});
observer.observe(document, { childList: true, subtree: true });
}
}
// 初始化样式
waitForHead(() => {
document.head.appendChild(uiStyleElement);
});
// 注册(不可用)菜单命令,允许用户禁用图标
GM_registerMenuCommand(menuTexts.disableIcon, () => {
GM_setValue("enableAntiShorts", false);
location.reload();
});
// 显示通知(复用通知元素)
function showNotification(message) {
// 如果通知已存在,先移除
if (state.notification) {
state.notification.classList.remove('show');
setTimeout(() => {
if (state.notification && state.notification.parentNode) {
state.notification.parentNode.removeChild(state.notification);
state.notification = null;
}
}, 300);
}
// 创建新通知
state.notification = document.createElement('div');
state.notification.className = 'anti-shorts-notification';
state.notification.textContent = message;
document.body.appendChild(state.notification);
// 显示通知
requestAnimationFrame(() => {
if (state.notification) {
state.notification.classList.add('show');
}
});
// 3秒后隐藏通知
setTimeout(() => {
if (state.notification) {
state.notification.classList.remove('show');
setTimeout(() => {
if (state.notification && state.notification.parentNode) {
state.notification.parentNode.removeChild(state.notification);
state.notification = null;
}
}, 300);
}
}, 3000);
}
// 检测当前页面类型(缓存结果)
function getPageType() {
if (state.pageType) return state.pageType;
const path = window.location.pathname;
const search = window.location.search;
if (path === "/" || path === "/feed/subscriptions") {
state.pageType = "home";
} else if (path === "/results" || search.includes("search_query")) {
state.pageType = "search";
} else {
state.pageType = "other";
}
return state.pageType;
}
// 从localStorage获取设置,使用缓存减少读取
function getHideShortsSetting(pageType) {
// 如果缓存中有设置,直接返回
if (state.settings[pageType] !== undefined) {
return state.settings[pageType];
}
const key = `hideShorts_${pageType}`;
const storedValue = localStorage.getItem(key);
// 如果没有存储过值,默认为false(显示Shorts,即off状态)
if (storedValue === null) {
debugLog(`首次访问${pageType}页面,默认显示Shorts (off状态)`);
state.settings[pageType] = false;
return false;
}
// 解析存储的值并缓存
const hideShorts = storedValue === "true";
state.settings[pageType] = hideShorts;
debugLog(`从记忆中读取${pageType}页面设置: ${hideShorts ? '隐藏' : '显示'} Shorts`);
return hideShorts;
}
// 保存设置到localStorage并更新缓存
function saveHideShortsSetting(pageType, hide) {
const key = `hideShorts_${pageType}`;
localStorage.setItem(key, hide);
state.settings[pageType] = hide; // 更新缓存
debugLog(`已保存${pageType}页面设置: ${hide ? '隐藏' : '显示'} Shorts`);
// 显示保存成功的通知
const pageTypeName = pageType === "home" ? "主页" : "搜索页";
showNotification(`已保存设置:${pageTypeName} Shorts ${hide ? '已关闭' : '已开启'}`);
}
// 应用或移除CSS规则
function applyShortsSetting() {
const pageType = getPageType();
const hideShorts = getHideShortsSetting(pageType);
if (hideShorts) {
if (!document.getElementById("__anti_shorts_css")) {
document.documentElement.appendChild(styleElement);
debugLog(`已应用CSS规则,${pageType}页面Shorts已隐藏`);
}
} else {
const existingStyle = document.getElementById("__anti_shorts_css");
if (existingStyle) {
existingStyle.remove();
debugLog(`已移除CSS规则,${pageType}页面Shorts已显示`);
}
}
// 更新按钮文本
updateButtonText();
}
// 更新按钮文本和样式
function updateButtonText() {
// 使用缓存的按钮元素
const button = state.button || document.getElementById("anti-shorts-toggle");
if (button) {
state.button = button; // 缓存按钮元素
const pageType = getPageType();
const hideShorts = getHideShortsSetting(pageType);
// 统一使用"shorts: on/off"格式
const buttonText = hideShorts ? "shorts: off" : "shorts: on";
button.textContent = buttonText;
// 根据状态添加或移除on类
button.classList.toggle("on", hideShorts);
}
}
// 创建开关按钮
function createToggleButton() {
const button = document.createElement("button");
button.id = "anti-shorts-toggle";
// 设置初始文本
const pageType = getPageType();
const hideShorts = getHideShortsSetting(pageType);
const buttonText = hideShorts ? "shorts: off" : "shorts: on";
button.textContent = buttonText;
button.classList.toggle("on", hideShorts);
button.addEventListener("click", () => {
const pageType = getPageType();
const hideShorts = getHideShortsSetting(pageType);
saveHideShortsSetting(pageType, !hideShorts);
applyShortsSetting();
});
debugLog(`已创建控制按钮,初始文本: ${buttonText}`);
return button;
}
// 查找并添加按钮到YouTube顶栏
function addButtonToHeader() {
// 如果按钮已存在,直接返回
if (document.getElementById("anti-shorts-toggle")) {
return true;
}
// 尝试多种可能的选择器,按优先级排序
const possibleSelectors = [
"#end",
"#buttons",
"ytd-masthead #end",
"ytd-masthead #buttons"
];
for (const selector of possibleSelectors) {
const header = document.querySelector(selector);
if (header) {
const button = createToggleButton();
header.appendChild(button);
state.button = button; // 缓存按钮元素
debugLog(`已添加控制按钮到YouTube顶栏 (${selector})`);
return true;
}
}
return false;
}
// 清理资源
function cleanup() {
if (state.buttonCheckInterval) {
clearInterval(state.buttonCheckInterval);
state.buttonCheckInterval = null;
}
if (state.spaObserver) {
state.spaObserver.disconnect();
state.spaObserver = null;
}
if (state.domObserver) {
state.domObserver.disconnect();
state.domObserver = null;
}
}
// 处理页面导航
function handleNavigation() {
// 重置页面类型缓存
state.pageType = null;
// 应用设置
applyShortsSetting();
// 检查按钮是否存在,如果不存在则重新添加
if (!document.getElementById("anti-shorts-toggle")) {
debugLog("导航后按钮不存在,尝试重新添加");
addButtonToHeader();
}
}
// 初始化
function init() {
// 防止重复初始化
if (state.isInitialized) return;
state.isInitialized = true;
debugLog("开始初始化");
debugLog(`使用浏览器语言: ${navigator.language}`);
// 应用初始设置
applyShortsSetting();
// 尝试添加按钮
let buttonAdded = addButtonToHeader();
// 如果按钮未添加成功,观察DOM变化
if (!buttonAdded) {
debugLog("按钮未添加成功,将观察DOM变化");
state.domObserver = new MutationObserver(() => {
if (!document.getElementById("anti-shorts-toggle") && addButtonToHeader()) {
debugLog("通过DOM观察成功添加按钮");
state.domObserver.disconnect();
state.domObserver = null;
}
});
// 只观察body的子元素变化,而不是整个子树
state.domObserver.observe(document.body, {
childList: true
});
// 30秒后停止观察
setTimeout(() => {
if (state.domObserver) {
state.domObserver.disconnect();
state.domObserver = null;
if (!document.getElementById("anti-shorts-toggle")) {
debugLog("30秒内未找到合适位置添加按钮");
}
}
}, 30000);
}
// 监听URL变化,以便在页面导航时更新设置
window.addEventListener("popstate", handleNavigation);
// 监听YouTube的SPA导航
state.spaObserver = new MutationObserver(() => {
const url = location.href;
if (url !== state.lastUrl) {
state.lastUrl = url;
debugLog("检测到YouTube SPA导航,更新设置");
// 使用requestAnimationFrame确保DOM已更新
requestAnimationFrame(handleNavigation);
}
});
// 只观察documentElement的子元素变化,减少观察范围
state.spaObserver.observe(document.documentElement, {
childList: true,
subtree: false
});
// 减少按钮检查频率,从5秒改为10秒
state.buttonCheckInterval = setInterval(() => {
const button = document.getElementById("anti-shorts-toggle");
if (button && (!button.textContent || button.textContent.trim() === "")) {
debugLog("检测到按钮文本为空,更新");
updateButtonText();
}
}, 10000);
// 页面卸载时清理资源
window.addEventListener("beforeunload", cleanup);
debugLog("初始化完成,记忆功能已启用");
}
// 确保DOM加载完成后初始化
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
debugLog("现代浏览器模式已启用 (CSS :has())");
})();