Last active
January 2, 2025 10:39
-
-
Save SanteriHetekivi/813f9e44b1ee9074b21893a2b911af7d to your computer and use it in GitHub Desktop.
licensedInEnglish.user.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Licensed (in English) | |
// @namespace http://tampermonkey.net/ | |
// @version 0.6 | |
// @description Show if manga is licensed in English. | |
// @author Santeri Hetekivi | |
// @match https://mangadex.org/* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=mangadex.org | |
// @grant GM.xmlHttpRequest | |
// @grant window.onurlchange | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
// ID for result element. | |
const ID_RESULT = "licensedInEnglishResult" | |
// Colors for different results. | |
const COLOR_ERROR = "yellow" | |
const COLOR_LICENSED = "red" | |
const COLOR_UNLICENSED = "green" | |
// Milliseconds to sleep between tries. | |
const MS_SLEEP = 1000 | |
// URL starts. | |
// Start for Manga Updates series url. | |
const URL_START_MANGAUPDATES = "https://www.mangaupdates.com/series" | |
// Start for MangaDex title url. | |
const URL_START_MANGADEX = "https://mangadex.org/title/" | |
// Querys for elements. | |
// Parent to store result element in. | |
const QUERY_PARENT = ".title" | |
// Query for getting link for Manga Updates. | |
const QUERY_LINK_MANGAUPDATES = "a[href^='"+URL_START_MANGAUPDATES+"']" | |
// Query for getting elements that can contain TEXT_LICENSED_IN_ENGLISH text. | |
const QUERY_MANGAUPDATES_LICENSED_IN_ENGLISH = '[data-cy="info-box-licensed-header"] b' | |
const QUERY_TRACK = ".font-bold.mb-2" | |
// Texts. | |
const TEXT_LICENSED_IN_ENGLISH = "Licensed (in English)" | |
const TEXT_LICENSED_POSITIVE = "Yes" | |
const TEXT_LICENSED_NEGATIVE = "No" | |
const TEXT_TRACK = "Track" | |
const TEXT_CONSOLE_START = "Licensed (in English):" | |
// Log debug data. | |
const logDebug = (...args) => { | |
console.debug(TEXT_CONSOLE_START, ...args) | |
} | |
// Update result element. | |
const updateLicensedInEnglishText = (_elementResult, _text, _color = "yellow") => { | |
_elementResult.style.color = _color | |
_elementResult.textContent = _text; | |
} | |
// Select node list with given _query. | |
const selectNodeList = (_query, _document = document) => { | |
const nodeList = _document.querySelectorAll(_query); | |
if(!(nodeList instanceof NodeList)) | |
{ | |
throw "nodeList for query '"+_query+"' was not instance of NodeList!" | |
} | |
return nodeList | |
} | |
// Select single element. | |
const selectElement = (_query) => { | |
// Get node list. | |
const nodeList = selectNodeList(_query); | |
// Check that got only single result. | |
const lengthNodeList = nodeList.length | |
if(lengthNodeList !== 1) | |
{ | |
throw "Found "+lengthNodeList+" nodes for '"+_query+"'!" | |
} | |
// Check that single element is instance of Element. | |
const element = nodeList[0] | |
if(!(element instanceof Element)) | |
{ | |
throw "Element for query "+_query+" was not instance of Element!" | |
} | |
// Return gotten element. | |
return element | |
} | |
// Get element for result. | |
const getElementResult = () => { | |
return document.getElementById(ID_RESULT) | |
} | |
// Output given _error to console. | |
const consoleError = (_error) => { | |
console.error(TEXT_CONSOLE_START, _error) | |
} | |
// Handle given _error. | |
const handleError = (_url, _error) => { | |
// Output to console. | |
consoleError(_error) | |
// Get element for result. | |
const elementResult = getElementResult() | |
// If element result found. | |
if(elementResult instanceof Element) | |
{ | |
// Update error to result element. | |
updateLicensedInEnglishText( | |
elementResult, | |
_error, | |
COLOR_ERROR | |
) | |
} | |
// If url given | |
if(_url) | |
{ | |
// remove url. | |
urlLicensedStatus(_url, false, true, null) | |
} | |
} | |
// Check that given _element is instance of Element. | |
const checkIsElement = (_element, _name) => { | |
if(!(_element instanceof Element)) | |
{ | |
throw _name+" was not instance of Element!" | |
} | |
return _element | |
} | |
// Get element with TEXT_LICENSED_IN_ENGLISH text. | |
const getElementWithText = ( | |
_query, | |
_text, | |
_document = document | |
) => { | |
// Loop node list. | |
const nodeList = selectNodeList(_query, _document) | |
let element = null | |
for (let i = 0; i < nodeList.length; i++) | |
{ | |
// Get element. | |
element = checkIsElement(nodeList[i], "nodeList["+i+"]") | |
// If text matches | |
if (element.textContent == _text) | |
{ | |
// return element. | |
return element | |
} | |
} | |
// No element found. | |
return null | |
} | |
// Get element with TEXT_LICENSED_IN_ENGLISH text. | |
const getElementLicensedInEnglish = (_document) => { | |
// Get element for text. | |
const element = getElementWithText( | |
QUERY_MANGAUPDATES_LICENSED_IN_ENGLISH, | |
TEXT_LICENSED_IN_ENGLISH, | |
_document | |
) | |
// No element found. | |
if (element === null) | |
{ | |
throw "No element with text '"+TEXT_LICENSED_IN_ENGLISH+"' found!" | |
} | |
// Return element. | |
return element | |
} | |
// Get text answer for licensed in english. | |
const getTextLicensedInEnglish = (_document) => { | |
// Get text. | |
const text = checkIsElement( | |
checkIsElement( | |
getElementLicensedInEnglish(_document).parentElement ?? null, | |
"getElementLicensedInEnglish.parentElement" | |
).nextElementSibling ?? null, | |
"getElementLicensedInEnglish.parentElement.nextElementSibling" | |
).textContent.trim() | |
// Check gotten text. | |
if( | |
text !== TEXT_LICENSED_POSITIVE | |
&& | |
text !== TEXT_LICENSED_NEGATIVE | |
) | |
{ | |
throw "Invalid text: "+text | |
} | |
// Return text. | |
return text | |
} | |
// Handle response. | |
const handleResponse = (_url, _response) => { | |
// Log debug message. | |
logDebug("Handling response...") | |
// Get answer for licensed in english. | |
logDebug("Getting licensedInEnglish text...") | |
const licensedInEnglish = getTextLicensedInEnglish( | |
( | |
new DOMParser() | |
).parseFromString( | |
_response.responseText, | |
'text/html' | |
) | |
) | |
// Get licensed. | |
logDebug("Getting licensed from text "+licensedInEnglish+"...") | |
let licensed = null | |
if(licensedInEnglish === TEXT_LICENSED_NEGATIVE) | |
{ | |
licensed = false | |
} | |
else if(licensedInEnglish === TEXT_LICENSED_POSITIVE) | |
{ | |
licensed = true | |
} | |
else | |
{ | |
throw "Invalid licensedInEnglish: "+licensedInEnglish | |
} | |
// Call on success. | |
onSuccess( | |
checkIsElement( | |
getElementResult(), | |
"getElementResult" | |
), | |
_url, | |
licensed | |
) | |
} | |
// Handle success. | |
const onSuccess = (_elementResult, _url, _licensed) => { | |
// Write debug log. | |
logDebug("onSuccess _url: "+_url+" _licensed: "+(_licensed ? "YES" : "NO")) | |
// Update result element | |
updateLicensedInEnglishText( | |
_elementResult, | |
( | |
_licensed ? | |
TEXT_LICENSED_POSITIVE : | |
TEXT_LICENSED_NEGATIVE | |
), | |
( | |
_licensed ? | |
COLOR_LICENSED : | |
COLOR_UNLICENSED | |
) | |
) | |
// Set curren url licensed status. | |
urlLicensedStatus(_url, true, false, _licensed) | |
} | |
// Get url for Manga Updates. | |
const getURLMangaUpdates = () => { | |
// Init urls array | |
const urls = [] | |
// Loop node list. | |
const nodeList = selectNodeList(QUERY_LINK_MANGAUPDATES) | |
for (let i = 0; i < nodeList.length; i++) | |
{ | |
// Get href | |
let href = checkIsElement(nodeList[i], "nodeList["+i+"]").href ?? null | |
// If got href as string | |
if (typeof href === "string") | |
{ | |
// Trim href. | |
href = href.trim() | |
// If href not already in urls | |
if(!urls.includes(href)) | |
{ | |
// add href to urls. | |
urls.push(href) | |
} | |
} | |
} | |
// Check that has only one url. | |
const lengthUrls = urls.length | |
// If no urls found. | |
if(lengthUrls === 0) | |
{ | |
// throw error. | |
throw "Manga Updates link not found!" | |
} | |
// If different amount than 1 links found | |
if(lengthUrls !== 1) | |
{ | |
// throw error. | |
throw "Found "+lengthUrls+" Manga Updates urls: "+urls.join(",") | |
} | |
// Return url. | |
return urls[0] | |
} | |
// Get current url. | |
const currUrl = () => { | |
const href = window.location.href ?? null | |
if( | |
typeof href !== "string" | |
|| | |
href.trim() === "" | |
) | |
{ | |
throw "Invalid url: "+href | |
} | |
return href | |
} | |
// Handle url licensed status. | |
const urlLicensedStatus = (_url, _add = false, _remove = false, _licensed = null) => { | |
// If _remove given | |
if(_remove) | |
{ | |
// and _add given | |
if(_add) | |
{ | |
// throw error. | |
throw "Both _add and _remove given!" | |
} | |
// and _licensed given | |
if(_licensed !== null) | |
{ | |
// throw error. | |
throw "Both _licensed and _remove given!" | |
} | |
} | |
// If | |
if( | |
// is not MangaDex url | |
!_url.startsWith(URL_START_MANGADEX) | |
&& | |
// and is not removing operation | |
!_remove | |
) | |
{ | |
throw "Gotten _url was not MangaDex url: "+_url | |
} | |
// Init running urls. | |
if(typeof urlLicensedStatus.data !== "object") | |
{ | |
urlLicensedStatus.data = {} | |
} | |
// Was included already. | |
const includedAlready = _url in urlLicensedStatus.data | |
// Init included now to included already. | |
let includedNow = includedAlready | |
// If adding, not included and not wanted to remove | |
if(_add && !includedNow && !_remove) | |
{ | |
// add | |
urlLicensedStatus.data[_url] = _licensed | |
// and set included. | |
includedNow = true | |
} | |
// If removing and included and does not have status | |
if(_remove && includedNow && urlLicensedStatus.data[_url] === null) | |
{ | |
// remove | |
delete urlLicensedStatus.data[_url] | |
// and update included now. | |
includedNow = false | |
} | |
// If licensed given | |
if(_licensed !== null) | |
{ | |
// update status | |
urlLicensedStatus.data[_url] = _licensed | |
// and add to be included now. | |
includedNow = true | |
} | |
// Debug log currently running urls. | |
logDebug("urlLicensedStatus.data", urlLicensedStatus.data) | |
// Return status. | |
return ( | |
includedAlready ? | |
urlLicensedStatus.data[_url] : | |
undefined | |
) | |
} | |
// Get element for track. | |
const getElementTrack = () => { | |
return getElementWithText( | |
QUERY_TRACK, | |
TEXT_TRACK | |
) | |
} | |
// Return if needing to try again later. | |
const tryAgain = (_licensedStatus) => { | |
// If already running try again later. | |
if(_licensedStatus === null) | |
{ | |
return true; | |
} | |
// Get parent elements. | |
const parentElementsLength = selectNodeList(QUERY_PARENT).length | |
// If | |
if( | |
// no parent values found | |
parentElementsLength === 0 | |
) | |
{ | |
return true; | |
} | |
// Throw error if found other than 0 or 1 elements. | |
else if(parentElementsLength !== 1) | |
{ | |
throw "Found "+parentElementsLength+" nodes for '"+QUERY_PARENT+"'!" | |
} | |
// Return that track element is defined. | |
return ( | |
getElementTrack() === null | |
) | |
} | |
// Run logic. | |
const run = (_url) => { | |
// Log debug message. | |
logDebug("Running "+_url+"...") | |
// Get licensed status. | |
const licensedStatus = urlLicensedStatus(_url, false, null) | |
// Init data. | |
if(typeof run.data !== "object") | |
{ | |
run.data = {} | |
} | |
// Init data for given _url. | |
if(!(_url in run.data)) | |
{ | |
run.data[_url] = { | |
counter: 1, | |
timeout: null | |
} | |
} | |
// Debug data. | |
logDebug("run.data", run.data) | |
logDebug("Counter ", run.data[_url].counter) | |
// If has active timeout | |
if(run.data[_url].timeout !== null) | |
{ | |
// throw error. | |
throw "Already has active timeout!" | |
} | |
// Make sure that document is instance of HTMLDocument. | |
if(!(document instanceof HTMLDocument)) | |
{ | |
throw "document was not instance of HTMLDocument!" | |
} | |
// If trying again. | |
if(tryAgain(licensedStatus)) | |
{ | |
// Log debug message. | |
logDebug("Trying again!") | |
// Stop if counter is over 10. | |
if(10 < run.data[_url].counter) | |
{ | |
throw "Trying again, but counter is: "+run.data[_url].counter | |
} | |
// Call after timeout. | |
run.data[_url].timeout = setTimeout( | |
() => { | |
try | |
{ | |
// Zero timeout. | |
delete run.data[_url].timeout | |
run.data[_url].timeout = null; | |
// Increment counter. | |
++run.data[_url].counter | |
// Call run again. | |
run(_url) | |
} | |
catch(_error) | |
{ | |
handleError(_url, _error) | |
} | |
}, | |
MS_SLEEP | |
); | |
// Throw error. | |
throw "Trying again in "+MS_SLEEP+" ms!" | |
} | |
// Get result element. | |
let elementResult = getElementResult(ID_RESULT) | |
// If no result element found. | |
if(!(elementResult instanceof Element)) | |
{ | |
// Create result element. | |
logDebug("Adding result element...") | |
elementResult = document.createElement("p") | |
elementResult.id = ID_RESULT | |
selectElement(QUERY_PARENT).appendChild(elementResult); | |
logDebug("Added result element.") | |
} | |
// If already has licensed status | |
if(typeof licensedStatus === "boolean") | |
{ | |
// just update element. | |
onSuccess( | |
elementResult, | |
_url, | |
licensedStatus | |
) | |
// and return. | |
return | |
} | |
// Update result to loading. | |
updateLicensedInEnglishText( | |
elementResult, | |
"Loading...", | |
COLOR_ERROR | |
) | |
// Get Manga Updates page. | |
logDebug("Making GET request...") | |
GM.xmlHttpRequest( | |
{ | |
method: "GET", | |
url: getURLMangaUpdates(), | |
headers: { | |
"Accept": "text/html" | |
}, | |
onload: function(_response) { | |
// Handle response. | |
logDebug("onload") | |
try | |
{ | |
handleResponse(_url, _response) | |
} | |
catch(_error) | |
{ | |
handleError(_url, _error) | |
} | |
}, | |
onerror: (_response) => { | |
logDebug("onerror") | |
consoleError(_response) | |
handleError(_url, _response.error ?? "Unknown error!") | |
}, | |
ontimeout: (_response) => { | |
logDebug("ontimeout") | |
consoleError(_response) | |
handleError(_url, _response.error ?? "Unknown timeout!") | |
}, | |
onabort: (_response) => { | |
logDebug("onabort") | |
consoleError(_response) | |
handleError(_url, _response.error ?? "Unknown timeout!") | |
} | |
} | |
); | |
} | |
// After page has fully loaded. | |
window.addEventListener( | |
'load', | |
function() | |
{ | |
// Log debug message. | |
logDebug("Page loaded!") | |
// Init url. | |
let url = null | |
try | |
{ | |
// Check that window.onurlchange feature is supported | |
// https://www.tampermonkey.net/documentation.php#api:window.onurlchange | |
if (window.onurlchange !== null) | |
{ | |
throw "window.onurlchange feature is not supported!" | |
} | |
// Add url change event. | |
window.addEventListener( | |
'urlchange', | |
(_info) => { | |
// Handle urlchange. | |
logDebug("urlchange") | |
const url = _info.url | |
try | |
{ | |
run(url) | |
} | |
catch(_error) | |
{ | |
handleError(url, _error) | |
} | |
} | |
); | |
// Start running. | |
logDebug("Start running!") | |
url = currUrl(); | |
run(url) | |
} | |
catch(_error) | |
{ | |
handleError(url, _error) | |
} | |
}, | |
false | |
); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment