您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Preview media links including shortened URLs
当前为
// ==UserScript== // @name Previewer Media on Chats 1.2.31 // @namespace http://tampermonkey.net/ // @version 1.2.31 // @description Preview media links including shortened URLs // @author Gullampis810 // @license MIT // @grant GM_xmlhttpRequest // @match https://www.twitch.tv/* // @match https://grok.com/* // @match https://*.imgur.com/* // @match https://7tv.app/* // @icon https://yt3.googleusercontent.com/ytc/AOPolaS0epA6kuqQqudVFRN0l9aJ2ScCvwK0YqC7ojbU=s900-c-k-c0x00ffffff-no-rj // ==/UserScript== (function() { 'use strict'; // Функция для проверки типа файла по расширению или домену function getFileType(url) { const cleanUrl = url.split('?')[0]; const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.gifv']; const imageExtensions = ['.png', '.jpg', '.jpeg', '.svg', '.gif', '.webp', '.avif']; const extension = cleanUrl.substring(cleanUrl.lastIndexOf('.')).toLowerCase(); if (videoExtensions.includes(extension)) return 'video'; if (imageExtensions.includes(extension)) return 'image'; // Добавляем поддержку gachi.gay if (url.includes('gachi.gay')) { return 'image'; // По умолчанию считаем изображением, уточняется через Content-Type } // Поддержка Imgur if (url.includes('imgur.com') || url.includes('i.imgur.com')) { if (extension === '.gifv') return 'video'; if (imageExtensions.includes(extension)) return 'image'; return 'image'; // По умолчанию Imgur — изображение } if (url.includes('emote') || url.includes('cdn.7tv.app') || url.includes('7tv.app/emotes')) return 'image'; return null; } // Функция для преобразования ссылки 7TV в прямую ссылку на изображение function transform7TVUrl(url) { if (url.includes('7tv.app/emotes')) { const emoteIdMatch = url.match(/7tv\.app\/emotes\/([a-zA-Z0-9]+)/); if (emoteIdMatch && emoteIdMatch[1]) { const emoteId = emoteIdMatch[1]; return `https://cdn.7tv.app/emote/${emoteId}/4x.webp`; } } return url; } // Функция для определения типа файла по Content-Type function getFileTypeFromContentType(contentType) { if (!contentType) return null; if (contentType.includes('video')) return 'video'; if (contentType.includes('image')) return 'image'; return null; } // Функция для разрешения сокращенных ссылок и получения Content-Type async function resolveShortUrl(url) { try { const response = await fetch(url, { method: 'HEAD', redirect: 'follow', headers: { 'User-Agent': 'Mozilla/5.0 (compatible; PreWatcher/1.2.4)' } }); const contentType = response.headers.get('Content-Type'); return { resolvedUrl: response.url, contentType }; } catch (error) { console.error('Ошибка при разрешении ссылки:', error); return { resolvedUrl: url, contentType: null }; } } // Функция для извлечения медиа из поста Reddit async function extractMediaFromReddit(url) { try { const response = await fetch(url); const text = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(text, 'text/html'); const links = doc.querySelectorAll('a[href]'); for (let link of links) { const href = link.getAttribute('href'); const type = getFileType(href); if (type) return { url: href, type }; } const video = doc.querySelector('video source[src]'); if (video) return { url: video.getAttribute('src'), type: 'video' }; const img = doc.querySelector('img[src]'); if (img) return { url: img.getAttribute('src'), type: 'image' }; return null; } catch (error) { console.error('Ошибка при извлечении медиа:', error); return null; } } // Функция для создания элемента предпросмотра function createPreviewElement(url, type) { const container = document.createElement('div'); container.style.position = 'fixed'; container.style.zIndex = '1000'; container.style.background = '#0e1a1a'; container.style.border = '1px solid #ccc'; container.style.padding = '5px'; container.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)'; container.style.display = 'none'; container.style.left = '85%'; // Центрируем по горизонтали container.style.top = '50%'; // Центрируем по вертикали container.style.transform = 'translate(-50%, -50%)'; // Точное центрирование let element; if (type === 'video') { element = document.createElement('video'); element.src = url; element.controls = true; element.muted = true; element.style.maxWidth = '400px'; element.style.maxHeight = '300px'; } else if (type === 'image') { element = document.createElement('img'); element.src = url; element.style.maxWidth = '400px'; element.style.maxHeight = '440px'; element.draggable = false; } else { element = document.createElement('div'); element.textContent = 'Предпросмотр недоступен'; element.style.color = '#fff'; element.style.padding = '10px'; } if (element) { container.appendChild(element); } return container; } // Основная функция обработки ссылок async function processLinks() { const messages = document.querySelectorAll( '.message, .PostContent-imageWrapper, .PostContent-imageWrapper-rounded, .Gallery-Content--media, .imageContainer' ); for (let message of messages) { let targetLink = message.querySelector('a[href]') || message.querySelector('img[src]:not(.image-placeholder)') || message.querySelector('img[src].image-placeholder'); if (!targetLink || targetLink.dataset.processed) continue; if (targetLink.tagName === 'IMG' && targetLink.getAttribute('src').includes('cdn.7tv.app')) { if (message.querySelector('a[href]')) continue; } let url = targetLink.tagName === 'IMG' ? targetLink.getAttribute('src') : targetLink.getAttribute('href'); let fileType = getFileType(url); let mediaUrl = url; let contentType = null; // Преобразуем ссылку 7TV if (url.includes('7tv.app/emotes')) { mediaUrl = transform7TVUrl(url); fileType = getFileType(mediaUrl); } // Разрешаем ссылки и проверяем Content-Type для gachi.gay и других случаев if (!fileType || url.includes('gachi.gay') || url.includes('kappa.lol') || url.includes('t.co') || url.includes('bit.ly')) { const { resolvedUrl, contentType: fetchedContentType } = await resolveShortUrl(url); mediaUrl = resolvedUrl; contentType = fetchedContentType; fileType = getFileType(mediaUrl) || getFileTypeFromContentType(contentType); } // Специальная обработка Imgur if (url.includes('imgur.com') && !fileType) { const { resolvedUrl, contentType: fetchedContentType } = await resolveShortUrl(url); mediaUrl = resolvedUrl; contentType = fetchedContentType; fileType = getFileType(mediaUrl) || getFileTypeFromContentType(contentType); } // Обработка Reddit if (mediaUrl.includes('reddit.com') && !fileType) { const media = await extractMediaFromReddit(mediaUrl); if (media) { mediaUrl = media.url; fileType = media.type; } } if (!fileType) continue; const preview = createPreviewElement(mediaUrl, fileType); document.body.appendChild(preview); message.addEventListener('mouseenter', () => { preview.style.display = 'block'; if (fileType === 'video') preview.querySelector('video')?.play(); }); message.addEventListener('mouseleave', () => { preview.style.display = 'none'; if (fileType === 'video') { const video = preview.querySelector('video'); video?.pause(); video.currentTime = 0; } }); targetLink.dataset.processed = 'true'; } } // Инициализация document.addEventListener('DOMContentLoaded', () => processLinks()); const observer = new MutationObserver(() => processLinks()); observer.observe(document.body, { childList: true, subtree: true }); window.previewLinks = processLinks; const style = document.createElement('style'); style.textContent = ` a[href], img[src] { position: relative; } `; document.head.appendChild(style); })();