// ==UserScript==
// @name MangaDex Shit Filter
// @version 1.5
// @namespace mangadexshitfilter
// @author nazatanada
// @description Cleans garbage and embeds tags on latest updates page and other title list pages on Mangadex.org/.cc
// @include /https?:\/\/mangadex\.org\/?.*/
// @include /https?:\/\/mangadex\.cc\/?.*/
// @require https://code.jquery.com/jquery-3.2.1.min.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @run-at document-end
// ==/UserScript==
const UPDATE = 7 * 24 * 60 * 60 * 1000; // Updates records weekly
const DEXKEY = 'dex1.3';
const LANG = { 1: 'English', 2: 'Japanese', 3: 'Polish', 8: 'German', 10: 'French', 12: 'Vietnamese', 21: 'Chinese', 27: 'Indonesian', 28: 'Korean', 29: 'Spanish (LATAM)', 32: 'Thai', 34: 'Filipino', 35: 'Chinese (Trad)' };
const STAT = { 1: 'Ongoing', 2: 'Completed', 3: 'Cancelled', 4: 'Hiatus' };
const DEMO = { 1: 'Shounen', 2: 'Shoujo', 3: 'Seinen', 4: 'Josei' };
const GENR = { 1: '4-Koma', 4: 'Award Winning', 7: 'Doujinshi', 9: 'Ecchi', 2: 'Action', 3: 'Adventure', 5: 'Comedy', 8: 'Drama', 6: 'Cooking', 10: 'Fantasy', 11: 'Gyaru', 12: 'Harem', 13: 'Historical', 14: 'Horror', 16: 'Martial Arts', 17: 'Mecha', 18: 'Medical', 19: 'Music', 20: 'Mystery', 21: 'Oneshot', 22: 'Psychological', 23: 'Romance', 24: 'School Life', 25: 'Sci-Fi', 28: 'Shoujo Ai', 30: 'Shounen Ai', 31: 'Slice of Life', 32: 'Smut', 33: 'Sports', 34: 'Supernatural', 35: 'Tragedy', 36: 'Long Strip', 37: 'Yaoi', 38: 'Yuri', 40: 'Video Games', 41: 'Isekai', 42: 'Adaptation', 43: 'Anthology', 44: 'Web Comic', 45: 'Full Color', 46: 'User Created', 47: 'Official Colored', 48: 'Fan Colored', 49: 'Gore', 50: 'Sexual Violence', 51: 'Crime', 52: 'Magical Girls', 53: 'Philosophical', 54: 'Superhero', 55: 'Thriller', 56: 'Wuxia', 57: 'Aliens', 58: 'Animals', 59: 'Crossdressing', 60: 'Demons', 61: 'Delinquents', 62: 'Genderswap', 63: 'Ghosts', 64: 'Monster Girls', 65: 'Loli', 66: 'Magic', 67: 'Military', 68: 'Monsters', 69: 'Ninja', 70: 'Office Workers', 71: 'Police', 72: 'Post-Apocalyptic', 73: 'Reincarnation', 74: 'Reverse Harem', 75: 'Samurai', 76: 'Shota', 77: 'Survival', 78: 'Time Travel', 79: 'Vampires', 80: 'Traditional Games', 81: 'Virtual Reality', 82: 'Zombies', 83: 'Incest', 84: 'Mafia' };
let parallel = 0;
let paradise = 5; // Decrease this value if you get a red DESU spam
function keyof(arr, val) {
let key = 0;
if (val) {
Object.entries(arr).some(e => val === e[1] && (key = e[0]));
}
return parseInt(key);
}
function make(doc, maid) {
const gkeys = [];
doc.find('a[href^="/genre/"]').each((idx, elm) => {
gkeys.push(keyof(GENR, $(elm).text()));
});
const dict = GM_getValue(DEXKEY, {});
dict[maid] = {
'm': Date.now(),
'l': keyof(LANG, doc.find('.card-header .flag').first().attr('title')),
's': keyof(STAT, doc.find('div.strong:contains("Pub. status:")').first().next().text()),
'd': keyof(DEMO, doc.find('a[href^="/search?demo_id="]').first().text()),
'g': gkeys,
};
GM_setValue(DEXKEY, dict);
return dict[maid];
}
function filt(madict, shit) {
function wrap(bad, ger) {
return ger ? '<a class="badge badge-' + bad + '">' + ger + '</a> ' : '';
}
if (madict['l'] === 2 && shit[1] !== 'm2254328958') {
let e = '<div style="color: white">';
e += wrap('primary', STAT[madict['s']]);
e += wrap('primary', DEMO[madict['d']]);
madict['g'].forEach(g => {
const gstr = GENR[g];
e += wrap(/(^(Y[au]|W|U)|Ai$)/.test(gstr) ? 'warning' : 'secondary', gstr);
});
shit[0].append(e + '</div>');
} else {
shit[0].remove();
}
}
function gets(deq, maid, shit) {
return () => {
$.get({
url: '/title/' + maid,
success: (raw) => {
if (raw) {
filt(make($(raw), maid), shit);
}
},
error: () => {
if (paradise) {
deq.queue(gets(deq, maid, shit));
}
},
complete: (xhr) => {
parallel--;
if (xhr.status === 403 || xhr.status === 503) {
paradise = 0;
deq.clearQueue();
shit[0].parent().prepend($('<div style="color:red"> DESU </div>'));
} else {
do {
deq.dequeue();
} while ( parallel++ < paradise );
}
}
});
};
}
function mmh2_32(str, seed) {
var l = str.length, h = seed ^ l, i = 0, k;
while (l >= 4) {
k = ((str.charCodeAt(i) & 0xff)) |
((str.charCodeAt(++i) & 0xff) << 8) |
((str.charCodeAt(++i) & 0xff) << 16) |
((str.charCodeAt(++i) & 0xff) << 24);
k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
k ^= k >>> 24;
k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
l -= 4; ++i;
}
switch (l) {
case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
case 1: h ^= (str.charCodeAt(i) & 0xff);
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
}
h ^= h >>> 13;
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
h ^= h >>> 15;
return h >>> 0;
}
$(document).ready(() => {
const now = Date.now();
const deq = $({});
['dex', 'dex1.2'].forEach(v => GM_deleteValue(v, null));
$('.tab-content .manga_title').each((idx, elm) => {
const maid = $(elm).attr('href').match(/title\/(\d+)/i)[1];
const shit = [ $(elm).parent().parent(), 'm' + mmh2_32('m' + maid, maid)];
const dict = GM_getValue(DEXKEY, null);
if (dict && dict[maid] && now - dict[maid]['m'] < UPDATE) {
filt(dict[maid], shit);
} else {
deq.queue(gets(deq, maid, shit));
}
});
});