您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
youtube auto short video jmp to long video
// ==UserScript== // @name youtube-short-to-long // @namespace npm/vite-plugin-monkey // @version 1.1.2 // @author hzx // @description youtube auto short video jmp to long video // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @match https://www.youtube.com/* // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // ==/UserScript== (t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const e=document.createElement("style");e.textContent=t,document.head.append(e)})(" .mask{position:absolute;width:100%;height:100%;background-color:transparent;top:0;right:0;left:0;bottom:0;cursor:pointer} "); (function () { 'use strict'; const CornMenuManager = /* @__PURE__ */ (() => { const LOG_TAG = "CornMenuManager: "; let isLog = true; const callStack = []; function getCallStackString() { const sep = "\n "; return `callback:${sep}` + callStack.join(sep); } function log(msg, logMethod = console.log) { if (isLog) { logMethod(LOG_TAG + msg + "\n" + getCallStackString()); } } function logError(msg) { log(msg, console.error); } function logWarn(msg) { log(msg, console.warn); } function logWrapper(fnName, fn) { return () => { callStack.push(fnName); const result = fn(fnName); callStack.pop(); return result; }; } function logWrapperAndCall(fnName, fn) { return logWrapper(fnName, fn)(); } function isSwitchEntry(item) { return item && item.on && item.off; } const list = []; const idArr = []; const STORE_TAG = "MENU_MANAGER_STORE_TAG."; function setValue(key, value) { localStorage.setItem(STORE_TAG + key, value); } function getValue(key) { return localStorage.getItem(STORE_TAG + key); } function saveSwitchBooleanState(entry, state) { setValue(getEntryName(entry), state); } function getSwitchBooleanState(entry) { const storeValue = getValue(getEntryName(entry)); if (storeValue === null) { return null; } return storeValue === "true"; } function getEntryName(entry) { return entry["name"] || entry["on"]["name"] + entry["off"]["name"]; } function addEntry(entry) { logWrapper("addEntry(entry)", (fnName) => { if (!(typeof entry === "object")) { logError(`${fnName}: 请传入正确的 Menu Entry`); return; } if (!entry.callback) { logError(`${fnName}: callback 不能为空, 请传入正确的 Menu Entry`); return; } const nameEmptyHandler = () => { logError(`${fnName}: entry name 不能为空`); }; if (isSwitchEntry(entry)) { if (!entry.on.name || !entry.off.name) { nameEmptyHandler(); return; } if (entry.default === void 0) { entry.default = true; } let currState = getSwitchBooleanState(entry); if (currState === null) { saveSwitchBooleanState(entry, entry.default); currState = entry.default; } entry.callback(currState, true); if (currState) { entry.currEntry = entry.on; } else { entry.currEntry = entry.off; } entry.on.next = entry.off; entry.off.next = entry.on; } else { if (!entry.name) { nameEmptyHandler(); return; } } list.push(entry); })(); } function add(entries) { logWrapper("add(entries)", () => { if (!Array.isArray(entries)) { logError("add: 请传递数组, 添加单个请使用 addItem "); } for (const entry of entries) { addEntry(entry); } })(); } return { // 创建菜单 create(isInit = true) { logWrapper("create", (fnName) => { if (list.length === 0) { logWarn(`${fnName}: 未添加任何 要创建的菜单条目`); return; } for (const id of idArr) { GM_unregisterMenuCommand(id); } idArr.length = 0; list.forEach((entry, index) => { let targetName = entry.name; if (isSwitchEntry(entry)) { targetName = entry.currEntry.name; } const id = GM_registerMenuCommand(targetName, () => { if (isSwitchEntry(entry)) { entry.currEntry = entry.currEntry.next; let currValue = getSwitchBooleanState(entry); currValue = !currValue; saveSwitchBooleanState(entry, currValue); entry.callback(currValue, false); this.create(false); } else { entry.callback(); } }, entry.accessKey || null); idArr.push(id); }); })(); return this; }, // 添加要创建的菜单项 add(entryOrEntries) { logWrapperAndCall("add(entryOrEntries)", () => { if (Array.isArray(entryOrEntries)) { add(entryOrEntries); } else { addEntry(entryOrEntries); } }); return this; }, addAndCreate(entryOrEntries) { logWrapperAndCall("addAndCreate(entryOrEntries)", () => { this.add(entryOrEntries); this.create(); }); return this; }, disableLog() { isLog = false; return this; } }; })(); const elmGetter = function() { const win = window.unsafeWindow || document.defaultView || window; const doc = win.document; const listeners = /* @__PURE__ */ new WeakMap(); let mode = "css"; let $; const elProto = win.Element.prototype; const matches = elProto.matches || elProto.matchesSelector || elProto.webkitMatchesSelector || elProto.mozMatchesSelector || elProto.oMatchesSelector; const MutationObs = win.MutationObserver || win.WebkitMutationObserver || win.MozMutationObserver; function addObserver(target, callback) { const observer = new MutationObs((mutations) => { for (const mutation of mutations) { if (mutation.type === "attributes") { callback(mutation.target, "attr"); if (observer.canceled) return; } for (const node of mutation.addedNodes) { if (node instanceof Element) callback(node, "insert"); if (observer.canceled) return; } } }); observer.canceled = false; observer.observe(target, { childList: true, subtree: true, attributes: true, attributeOldValue: true }); return () => { observer.canceled = true; observer.disconnect(); }; } function addFilter(target, filter) { let listener = listeners.get(target); if (!listener) { listener = { filters: /* @__PURE__ */ new Set(), remove: addObserver(target, (el, reason) => listener.filters.forEach((f) => f(el, reason))) }; listeners.set(target, listener); } listener.filters.add(filter); } function removeFilter(target, filter) { const listener = listeners.get(target); if (!listener) return; listener.filters.delete(filter); if (!listener.filters.size) { listener.remove(); listeners.delete(target); } } function query(selector, options = {}) { let { parent, root, curMode, reason } = options; switch (curMode) { case "css": { if (reason === "attr") return matches.call(parent, selector) ? parent : null; const checkParent = parent !== root && matches.call(parent, selector); return checkParent ? parent : parent.querySelector(selector); } case "jquery": { if (reason === "attr") return $(parent).is(selector) ? $(parent) : null; const jNodes = $(parent !== root ? parent : []).add([...parent.querySelectorAll("*")]).filter(selector); return jNodes.length ? $(jNodes.get(0)) : null; } case "xpath": { const ownerDoc = parent.ownerDocument || parent; selector += "/self::*"; return ownerDoc.evaluate(selector, reason === "attr" ? root : parent, null, 9, null).singleNodeValue; } } } function queryAll(selector, options = {}) { let { parent, root, curMode, reason } = options; switch (curMode) { case "css": { if (reason === "attr") return matches.call(parent, selector) ? [parent] : []; const checkParent = parent !== root && matches.call(parent, selector); const result = parent.querySelectorAll(selector); return checkParent ? [parent, ...result] : [...result]; } case "jquery": { if (reason === "attr") return $(parent).is(selector) ? [$(parent)] : []; const jNodes = $(parent !== root ? parent : []).add([...parent.querySelectorAll("*")]).filter(selector); return $.map(jNodes, (el) => $(el)); } case "xpath": { const ownerDoc = parent.ownerDocument || parent; selector += "/self::*"; const xPathResult = ownerDoc.evaluate(selector, reason === "attr" ? root : parent, null, 7, null); const result = []; for (let i = 0; i < xPathResult.snapshotLength; i++) { result.push(xPathResult.snapshotItem(i)); } return result; } } } function isJquery(jq) { return jq && jq.fn && typeof jq.fn.jquery === "string"; } function getOne(selector, options = {}) { let { parent, timeout, onError, isPending, errEl: errEl2 } = options; const curMode = mode; return new Promise((resolve) => { const node = query( selector, { parent, root: parent, curMode } ); if (node) return resolve(node); let timer; const filter = (el, reason) => { const node2 = query( selector, { parent, root: parent, curMode } ); if (node2) { removeFilter(parent, filter); timer && clearTimeout(timer); resolve(node2); } }; addFilter(parent, filter); if (timeout > 0) { timer = setTimeout(() => { removeFilter(parent, filter); onError(selector); if (!isPending) { resolve(errEl2); } }, timeout); } }); } let errEl = document.createElement("div"); errEl.classList.add("no-found"); errEl.remove = () => { }; return { timeout: 0, onError: (selector) => { console.warn(`[elmGetter] [get失败] selector为: ${selector} 的查询超时`); }, isPending: true, errEl, get currentSelector() { return mode; }, /** * 异步的 querySelector * @param selector * @param options 一个对象 * - parent 父元素, 默认值是 document * - timeout 设置 get 的超时时间, 默认值是 elmGetter.timeout, 其值默认为 0 * - 如果该值为 0, 表示永不超时, 如果 selector 有误, 返回的 Promise 将永远 pending * - 如果该值不为 0, 表示等待多少毫秒, 和 setTimeout 单位一致 * - onError 超时后的失败回调, 参数为 selector, 默认值为 elmGetter.onError, 其默认行为是 console.warn 打印 selector * - isPending 超时后 Promise 是否仍然保持 pending, 默认值为 elmGetter.isPending, 其值默认为 true * - errEl 超时后 Promise 返回的值, 需要 isPending 为 false 才能有效, 默认值为 elmGetter.errorEl, 其值默认为一个 class 为一个 class 为 no-found 的元素 * @returns {Promise<Awaited<unknown>[]>|Promise<unknown>} */ get(selector, options = {}) { let { parent = doc, timeout = this.timeout, onError = this.onError, isPending = this.isPending, errEl: errEl2 = this.errEl } = options; options.parent = parent; options.timeout = timeout; options.onError = onError; options.isPending = isPending; options.errEl = errEl2; if (mode === "jquery" && parent instanceof $) parent = parent.get(0); if (Array.isArray(selector)) { return Promise.all(selector.map((s) => getOne(s, options))); } return getOne(selector, options); }, /** * 为父节点设置监听,所有符合选择器的元素(包括页面已有的和新插入的)都将被传给回调函数处理, * each方法适用于各种滚动加载的列表(如评论区),或者发生非刷新跳转的页面等 * @param selector * @param callback 回调函数, 只在每个元素上触发一次。 回调函数接收2个参数,第一个是符合选择器的元素,第二个表明该元素是否为新插入的(已有为false,插入为true) * @param options 一个对象 * - parent 父元素, 默认值是 document */ each(selector, callback, options = {}) { let { parent = doc } = options; if (mode === "jquery" && parent instanceof $) parent = parent.get(0); const curMode = mode; const refs = /* @__PURE__ */ new WeakSet(); for (const node of queryAll(selector, { parent, root: parent, curMode })) { refs.add(curMode === "jquery" ? node.get(0) : node); if (callback(node, false) === false) return; } const filter = (el, reason) => { for (const node of queryAll(selector, { parent: el, root: parent, curMode, reason })) { const _el = curMode === "jquery" ? node.get(0) : node; if (refs.has(_el)) break; refs.add(_el); if (callback(node, true) === false) { return removeFilter(parent, filter); } } }; addFilter(parent, filter); }, /** * 将html字符串解析为元素 * @param domString * @param options 一个对象 * - returnList 布尔值,是否返回以 id 作为索引的元素列表, 默认值为 false * - parent 父节点,将创建的元素添加到父节点末尾处, 如果不指定, 解析后的元素将 * @returns {Element|{}|null} 元素或对象,取决于returnList参数 */ create(domString, options = {}) { let { returnList = false, parent = null } = options; const template = doc.createElement("template"); template.innerHTML = domString; const node = template.content.firstElementChild; if (!node) return null; parent ? parent.appendChild(node) : node.remove(); if (returnList) { const list = {}; node.querySelectorAll("[id]").forEach((el) => list[el.id] = el); list[0] = node; return list; } return node; }, selector(desc) { switch (true) { case isJquery(desc): $ = desc; return mode = "jquery"; case (!desc || typeof desc.toLowerCase !== "function"): return mode = "css"; case desc.toLowerCase() === "jquery": for (const jq of [window.jQuery, window.$, win.jQuery, win.$]) { if (isJquery(jq)) { $ = jq; break; } } return mode = $ ? "jquery" : "css"; case desc.toLowerCase() === "xpath": return mode = "xpath"; default: return mode = "css"; } } }; }(); async function main() { createMenu(); } main(); function createMenu() { CornMenuManager.addAndCreate([ { default: true, callback(state, isInit) { if (!isInit) { location.reload(); } if (!state) { return; } async function processState() { await elmGetter.each(".shortsLockupViewModelHostEndpoint", (el) => { el.href = convertShortsToVideoLink(el.href); }); await elmGetter.each("ytm-shorts-lockup-view-model-v2", (el) => { let mask = document.createElement("a"); mask.className = "mask"; el.appendChild(mask); const aEl = el.querySelector(`a`); mask.href = aEl.href; }); } jmpToVideo(); processState(); }, on: { name: "自动跳转状态: 开启✅ (点我关闭)" }, off: { name: "自动跳转状态: 关闭❎ (点我开启)" } }, { name: "跳转到 Video", callback() { jmpToVideo(); } } ]); } function convertShortsToVideoLink(shortsUrl) { if (shortsUrl.toLowerCase().includes("/shorts/")) { return shortsUrl.replace("/shorts/", "/watch?v="); } else { return shortsUrl; } } function jmpToVideo() { const href = window.location.href; if (href.toLowerCase().includes("/shorts/")) { window.location.href = convertShortsToVideoLink(href); } } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址