// ==UserScript==
// @name Enhanced download labels
// @namespace http://tampermonkey.net/
// @version 0.2.0
// @description This JavaScript code was developed to improve the appearance of threads in the download area. For this purpose, various attributes are identified within the thread titles. These include the source (e.g. Blu-ray, Web-DL), resolution (e.g. 1080p, 720p) and language (audio, subtitles). These are then clearly displayed in the form of labels. Based on the “Mark quality” script by Kirdock.
// @description:de Dieser JavaScript-Code wurde entwickelt, um das Erscheinungsbild der Themen im Download-Bereich zu verbessern. Zu diesem Zweck werden verschiedene Attribute innerhalb der Thread-Titel identifiziert. Dazu gehören die Quelle (z.B. Blu-ray, Web-DL), die Auflösung (z.B. 1080p, 720p) und die Sprache (Audio, Untertitel). Diese werden dann in Form von Labels übersichtlich dargestellt. Basiert auf dem "Mark quality" script von Kirdock.
// @author ID107, Kirdock
// @match https://www.animes.so/forum/anime-deutsch-sub/*
// @match https://www.animes.so/forum/anime-deutsch/*
// @match https://www.animes.so/forum/anime-english-sub/
// @match https://www.animes.so/forum/anime-english/
// @match https://www.animes.so/forum/anime-filme/*
// @match https://www.animes.so/forum/anime-movies/*
// @grant none
// ==/UserScript==
// Define color constants for various states
const BLUERAY_BLUE = "rgba(0, 139, 195, 1)";
const SUCCESS_GREEN = "rgba(50, 168, 82, 1)";
const WARNING_YELLOW = "rgba(255, 193, 7, 0.375)";
const DANGER_RED = "rgba(220, 53, 69, 0.5)";
const WHITE = "rgba(225, 225, 225, 1)";
// Define color object mapping various states to their respective colors.
// If other colors are desired, here is the place to set them
const COLOR = {
bluray: BLUERAY_BLUE,
webdl: WARNING_YELLOW,
webrip: WARNING_YELLOW,
dvd: WARNING_YELLOW,
tv: DANGER_RED,
default: WHITE,
fullHd: SUCCESS_GREEN,
hd: WARNING_YELLOW,
sd: DANGER_RED,
other: WHITE
};
(function () {
'use strict';
// Select all nodes with the specified class
const nodes = document.querySelectorAll('.js-threadList .aso_thread--title--title');
// Define regular expression patterns for source and resolution
const regex = {
source: {
bluray: RegExp("(?:BD|BR|Blu(-?)Ray)(?:-)?(?:RIP)?", "i"),
webdl: RegExp("(?:WEB|DDC)(?:-)?(DL){1}", "i"),
webrip: RegExp("(?:WEB|VOD)(?:-)?(?:RIP)?", "i"),
dvd: RegExp("(?:DVD-RIP|DVDRIP|DVD)", "i"),
tv: RegExp("(?:TV|HDTV|SAT|DVB|DS)(?:-)?(?:RIP)?", "i")
},
resolution: {
fullHd: RegExp("1080p", "i"),
hd: RegExp("720p", "i"),
sd: RegExp("480p", "i")
}
}
// Define style object for applying CSS styles to elements
const style = {
default: {
alignItems: "center",
borderRadius: "6px",
borderStyle: "solid",
borderWidth: "1px",
display: "flex",
flexDirection: "row",
fontFamily: "monospace",
fontSize: "1.125rem",
fontWeight: "700",
marginRight: ".5em",
padding: ".125rem .5rem"
},
source: {
bluray: { color: COLOR.bluray, borderColor: COLOR.bluray },
webdl: { color: COLOR.webdl, borderColor: COLOR.webdl },
webrip: { color: COLOR.webrip, borderColor: COLOR.webrip },
dvd: { color: COLOR.dvd, borderColor: COLOR.dvd },
tv: { color: COLOR.tv, borderColor: COLOR.tv },
default: { color: COLOR.default, borderColor: COLOR.default }
},
resolution: {
fullHd: { color: COLOR.fullHd, borderColor: COLOR.fullHd },
hd: { color: COLOR.hd, borderColor: COLOR.hd },
sd: { color: COLOR.sd, borderColor: COLOR.sd },
default: { color: COLOR.other, borderColor: COLOR.other }
}
}
// Loop through each node and process accordingly
nodes.forEach((node) => {
const nodeText = node.innerText.replace(/\s/g, "");
var childElement = document.createElement("div");
Object.assign(childElement.style, { display: "flex", alignItems: "center" });
var resolutionElement = document.createElement("span");
Object.assign(resolutionElement.style, style.default);
// Create source node label
if (regex.source.bluray.test(nodeText)) {
childElement.appendChild(generateSourceNode(0));
} else if (regex.source.webdl.test(nodeText)) {
childElement.appendChild(generateSourceNode(1));
} else if (regex.source.webrip.test(nodeText)) {
childElement.appendChild(generateSourceNode(2));
} else if (regex.source.dvd.test(nodeText)) {
childElement.appendChild(generateSourceNode(3));
} else if (regex.source.tv.test(nodeText)) {
childElement.appendChild(generateSourceNode(4));
} else {
childElement.appendChild(generateSourceNode(-1));
}
// Create resolution node label
if (regex.resolution.fullHd.test(nodeText)) {
Object.assign(resolutionElement.style, style.resolution.fullHd);
resolutionElement.innerText = "FHD 1080p";
} else if (regex.resolution.hd.test(nodeText)) {
Object.assign(resolutionElement.style, style.resolution.hd);
resolutionElement.innerText = "HD 720p";
} else if (regex.resolution.sd.test(nodeText)) {
Object.assign(resolutionElement.style, style.resolution.sd);
resolutionElement.innerText = "SD 480p";
} else {
let txtstr;
Object.assign(resolutionElement.style, style.resolution.default);
const regex1 = RegExp('[0-9]{3,}[ip]', 'gmi');
let match;
while ((match = regex1.exec(nodeText)) !== null) {
txtstr == undefined ? txtstr = `${match[0]}` : txtstr += ` | ${match[0]}`;
}
txtstr = txtstr ?? "Unknown";
resolutionElement.innerText = txtstr;
}
childElement.appendChild(resolutionElement);
// Create audio and subtitle node label
const audioRegex = RegExp('(?:\\b[a-z]{3}\\b(?:-?))+Dub', 'igm');
const subtitleRegex = RegExp('(?:\\b[a-z]{3}\\b(?:-?))+Sub', 'igm');
let languageElement = document.createElement("div");
Object.assign(languageElement.style, {
border: "1px solid rgba(255, 255, 255, 0.5)",
borderRadius: "6px",
display: "flex",
padding: ".125rem"
});
// Create audio icon
let audioIconElement = document.createElement("div");
Object.assign(audioIconElement.style, {
background: `url\('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M533.6 32.5C598.5 85.2 640 165.8 640 256s-41.5 170.7-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64V448c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352H64c-35.3 0-64-28.7-64-64V224c0-35.3 28.7-64 64-64h67.8L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z" fill="rgba(255, 255, 255, 0.5)"/></svg>'\)`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "contain",
borderRadius: "3px",
height: "1em",
margin: "0 0.2rem",
width: "1em"
});
languageElement.appendChild(audioIconElement);
// Generate audio country flag icons
let audio;
if ((audio = audioRegex.exec(node.innerText)) !== null) {
audio[0].split("-").sort().forEach((str) => {
let flag = getFlag(str);
if (flag !== undefined) {
let flagElement = document.createElement("div");
Object.assign(flagElement.style, {
background: flag,
backgroundSize: "cover",
backgroundPosition: "center",
height: "1em",
width: "1em",
margin: "0 0.2rem",
borderRadius: "3px"
});
languageElement.appendChild(flagElement);
}
});
}
// Create subtitle icon
let subtitle;
if ((subtitle = subtitleRegex.exec(node.innerText)) !== null) {
let subtitleIconElement = document.createElement("div");
Object.assign(subtitleIconElement.style, {
background: `url\('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 96C0 60.7 28.7 32 64 32H512c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM200 208c14.2 0 27 6.1 35.8 16c8.8 9.9 24 10.7 33.9 1.9s10.7-24 1.9-33.9c-17.5-19.6-43.1-32-71.5-32c-53 0-96 43-96 96s43 96 96 96c28.4 0 54-12.4 71.5-32c8.8-9.9 8-25-1.9-33.9s-25-8-33.9 1.9c-8.8 9.9-21.6 16-35.8 16c-26.5 0-48-21.5-48-48s21.5-48 48-48zm144 48c0-26.5 21.5-48 48-48c14.2 0 27 6.1 35.8 16c8.8 9.9 24 10.7 33.9 1.9s10.7-24 1.9-33.9c-17.5-19.6-43.1-32-71.5-32c-53 0-96 43-96 96s43 96 96 96c28.4 0 54-12.4 71.5-32c8.8-9.9 8-25-1.9-33.9s-25-8-33.9 1.9c-8.8 9.9-21.6 16-35.8 16c-26.5 0-48-21.5-48-48z" fill="rgba(255, 255, 255, 0.5)"/></svg>'\)`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "contain",
borderRadius: "3px",
height: "1em",
margin: "0 0.2rem 0 1rem",
width: "1em"
});
languageElement.appendChild(subtitleIconElement);
// Generate subtitle country flag icons
subtitle[0].split("-").sort().forEach((str) => {
let flag = getFlag(str);
if (flag !== undefined) {
let flagElement = document.createElement("div");
Object.assign(flagElement.style, {
background: flag,
backgroundSize: "cover",
backgroundPosition: "center",
height: "1em",
width: "1em",
margin: "0 0.2rem",
borderRadius: "3px"
});
languageElement.appendChild(flagElement);
}
});
}
childElement.appendChild(languageElement);
node.parentNode.insertBefore(childElement, node.parentNode.firstChild);
});
/**
* Returns a URL for an SVG flag based on the ISO code.
* @param {string} isoCode - The ISO code of the desired flag.
* @returns {string|undefined} The URL for the SVG flag or undefined if no corresponding ISO code is found.
*/
function getFlag(isoCode) {
console.debug(isoCode.toLowerCase() + "=== deu", isoCode.toLowerCase() === "deu");
switch (isoCode.toLowerCase()) {
case "deu":
case "ger":
return `url\('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="600" viewBox="0 0 5 3"><path d="M0 0h5v3H0z"/><path fill="%23D00" d="M0 1h5v2H0z"/><path fill="%23FFCE00" d="M0 2h5v1H0z"/></svg>'\)`;
case "jpn":
case "jap":
return `url\('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="900" height="600"><path d="M0 0h900v600H0z" fill="%23fff"/><circle fill="%23bc002d" cx="450" cy="300" r="180"/></svg>'\)`;
case "eng":
return `url\('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="600" viewBox="0 0 60 30"><clipPath id="a"><path d="M0 0v30h60V0z"/></clipPath><clipPath id="b"><path d="M30 15h30v15zv15H0zH0V0zV0h30z"/></clipPath><g clip-path="url(%23a)"><path d="M0 0v30h60V0z" fill="%23012169"/><path d="m0 0 60 30m0-30L0 30" stroke="%23fff" stroke-width="6"/><path d="m0 0 60 30m0-30L0 30" clip-path="url(%23b)" stroke="%23C8102E" stroke-width="4"/><path d="M30 0v30M0 15h60" stroke="%23fff" stroke-width="10"/><path d="M30 0v30M0 15h60" stroke="%23C8102E" stroke-width="6"/></g></svg>'\)`;
default:
return undefined;
}
}
/**
* Generates a source node element based on the given type.
* @param {number} type - The type of the source (0: Blu-ray, 1: Web-DL, 2: WebRip, 3: DVD, 4: TV, default: Unknown).
* @returns {HTMLElement} The generated source node element.
*/
function generateSourceNode(type) {
var sourceElement = document.createElement("span");
Object.assign(sourceElement.style, style.default);
switch (type) {
case 0:
Object.assign(sourceElement.style, style.source.bluray);
sourceElement.innerText = "Blu-ray";
break;
case 1:
Object.assign(sourceElement.style, style.source.webdl);
sourceElement.innerText = "Web-DL";
break;
case 2:
Object.assign(sourceElement.style, style.source.webrip);
sourceElement.innerText = "WebRip";
break;
case 3:
Object.assign(sourceElement.style, style.source.dvd);
sourceElement.innerText = "DVD";
break;
case 4:
Object.assign(sourceElement.style, style.source.tv);
sourceElement.innerText = "TV";
break;
default:
Object.assign(sourceElement.style, style.source.default);
sourceElement.innerText = "Unknown";
sourceElement.style.opacity = "0";
}
return sourceElement;
}
})();