Skip to content

Instantly share code, notes, and snippets.

@qubodup
Last active September 4, 2025 09:52
Show Gist options
  • Save qubodup/a4ed2f9a50d4b554ba605607ad638499 to your computer and use it in GitHub Desktop.
Save qubodup/a4ed2f9a50d4b554ba605607ad638499 to your computer and use it in GitHub Desktop.
Fix broken audio embeds on Fandom Special:NewFiles and in Categories + enhance NewFiles gallery with filenames and repositioned captions
// ==UserScript==
// @name Fandom Audio Browser
// @namespace https://tampermonkey.net/
// @version 2025-09-04
// @description Fix broken audio embeds on Fandom Special:NewFiles and in Categories + enhance NewFiles gallery with filenames and repositioned captions
// @author Some BODY once TOLD me
// @license CC0 1.0 Universal
// @match https://*.fandom.com/wiki/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const path = location.pathname;
// Special:NewFiles or Category pages
const isNewFiles = path.startsWith("/wiki/Special:NewFiles");
const isCategory = path.startsWith("/wiki/Category:");
if (isNewFiles || isCategory) {
let currentAudio = null;
let currentButton = null;
function makeSimplePlayer(src, btn) {
const audio = new Audio(src);
audio.addEventListener("ended", () => {
if (currentAudio === audio) {
currentAudio = null;
if (currentButton) currentButton.textContent = "▶";
}
});
return audio;
}
function replaceWithToggle(container, src, title) {
// Hide existing children instead of nuking innerHTML
for (const child of Array.from(container.children)) {
child.style.setProperty("display", "none", "important");
child.style.setProperty("visibility", "hidden", "important");
}
// Only add once
if (container.querySelector("button[data-added-by-userscript]")) return;
const btn = document.createElement("button");
btn.textContent = "▶";
btn.title = "Play " + (title || "audio");
btn.dataset.addedByUserscript = "true";
btn.style.cursor = "pointer";
btn.style.width = "28px";
btn.style.height = "28px";
btn.style.fontSize = "16px";
btn.style.display = "block";
btn.style.margin = "0 auto";
let audio = null;
btn.addEventListener("click", ev => {
ev.preventDefault();
ev.stopPropagation();
if (!audio) audio = makeSimplePlayer(src, btn);
if (currentAudio && currentAudio !== audio) {
currentAudio.pause();
currentAudio.currentTime = 0;
if (currentButton) currentButton.textContent = "▶";
}
if (audio.paused) {
audio.play().catch(console.error);
currentAudio = audio;
currentButton = btn;
btn.textContent = "■"; // stop symbol
} else {
audio.pause();
audio.currentTime = 0;
currentAudio = null;
currentButton = null;
btn.textContent = "▶";
}
});
container.appendChild(btn);
}
function replaceWithAudio(container, src, title) {
// Hide existing content instead of deleting it
for (const child of Array.from(container.children)) {
child.style.setProperty("display", "none", "important");
child.style.setProperty("visibility", "hidden", "important");
}
// Make sure container uses flexbox for centering
container.style.display = "flex";
container.style.alignItems = "center";
container.style.justifyContent = "center";
// Only add our player once
if (container.querySelector("audio[data-added-by-userscript]")) return;
// Create and append audio element
const audio = document.createElement("audio");
audio.controls = true;
audio.src = src;
audio.title = title || "Audio";
audio.dataset.addedByUserscript = "true";
audio.style.width = "180px";
audio.style.height = "30px";
container.appendChild(audio);
}
function enhanceNewFilesItem(item) {
if (item.classList.contains("filename-enhanced")) return;
const link = item.querySelector("a.image.lightbox");
const caption = item.querySelector(".lightbox-caption");
const timeEl = caption?.querySelector("time");
const usernameLink = caption?.querySelector("a");
if (link && caption && timeEl && usernameLink) {
const fileName = link.getAttribute("title")?.split(" (")[0] || "";
const filePageUrl = link.getAttribute("href");
// New filename link
const fileLink = document.createElement("a");
fileLink.href = filePageUrl;
fileLink.textContent = fileName;
fileLink.title = fileName;
fileLink.style.display = "inline-block";
fileLink.style.maxWidth = "100%";
fileLink.style.whiteSpace = "nowrap";
fileLink.style.overflow = "hidden";
fileLink.style.textOverflow = "ellipsis";
// Replace username with filename link
usernameLink.replaceWith(fileLink);
// Move username next to the date
const movedUsername = usernameLink.cloneNode(true);
const spanWrapper = document.createElement("span");
spanWrapper.appendChild(document.createTextNode(" – "));
spanWrapper.appendChild(movedUsername);
timeEl.parentNode.appendChild(spanWrapper);
item.classList.add("filename-enhanced");
}
}
function handleNode(node) {
// Special:NewFiles (gallery) - audio player replacement
if (node.matches?.(".wikia-gallery-item .thumbimage")) {
const src = node.getAttribute("src") || node.getAttribute("data-src");
if (src && /\.(mp3|ogg|wav)/i.test(src)) {
const container = node.closest(".gallery-image-wrapper");
const title = node.getAttribute("data-image-name") || "audio";
replaceWithAudio(container, src, title);
}
}
// Special:NewFiles (gallery) - caption enhancements
if (location.pathname.startsWith("/wiki/Special:NewFiles") && node.matches?.(".wikia-gallery-item")) {
enhanceNewFilesItem(node);
}
// Category pages (toggle)
if (node.matches?.(".category-page__member-thumbnail")) {
const src = node.getAttribute("src") || node.getAttribute("data-src");
if (src && /\.(mp3|ogg|wav)/i.test(src)) {
const container = node.parentElement; // <a> wrapper
const title = node.getAttribute("alt") || "audio";
replaceWithToggle(container, src.replace(/\/smart\/.*$/, ""), title);
}
}
}
// Initial scan
document.querySelectorAll(".wikia-gallery-item .thumbimage, .category-page__member-thumbnail, .wikia-gallery-item")
.forEach(handleNode);
// Scoped observers
const roots = document.querySelectorAll(".wikia-gallery, .category-page__members, body.page-MediaWiki_File");
roots.forEach(root => {
const observer = new MutationObserver(mutations => {
for (const m of mutations) {
if (m.type === "attributes" && m.target.matches?.(".wikia-gallery-item .thumbimage, .category-page__member-thumbnail")) {
handleNode(m.target);
}
for (const node of m.addedNodes) {
if (node.nodeType === 1) {
if (node.matches(".wikia-gallery-item .thumbimage, .category-page__member-thumbnail, .wikia-gallery-item")) {
handleNode(node);
}
node.querySelectorAll?.(".wikia-gallery-item .thumbimage, .category-page__member-thumbnail, .wikia-gallery-item")
.forEach(handleNode);
}
}
//catch caption changes
if (m.type === "childList" && m.target.matches(".lightbox-caption")) {
const item = m.target.closest(".wikia-gallery-item");
if (item) enhanceNewFilesItem(item);
}
}
});
observer.observe(root, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["src", "data-src"]
});
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment