您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Replaces missing card images on arkhamdb. Supports card pages, modals and tooltips.
当前为
// ==UserScript== // @name ArkhamDB replace missing images // @namespace Violentmonkey Scripts // @match https://arkhamdb.com/** // @grant none // @version 1.0 // @author felice // @license MIT // @description Replaces missing card images on arkhamdb. Supports card pages, modals and tooltips. // ==/UserScript== const IMAGE_BASE_PATH = "https://pub-79eb3e46f591402891f499340e8bd095.r2.dev"; /** * Handles most places where card images are displayed (including tooltips and modals). * known limitations: * - doesn't handle the "View as full cards" set pages. * - doesn't handle double-sided cards(e.g. acts). * - doesn't handle the "/reviews" page. * - doesn't handle the "/faqs" page. */ function init() { replaceCardPage(); replaceScansOnly(); watchCardTooltip(); watchCardModal(); } /** * Replace the "no image" placeholder on card pages with an image. */ function replaceCardPage() { const node = document.querySelector(".no-image"); if (!window.location.href.includes("/set/") && node) { const id = idFromCardUrl(window.location.href); const image = htmlFromString( `<img class="img-responsive img-vertical-card" src="${imageUrl(id)}" />` ); node.parentNode.replaceChild(image, node); } } /** * Replace the scans only page. */ function replaceScansOnly() { const images = document.querySelectorAll("a[href*='/card/'] > img"); for (const image of images) { // check for empty image "src" attributes, which default to the current url. if (image.src === window.location.href) { const id = idFromCardUrl(image.parentNode.getAttribute("href")); image.src = imageUrl(id); } } } /** * Watch for the card modal opening, then replace the broken image inside. */ function watchCardModal() { const modal = document.querySelector("#cardModal"); if (!modal) return; // watch for images being added to the modal DOM. // filter for images that have src "undefined". const observer = new MutationObserver((mutationList) => { const image = mutationList.reduce( (acc, { type, addedNodes }) => acc || type !== "childList" ? acc : Array.from(addedNodes).find((node) => node?.src?.includes("undefined") ), undefined ); // traverse modal DOM to find card link and update image src. if (image) { const cardUrl = image .closest("#cardModal") .querySelector("a[href*='/card/']") .getAttribute("href"); image.src = imageUrl(idFromCardUrl(cardUrl)); } }); observer.observe(modal, { attributes: false, characterData: false, childList: true, subtree: true, }); } function watchCardTooltip() { const observer = new MutationObserver((mutationList) => { // watch for changes on links with a tooltip. // once changed, find the corresponding tooltip and update it. for (const { target, type } of mutationList) { if ( type === "attributes" && target instanceof HTMLAnchorElement && target.dataset.hasqtip ) { const tooltipId = target.dataset.hasqtip; const id = idFromCardUrl(target.href); const tooltip = document.querySelector(`#qtip-${tooltipId}-content`); if (tooltip && !tooltip.querySelector(".card-thumbnail")) { const url = imageUrl(id); // different card types art cropped differently, try to infer card type from tooltip text. const cardType = tooltip .querySelector(".card-type") ?.textContent?.split(".") ?.at(0) ?.toLowerCase() || "skill"; const image = htmlFromString(` <div class="card-thumbnail card-thumbnail-3x card-thumbnail-${cardType}" style="background-image: url(${url})" /> `); tooltip.prepend(image); } } } }); observer.observe(document.body, { attributes: true, childList: false, characterData: false, subtree: true, }); } function imageUrl(id) { return `${IMAGE_BASE_PATH}/${id}.jpg`; } function idFromCardUrl(href) { return href.split("/").at(-1); } function htmlFromString(html) { const template = document.createElement("template"); html = html.trim(); template.innerHTML = html; return template.content.firstChild; } init();