您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Converts URLs into clickable links in descriptions and comments on YouTube Shorts.
当前为
- // ==UserScript==
- // @name YouTube Shorts Linkify
- // @namespace http://tampermonkey.net/
- // @version 1.5
- // @license GPL-3.0-or-later
- // @description Converts URLs into clickable links in descriptions and comments on YouTube Shorts.
- // @match https://www.youtube.com/shorts/*
- // @icon https://www.google.com/s2/favicons?domain=www.youtube.com&sz=64
- // @grant none
- // ==/UserScript==
- (function() {
- 'use strict';
- const DEBUG = false;
- function log(...args) {
- if (DEBUG) console.log('[YT Shorts Linkify]', ...args);
- }
- function addStyles() {
- const style = document.createElement('style');
- style.textContent = `
- a.yt-short-linkify {
- color: inherit !important; /* Matches surrounding text color */
- text-decoration: none !important;
- cursor: pointer;
- }
- a.yt-short-linkify:hover,
- a.yt-short-linkify:focus,
- a.yt-short-linkify:active {
- color: inherit !important;
- text-decoration: underline !important; /* Underline on hover */
- outline: none !important;
- }
- `;
- document.head.appendChild(style);
- log("Styles added.");
- }
- function initLinkify() {
- const policy = window.trustedTypes ? trustedTypes.createPolicy('ytShortsLinkify', {
- createHTML: input => input
- }) : null;
- }
- const urlRegex = /(?<=\s|^|\()((?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*))\b(?!\.)/g;
- function isSafeToLinkify(node) {
- let parent = node.parentNode;
- while (parent && parent !== document.body) {
- if (parent.nodeName === 'A' || parent.nodeName === 'SCRIPT' || parent.nodeName === 'STYLE') {
- return false;
- }
- parent = parent.parentNode;
- }
- return true;
- }
- function createLinkElement(url) {
- const a = document.createElement('a');
- a.className = 'yt-short-linkify';
- a.href = /^https?:\/\//i.test(url) ? url : 'https://' + url;
- a.target = '_blank';
- a.rel = 'noopener noreferrer';
- a.textContent = /^https?:\/\//i.test(url) ? url : 'https://' + url;
- return a;
- }
- function linkifyTextNode(textNode) {
- const text = textNode.nodeValue;
- if (!text || !/\.[a-zA-Z]{2}/.test(text)) return;
- urlRegex.lastIndex = 0;
- if (!urlRegex.test(text)) return;
- log("Linkifying text node:", text.substring(0, 50) + "...");
- const fragment = document.createDocumentFragment();
- let lastIndex = 0;
- let match;
- urlRegex.lastIndex = 0;
- while ((match = urlRegex.exec(text)) !== null) {
- const url = match[1];
- const index = match.index;
- if (index > lastIndex) {
- fragment.appendChild(document.createTextNode(text.substring(lastIndex, index)));
- }
- const link = createLinkElement(url);
- fragment.appendChild(link);
- lastIndex = index + match[0].length;
- }
- if (lastIndex < text.length) {
- fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
- }
- textNode.parentNode.replaceChild(fragment, textNode);
- log("Text node replaced.");
- }
- function linkifyElement(element) {
- if (!element || element.nodeType !== Node.ELEMENT_NODE || !element.isConnected) {
- log("Skipping linkifyElement for invalid/disconnected element:", element);
- return;
- }
- log("Scanning element for text nodes:", element.tagName);
- const treeWalker = document.createTreeWalker(
- element,
- NodeFilter.SHOW_TEXT,
- {
- acceptNode: function(node) {
- if (node.nodeValue.trim() !== '' && isSafeToLinkify(node)) {
- urlRegex.lastIndex = 0;
- if (urlRegex.test(node.nodeValue)) {
- return NodeFilter.FILTER_ACCEPT;
- }
- }
- return NodeFilter.FILTER_REJECT;
- }
- }
- );
- const nodesToProcess = [];
- let currentNode;
- while ((currentNode = treeWalker.nextNode())) {
- nodesToProcess.push(currentNode);
- }
- if (nodesToProcess.length > 0) {
- log(`Found ${nodesToProcess.length} text nodes to process in`, element.tagName);
- nodesToProcess.forEach(linkifyTextNode);
- } else {
- log("No relevant text nodes found in", element.tagName);
- }
- }
- function setupObservers() {
- log("Setting up observers...");
- const observer = new MutationObserver(mutations => {
- requestAnimationFrame(() => {
- let addedNodes = new Set();
- let charDataNodes = new Set();
- mutations.forEach(mutation => {
- if (mutation.type === 'childList') {
- mutation.addedNodes.forEach(node => {
- if (node.nodeType === Node.ELEMENT_NODE && node.isConnected) {
- addedNodes.add(node);
- } else if (node.nodeType === Node.TEXT_NODE && node.parentNode?.isConnected) {
- charDataNodes.add(node.parentNode);
- }
- });
- } else if (mutation.type === 'characterData') {
- if (mutation.target.parentNode?.isConnected) {
- charDataNodes.add(mutation.target.parentNode);
- }
- }
- });
- addedNodes.forEach(node => {
- if (node.isConnected) {
- linkifyElement(node);
- }
- });
- charDataNodes.forEach(node => {
- if (node.isConnected && node !== document.body && node !== document.documentElement) {
- linkifyElement(node);
- }
- });
- });
- });
- observer.observe(document.body, { childList: true, subtree: true, characterData: true });
- log("MutationObserver attached to body.");
- document.querySelectorAll('body *').forEach(el => {
- if (el.isConnected && el.textContent?.trim() && /\.[a-zA-Z]{2}/.test(el.textContent)) {
- if (!el.matches('a') && !el.closest('a')) {
- linkifyElement(el);
- }
- }
- });
- log("Initial scan complete.");
- }
- function init() {
- log("Initializing script...");
- initLinkify();
- addStyles();
- setupObservers();
- log("Script initialized.");
- }
- if (document.readyState === 'loading') {
- window.addEventListener('load', init);
- } else {
- init();
- }
- })();