您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows a preview of images linked in destiny.gg chat without opening new tabs. NSFW blur toggle + preserves autoscroll.
// ==UserScript== // @name DGG Auto Image Embed // @namespace boofus // @description Shows a preview of images linked in destiny.gg chat without opening new tabs. NSFW blur toggle + preserves autoscroll. // @match https://www.destiny.gg/embed/chat* // @icon https://cdn.destiny.gg/2.49.0/emotes/6296cf7e8ccd0.png // @version 10.1.1 // @author boofus (credits: legolas, vyneer) // @license MIT // @grant GM_xmlhttpRequest // @run-at document-end // @connect discordapp.net // @connect discordapp.com // @connect pbs.twimg.com // @connect polecat.me // @connect imgur.com // @connect gyazo.com // @connect redd.it // @connect i.4cdn.org // ==/UserScript== const imageRegex = /http.+(redd.it|pbs.twimg.com|(media|cdn)\.discordapp\.(net|com)|imgur\.com|gyazo\.com|polecat\.me|catbox\.moe|i\.4cdn\.org).+\.(png|jpe?g|gifv?)/gm; let overlay; // START STOLEN FROM VYNEER // START STOLEN FROM LEGOLAS (also from vyneer) class ConfigItem { constructor(keyName, defaultValue) { this.keyName = keyName; this.defaultValue = defaultValue; } } const configItems = { BlurNSFW : new ConfigItem("BlurNSFW", true), HideLink : new ConfigItem("HideLink", true), Block4cdn: new ConfigItem("Block4cdn", true) // NEW: default to blocking i.4cdn.org }; class Config { constructor(configKeys, keyPrefix) { this.configItems = configKeys; this.configKeyPrefix = keyPrefix; // Initialize with defaults if not present for (const [k, item] of Object.entries(configKeys)) { if (this[k] === undefined) this[k] = item.defaultValue; } } getFullKeyName(configKey) { return `${this.configKeyPrefix}${configKey}`; } save(configKey, value) { const fullKeyName = this.getFullKeyName(configKey); unsafeWindow.localStorage.setItem(fullKeyName, JSON.stringify(value)); } load(configKey) { const fullKeyName = this.getFullKeyName(configKey); const item = unsafeWindow.localStorage.getItem(fullKeyName); if (item != null) return JSON.parse(item); } } const config = new Config(configItems, "img-util."); // Sticky-bottom state must be global so image handler can read it let shouldStickToBottom = true; function getChatEl() { return unsafeWindow.document.getElementsByClassName("chat-lines")[0]; } function isAtBottom(el, threshold = 2) { return (el.scrollHeight - el.scrollTop - el.clientHeight) <= threshold; } function scrollToBottom(el) { requestAnimationFrame(() => { el.scrollTop = el.scrollHeight; }); } let addSettings = () => { let settingsArea = document.querySelector("#chat-settings-form"); // Settings Title let settingsTitle = document.createElement("h4"); settingsTitle.innerHTML = "D.GG Img Preview Settings"; // Blur Nsfw let blurNsfw = document.createElement("div"); blurNsfw.className = "form-group checkbox"; let blurNsfwLabel = document.createElement("label"); blurNsfwLabel.innerHTML = "Blur nsfw/nsfl images"; blurNsfw.appendChild(blurNsfwLabel); let blurNsfwCheck = document.createElement("input"); blurNsfwCheck.name = "blurNsfw"; blurNsfwCheck.type = "checkbox"; blurNsfwCheck.checked = config.BlurNSFW; blurNsfwCheck.addEventListener("change", () => { config.BlurNSFW = blurNsfwCheck.checked; }); blurNsfwLabel.prepend(blurNsfwCheck); // Hide Link let hideLink = document.createElement("div"); hideLink.className = "form-group checkbox"; let hideLinkLabel = document.createElement("label"); hideLinkLabel.innerHTML = "Hide Link from previewed messages"; hideLink.appendChild(hideLinkLabel); let hideLinkCheck = document.createElement("input"); hideLinkCheck.name = "HideLink"; hideLinkCheck.type = "checkbox"; hideLinkCheck.checked = config.HideLink; hideLinkCheck.addEventListener("change", () => { config.HideLink = hideLinkCheck.checked; }); hideLinkLabel.prepend(hideLinkCheck); // NEW: Toggle i.4cdn.org embedding let block4cdn = document.createElement("div"); block4cdn.className = "form-group checkbox"; let block4cdnLabel = document.createElement("label"); block4cdnLabel.innerHTML = "Disable 4Chan (i.4cdn.org) auto embeds"; block4cdn.appendChild(block4cdnLabel); let block4cdnCheck = document.createElement("input"); block4cdnCheck.name = "Block4cdn"; block4cdnCheck.type = "checkbox"; block4cdnCheck.checked = config.Block4cdn; // checked by default (disabled) block4cdnCheck.addEventListener("change", () => { config.Block4cdn = block4cdnCheck.checked; }); block4cdnLabel.prepend(block4cdnCheck); // Add to settings settingsArea.appendChild(settingsTitle); settingsArea.appendChild(blurNsfw); settingsArea.appendChild(hideLink); settingsArea.appendChild(block4cdn); console.log("[D.gg Img Preview] Settings Added"); }; // END STOLEN FROM VYNEER function waitForElm(selector) { // stolen from stack overflow return new Promise(resolve => { if (unsafeWindow.document.querySelector(selector)) { return resolve(unsafeWindow.document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (unsafeWindow.document.querySelector(selector)) { resolve(unsafeWindow.document.querySelector(selector)); observer.disconnect(); } }); observer.observe(unsafeWindow.document.body, { childList: true, subtree: true }); }); } let chatObserver = new MutationObserver(function(mutations) { if (mutations[0].addedNodes[0] && mutations[0].addedNodes[0].tagName == "DIV" && mutations[0].addedNodes[0].querySelector(".externallink")) { let links = mutations[0].addedNodes[0].querySelectorAll(".externallink "); links.forEach((el) => { // NEW: if blocking is ON and link is i.4cdn.org, skip embedding if (config.Block4cdn && /https?:\/\/i\.4cdn\.org\//i.test(el.href)) return; if (imageRegex.test(el.href)) { GM_xmlhttpRequest({ // get image data since csp blocks us from just using the url directly method: "GET", url: el.href, responseType: 'blob', onload: function(res) { var reader = new FileReader(); reader.readAsDataURL(res.response); reader.onloadend = function() { var base64data = reader.result; let PreviewImage = unsafeWindow.document.createElement("img"); PreviewImage.src = base64data; PreviewImage.style.maxHeight = "300px"; PreviewImage.style.maxWidth = "300px"; PreviewImage.style.marginLeft = "5px"; PreviewImage.style.marginBottom = "10px"; PreviewImage.style.marginTop = "10px"; PreviewImage.style.display = "block"; PreviewImage.style.cursor = "pointer"; let blurred = false; if (el.className.includes("nsfw") && config.BlurNSFW || el.className.includes("nsfl") && config.BlurNSFW) { PreviewImage.style.filter = "blur(15px)"; blurred = true; } // PEPE WINS PreviewImage.onclick = () => { if (blurred) { PreviewImage.style.filter = "blur(0px)"; blurred = false; } else { overlay.style.display = "flex"; let PreviewImg = unsafeWindow.document.createElement("img"); PreviewImg.src = base64data; PreviewImg.style.maxHeight = "70%"; PreviewImg.style.maxWidth = "70%"; PreviewImg.style.display = "block"; PreviewImg.style.position = "relative"; overlay.appendChild(PreviewImg); let openOriginal = document.createElement("a"); openOriginal.href = el.href; openOriginal.innerHTML = "Open Original"; openOriginal.target = "_blank"; openOriginal.style.marginTop = "5px"; openOriginal.style.color = "#999"; overlay.appendChild(openOriginal); } }; el.parentNode.appendChild(PreviewImage); if (config.HideLink) { el.remove(); } // append image, then only scroll if user was at bottom const chatEl = unsafeWindow.document.getElementsByClassName("chat-lines")[0]; const _onReady = () => { if (shouldStickToBottom) { requestAnimationFrame(() => { chatEl.scrollTop = chatEl.scrollHeight; }); } }; if ('decode' in PreviewImage && typeof PreviewImage.decode === 'function') { PreviewImage.decode().then(_onReady).catch(_onReady); } else { PreviewImage.addEventListener('load', _onReady, { once: true }); PreviewImage.addEventListener('error', _onReady, { once: true }); } el.parentNode.appendChild(PreviewImage); if (config.HideLink) { el.remove(); } }; } }); } }); } }); console.log("[D.gg Img Preview] Connecting"); waitForElm('.chat-lines').then((elm) => { // overlay overlay = document.createElement("div"); overlay.style.position = "absolute"; overlay.style.justifyContent = "center"; overlay.style.alignItems = "center"; overlay.style.inset = "0px"; overlay.style.margin = "0px"; overlay.style.background = "rgba(0,0,0,0.85)"; overlay.style.zIndex = "999"; overlay.style.height = "100%"; overlay.style.width = "100%"; overlay.style.display = "none"; overlay.style.flexDirection = "column"; overlay.onclick = () => { overlay.style.display = "none"; overlay.innerHTML = ""; }; document.body.appendChild(overlay); // --- sticky-bottom engine --- const chatEl = elm; // already resolved by waitForElm // initialize & track user intent shouldStickToBottom = isAtBottom(chatEl); chatEl.addEventListener('scroll', () => { shouldStickToBottom = isAtBottom(chatEl); }, { passive: true }); // re-pin on growth (images decoding / layout changes) const ro = new ResizeObserver(() => { if (shouldStickToBottom) scrollToBottom(chatEl); }); ro.observe(chatEl); // start observing chat for new messages chatObserver.observe(chatEl, { attributes: false, childList: true, characterData: false, subtree: true }); console.log("[D.gg Img Preview] Connected"); console.log("[D.gg Img Preview] Adding Settings"); addSettings(); });
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址