您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows pictures and some videos right after the link, loads and expands comment threads.
当前为
// ==UserScript== // @name Reddit expand media and comments // @description Shows pictures and some videos right after the link, loads and expands comment threads. // @version 0.2.2 // @author wOxxOm // @namespace wOxxOm.scripts // @license MIT License // @match *://*.reddit.com/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect imgur.com // @connect gfycat.com // @connect streamable.com // @connect instagram.com // @connect ibb.co // @connect prntscr.com // @connect prnt.sc // ==/UserScript== const CLASS = 'reddit-inline-media'; const CLASS_ALBUM = CLASS + '-album'; const OVERFLOW_ATTR = 'data-overflow'; const MORE_SELECTOR = '[id^="moreComments-"] p, .morecomments a'; const REQUEST_THROTTLE_MS = location.hostname.startsWith('old.') ? 500 : 100; const RULES = [{ r: /^https?:\/\/(i\.)?imgur\.com\/(?:a|gallery)\/(\w+)$/i, s: 'https://imgur.com/ajaxalbums/getimages/$2/hit.json?all=true', q: json => Array.from(((json || {}).data || {}).images || []) .map(img => img && `https://i.imgur.com/${img.hash}${img.ext}`), }, { r: /^https?:\/\/(i\.)?imgur\.com\/\w+$/i, q: 'link[rel="image_src"], meta[name="twitter:player:stream"]', }, { r: /^https?:\/\/streamable\.com\/.+/i, q: 'video', }, { r: /^https?:\/\/gfycat\.com\/.+/i, q: 'source[src*=".webm"]', }, { r: /^https?:\/\/(www\.)?instagram\.com\/p\/[^/]+\/?$/i, q: 'meta[property="og:image"]', }, { r: /^https?:\/\/ibb\.co\/\w+$/i, q: 'meta[property="og:image"]', }, { r: /^https?:\/\/prntscr\.com\/(\w+)$/i, s: 'https://prnt.sc/$1', q: 'meta[property="og:image"]', xhr: true, }, { r: /^https?:\/\/prnt\.sc\/(\w+)$/i, q: 'meta[property="og:image"]', xhr: true, }, { r: /\.gifv(\?.*)?$/i, s: '.mp4', }, { // keep this one at the end of the list r: /\.(jpe?g|png|gif|webm|mp4)(\?.*)?$/i, }]; // language=CSS GM_addStyle(` .${CLASS} { max-width: 100%; display: block; } .${CLASS}[data-src] { padding-top: 400px; } .${CLASS}:hover { outline: 2px solid #3bbb62; } .${CLASS_ALBUM} { overflow-y: auto; max-height: calc(100vh - 100px); margin: .5em 0; } .${CLASS_ALBUM}[${OVERFLOW_ATTR}] { -webkit-mask-image: linear-gradient(white 75%, transparent); mask-image: linear-gradient(white 75%, transparent); } .${CLASS_ALBUM}[${OVERFLOW_ATTR}]:hover { -webkit-mask-image: none; mask-image: none; } .${CLASS_ALBUM} > :nth-child(n + 2) { margin-top: 1em; } `); const isChrome = navigator.userAgent.includes('Chrom'); const more = []; let scrollObserver = lazyCreateObserver(onScroll, {rootMargin: '200% 0px'}, obs => scrollObserver = obs); let albumObserver = lazyCreateObserver(onScroll, {rootMargin: '200px 0px'}, obs => albumObserver = obs); new MutationObserver(onMutation) .observe(document.body, {subtree: true, childList: true}); onMutation([{ addedNodes: [document.body] }]); function onMutation(mutations) { var items = []; var someElementsAdded = false; for (var i = 0, m; (m = mutations[i++]);) { for (var j = 0, added = m.addedNodes, node; (node = added[j++]);) { if (!node.localName) continue; someElementsAdded = true; if (node.localName === 'a') { var rule = findMatchingRule(node); if (rule) items.push(rule); continue; } if (!node.firstElementChild) continue; var aa = node.getElementsByTagName('a'); for (var k = 0, a; (a = aa[k++]);) { const data = findMatchingRule(a); if (data) items.push(data); } } } if (someElementsAdded) debounce(observeShowMore); if (items.length) setTimeout(maybeExpand, 0, items); } function onScroll(entries, observer) { for (const e of entries) { let el = e.target; if (el.localName === 'ins') { toggleAttribute(el.parentNode, OVERFLOW_ATTR, !e.isIntersecting); continue; } if (!e.isIntersecting) { const rect = e.boundingClientRect; if ((rect.bottom < -200 || rect.top > innerHeight + 200) && !el.dataset.src && el[GM_info.script.name]) { delete el[GM_info.script.name]; el.dataset.src = el.src; el.removeAttribute('src'); el.removeEventListener('load', unobserveOnLoad); } continue; } const isImage = el.localName === 'img'; if (isImage || el.localName === 'video') { el.src = el.dataset.src; el[GM_info.script.name] = {observer}; el.addEventListener(isImage ? 'load' : 'loadedmetadata', unobserveOnLoad); delete el.dataset.src; continue; } if (el.localName === 'a') { // switch to an unfocusable element to prevent the link // from stealing focus and scrolling the view const el2 = document.createElement('span'); el2.setAttribute('onclick', el.getAttribute('onclick')); el2.setAttribute('id', el.id); el.parentNode.replaceChild(el2, el); el = el2; } expandNextComment(el); } } function findMatchingRule(a) { var url = a.href; for (var i = 0; i < RULES.length; i++) { var rule = RULES[i]; var r = rule.r; if (typeof r === 'string') { if (!url.includes(r)) continue; } else { if (!r.test(url)) continue; var s = rule.s; if (s) url = url.replace(r, s); } return { a, url, q: rule.q, xhr: rule.xhr, }; } } function maybeExpand(items) { for (const item of items) { const {a, q} = item; const {href} = a; const text = a.textContent.trim(); if ( text && !a.getElementsByTagName('img')[0] && ( !text.startsWith('http') || !text.endsWith('...') || !/^https?:\/\/\S+?\.{3}$/.test(text) ) && !a.closest( '.scrollerItem,' + '[contenteditable="true"],' + `a[href="${href}"] + * a[href="${href}"],` + `img[src="${href}"] + * a[href="${href}"]`) && ( // don't process insides of a post except for its text !a.closest('[data-test-id="post-content"]') || a.closest('[data-click-id="text"]') ) ) { try { (q ? expandRemote : expand)(item); } catch (e) { // console.debug(e, item); } } } } function expand({a, url = a.href, isAlbum}, observer = scrollObserver) { const isVideo = /(webm|gifv|mp4)(\?.*)?$/i.test(url); const el = document.createElement(isVideo ? 'video' : 'img'); el.dataset.src = url; el.className = CLASS; a.insertAdjacentElement(isAlbum ? 'beforeEnd' : 'afterEnd', el); if (isVideo) { el.controls = true; el.preload = 'metadata'; if (isChrome) el.addEventListener('click', playOnClick); } observer.observe(el); return !isAlbum && el; } async function expandRemote(item) { const {url, q} = item; const r = await download(url); const isJSON = /^content-type:.*?json\s*$/mi.test(r.responseHeaders); const doc = isJSON ? tryJSONparse(r.response) : new DOMParser().parseFromString(r.response, 'text/html'); switch (typeof q) { case 'string': { if (!isJSON) expandRemoteFromSelector(doc, item); return; } case 'function': { let urls = await q(doc, r.response); if (urls && urls.length) { urls = Array.isArray(urls) ? urls : [urls]; expandFromUrls(urls, item); } return; } } } async function expandRemoteFromSelector(doc, {q, xhr, url, a}) { if (!doc) return; const el = doc.querySelector(q); if (!el) return; let imageUrl = el.href || el.src || el.content; if (!imageUrl) return; if (xhr) imageUrl = await downloadAsBase64({imageUrl, url}); if (imageUrl) expand({a, url: imageUrl}); } function expandFromUrls(urls, {a, url}) { let observer; const isAlbum = urls.length > 1; if (isAlbum) { observer = albumObserver; a = a.insertAdjacentElement('afterEnd', document.createElement('div')); a.className = CLASS_ALBUM; } for (const url of urls) { if (url) a = expand({a, url, isAlbum}, observer) || a; } if (isAlbum) { new IntersectionObserver(onScroll, {root: a}) .observe(a.appendChild(document.createElement('ins'))); } } function expandNextComment(el) { if (el) more.push(el); else more.shift(); if (more.length === 1 || !el && more.length) { more[0].dispatchEvent(new MouseEvent('click', {bubbles: true})); setTimeout(expandNextComment, REQUEST_THROTTLE_MS); } } function observeShowMore() { if (document.querySelector(MORE_SELECTOR)) { for (const el of document.querySelectorAll(MORE_SELECTOR)) { scrollObserver.observe(el); } } } function playOnClick(event, el, wasPaused) { if (!el) { setTimeout(playOnClick, 0, event, this, this.paused); } else if (el.paused === wasPaused) { wasPaused ? el.play() : el.pause(); } } function debounce(fn, timeout = 0, ...args) { clearTimeout(fn.__timeout); fn.__timeout = setTimeout(fn, timeout, ...args); } function tryJSONparse(str) { try { return JSON.parse(str); } catch (e) { return undefined; } } function download(options) { if (typeof options === 'string') options = {url: options}; return new Promise((resolve, reject) => { GM_xmlhttpRequest(Object.assign({ method: 'GET', onload: resolve, onerror: reject, }, options)); }); } async function downloadAsBase64({imageUrl, url}) { let blob = (await download({ url: imageUrl, headers: { 'Referer': url, }, responseType: 'blob', })).response; if (blob.type !== getMimeType(imageUrl)) blob = blob.slice(0, blob.size, getMimeType(imageUrl)); return new Promise(resolve => { Object.assign(new FileReader(), { onload: e => resolve(e.target.result) }).readAsDataURL(blob); }); } function getMimeType(url) { const ext = (url.match(/\.(\w+)(\?.*)?$|$/)[1] || '').toLowerCase(); return 'image/' + (ext === 'jpg' ? 'jpeg' : ext); } function toggleAttribute(el, name, state) { if (state && !el.hasAttribute(name)) el.setAttribute(name, ''); else el.removeAttribute(name); } function lazyCreateObserver(onIntersect, options, onCreate) { return new Proxy({}, { get(_target, k) { const observer = new IntersectionObserver(onIntersect, options); onCreate(observer); const v = observer[k]; return typeof v === 'function' ? v.bind(observer) : v; }, }); } function unobserveOnLoad() { this.removeEventListener('load', unobserveOnLoad); const {observer} = this[GM_info.script.name] || {}; if (observer) observer.unobserve(this); delete this[GM_info.script.name]; }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址