Last active
October 3, 2023 12:08
-
-
Save Klaster1/11a1e873db7dd6d2e5224dca3ebcfe47 to your computer and use it in GitHub Desktop.
MangaUpdates select scanlated series userscript
This file contains 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 MangaUpdates scanlated selector | |
// @author Klaster_1 | |
// @version 1.0.2 | |
// @match https://www.mangaupdates.com/mylist.html* | |
// @description Checks scanlated series from reading list. Useful to manage wishlists. | |
// @icon https://www.mangaupdates.com/favicon.ico | |
// @downloadURL https://gist.github.com/Klaster1/11a1e873db7dd6d2e5224dca3ebcfe47/raw/mu-select-scanlated.user.js | |
// @updateURL https://gist.github.com/Klaster1/11a1e873db7dd6d2e5224dca3ebcfe47/raw/mu-select-scanlated.user.js | |
// @grant none | |
// ==/UserScript== | |
const asyncWait = (duration) => | |
new Promise((resolve) => setTimeout(() => resolve(true), duration)); | |
const retry = async (fn, attempts) => { | |
try { | |
return await fn(); | |
} catch (e) { | |
if (attempts === 0) { | |
return Promise.reject(e); | |
} else { | |
console.log(`Retrying after ${3_000 / attempts}ms`); | |
await asyncWait(3_000 / attempts); | |
return retry(fn, attempts - 1); | |
} | |
} | |
}; | |
const gatherSeries = () => | |
Array.from(document.querySelectorAll(`a[href*='/series/'`), (a) => ({ | |
url: a.href, | |
checkbox: a.closest(".row").querySelector("input[type=checkbox]"), | |
name: a.innerText, | |
})); | |
const querySection = | |
({ sectionTitle, partialContentText }) => | |
(text) => | |
[ | |
...document | |
.createRange() | |
.createContextualFragment(text) | |
.querySelectorAll(".sCat"), | |
] | |
.filter((el) => el.innerText.includes(sectionTitle)) | |
.map((el) => el.nextElementSibling.innerText.includes(partialContentText)) | |
.pop(); | |
const splitArray = (arr, size) => | |
arr.reduce( | |
(a, v, i) => | |
a[a.length - 1].length < size | |
? [...a.slice(0, -1), [...a[a.length - 1], v]] | |
: [...a, [v]], | |
[[]] | |
); | |
const asyncMap = (fn, data = [], concurrency = 3) => | |
Promise.all( | |
splitArray(data, concurrency).map((chunk) => | |
chunk.reduce((a, b) => a.then(() => fn(b)), Promise.resolve()) | |
) | |
); | |
const getSeriesPage = (url) => | |
retry( | |
() => | |
fetch(url) | |
.then((res) => res.text()) | |
.then((text) => { | |
if (text.includes("503 Service Temporarily Unavailable")) { | |
return Promise.reject("Got a 503"); | |
} else { | |
return text; | |
} | |
}), | |
4 | |
).catch((e) => console.log(url, e)); | |
const selectSeries = ({ | |
predicate, | |
onResult = () => {}, | |
onComplete = () => {}, | |
concurrency = 3, | |
} = {}) => { | |
const allSeries = gatherSeries(); | |
asyncMap( | |
(series) => | |
getSeriesPage(series.url) | |
.then(predicate) | |
.then((matches) => { | |
series.checkbox.checked = matches; | |
series.matches = matches; | |
onResult(allSeries, series, matches); | |
}), | |
allSeries | |
).then(() => onComplete(allSeries)); | |
}; | |
const injectButton = ({ label, predicate }) => { | |
const button = document | |
.createRange() | |
.createContextualFragment( | |
` | |
<button type='button' class='button'>☑ ${label}</button> | |
` | |
) | |
.querySelector("button"); | |
button.style.marginLeft = "5px"; | |
let done = 0; | |
button.onclick = (e) => | |
selectSeries({ | |
predicate, | |
concurrency: 10, | |
onResult(all, item, value) { | |
done += 1; | |
e.target.innerText = `☑ ${label} (${done} / ${all.length})`; | |
}, | |
onComplete(all) { | |
done = 0; | |
e.target.innerText = `☑ ${label}`; | |
}, | |
}); | |
document.querySelector('button[value="Add Series"]').after(button); | |
}; | |
injectButton({ | |
label: `scanlated`, | |
predicate: querySection({ | |
sectionTitle: "Completely Scanlated?", | |
partialContentText: "Yes", | |
}), | |
}); | |
injectButton({ | |
label: `completed`, | |
predicate: querySection({ | |
sectionTitle: "Status in Country of Origin", | |
partialContentText: "(Complete)", | |
}), | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment