Skip to content

Instantly share code, notes, and snippets.

@fspoettel
Created August 6, 2023 16:24
Show Gist options
  • Save fspoettel/db9707ab04d0730a7266bc7d854ee929 to your computer and use it in GitHub Desktop.
Save fspoettel/db9707ab04d0730a7266bc7d854ee929 to your computer and use it in GitHub Desktop.
ArkhamDB: replace missing images
// ==UserScript==
// @name ArkhamDB: replace missing images
// @namespace Violentmonkey Scripts
// @match https://arkhamdb.com/**
// @grant none
// @version 1.0
// @author @felice
// @description 31/05/2023, 16:37:23
// ==/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();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment