// ==UserScript==
// @name MangaDex upload
// @namespace https://github.com/ewasion
// @version 0.1.8
// @license GPL-3.0
// @description Highly customizable upload script for MangaDex
// @author Eva
// @match https://mangadex.com/upload/*
// @match https://mangadex.org/upload/*
// @exclude https://mangadex.com/upload/*?manual
// @exclude https://mangadex.org/upload/*?manual
// @icon https://mangadex.org/favicon.ico
// @homepage https://ewasion.github.io/userscripts/mangadex-upload/
// @grant none
// ==/UserScript==
function uploadLog(message, display) {
$('<p>', {
text: message,
class: display
}).appendTo($('#logs'));
}
function processFiles() {
const filepicker = $("#files");
if(filepicker.get(0).files.length === 0) {
return alert('You must select at least one file');
}
if(!$('#fallback_manga').val()) {
return alert('You must select a fallback manga');
}
const names = [];
for (var i = 0; i < filepicker.get(0).files.length; ++i) {
names.push(filepicker.get(0).files[i].name);
}
const chapter_titles = {};
const manga_db = {};
const group_db = {};
['#chapter_titles', '#manga_db', '#group_db'].forEach(function(db) {
$(db).val().split('\n').forEach(function(line) {
const split = line.split(/(\d+):(.+)/);
if(split[1] && split[2]) {
if(db == '#chapter_titles') chapter_titles[split[1]] = split[2];
if(db == '#manga_db') manga_db[split[2].toLowerCase()] = split[1];
if(db == '#group_db') group_db[split[2].toLowerCase()] = split[1];
}
});
if(db == '#manga_db') localStorage.setItem('manga_db', JSON.stringify(manga_db));
if(db == '#group_db') localStorage.setItem('group_db', JSON.stringify(group_db));
});
const regParts = $('#regex').val().match(/^\/(.*?)\/([gmiyu]*)$/);
const regex = regParts ? new RegExp(regParts[1], regParts[2]) : new RegExp($('#regex').val());
const uploads = [];
const lang = $('#lang_id').val();
const fallback_group = $('#group_id').val() ? $('#group_id').val() : 2;
const fallback_manga = $('#fallback_manga').val();
names.forEach(function(name, index) {
const matches = regex.exec(name + name);
if(matches) {
let manga = false;
Object.keys(manga_db).forEach(function(index) {
if(name.toLowerCase().includes(index)) {
manga = manga_db[index];
}
});
const manga_id = manga ? manga : fallback_manga;
const group = !matches[4] ? !matches[1] ? '' : matches[1] : matches[4];
const group_id = Object.keys(group_db).includes(group.toLowerCase()) ? group_db[group.toLowerCase()] : fallback_group;
const chapter = matches[3].replace(/^0+(?=\d)/, '');
const volume = matches[2].replace(/^0+(?=\d)/, '');
const title = Object.keys(chapter_titles).includes(chapter.toString()) ? chapter_titles[chapter] : '';
uploadLog('Added to the upload queue [' + group + '] Vol.' + volume + ' Ch.' + chapter + ' (' + name + ') {' + index + '}', 'normal');
const upload = new FormData();
upload.append('manga_id', manga_id);
upload.append('volume_number', volume);
upload.append('chapter_number', chapter);
upload.append('chapter_name', title);
upload.append('group_id', group_id);
upload.append('lang_id', lang);
upload.append('file', filepicker.get(0).files[index]);
uploads.push(upload);
} else {
uploadLog('Skipped. Regex doesn\'t match (' + name + ') {' + index + '}', 'warning');
}
});
if(uploads.length > 0) {
uploadFiles(uploads);
} else {
uploadLog('None of your files were uploaded', 'error');
}
}
function uploadFiles(files) {
const file = files.shift();
$.ajax({
url: "/ajax/actions.ajax.php?function=chapter_upload",
type: 'POST',
data: file,
cache: false,
contentType: false,
processData: false,
xhr: function() {
var myXhr = $.ajaxSettings.xhr();
if (myXhr.upload) {
myXhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
$('#progressbar_m').show();
$('#progressbar_m').width((Math.round(e.loaded/e.total*100) + '%'));
}
} , false);
}
return myXhr;
},
success: function (data) {
uploadLog('Uploaded [' + file.get('group_id') + '] Vol.' + file.get('volume_number') + ' Ch.' + file.get('chapter_number') + ' (' + file.get('file').name + ')', 'success');
$('#progressbar_m').hide();
$('#progressbar_m').width('0%');
if(files.length > 0) {
uploadFiles(files);
} else {
uploadLog('Upload complete', 'info');
}
},
error: function(err) {
uploadLog('Upload failed [' + file.get('group_id') + '] Vol.' + file.get('volume_number') + ' Ch.' + file.get('chapter_number') + ' (' + file.get('file').name + ')', 'error');
}
});
}
/* Display mass upload form */
(function() {
'use strict';
const filepicker = $('label[for="file"] + div > div');
const langpicker = $('#lang_id');
const grouppicker = $('#group_id');
const actions = $('#upload_form > div:last-child').prev();
$('#upload_form').remove(); /* Get rid of the low effort upload form */
$('#mass_upload_form').remove(); /* pls don't sue me Xnot */
$('.alert.alert-info[role="alert"]').remove();
const mangadex_uploader = $('<div>', {id: 'mangadex_uploader'});
/* Fancy title */
const title = $('.panel:last-child .panel-title');
const titleicon = title.find('span');
title.text(' Mass upload');
title.prepend(titleicon);
/* Make it look pretty */
if($('link[href="/bootstrap/css/bootstrap.2.css"]').length > 0) {
$('body').addClass('dark');
}
$('<style>', {text: `
#mangadex_uploader label:not(:first-child), #mangadex_uploader button[type="submit"], #mangadex_uploader a[role="button"] {
margin-top: 20px;
}
#logs:empty::before {
content: 'Logs will show up here.';
color: #ccc;
}
#mangadex_uploader::before {
display: block;
content: 'MangaDex uploader 0.1.8';
margin-bottom: 10px;
color: rgba(0, 0, 0, .3);
}
body.dark #mangadex_uploader::before {
color: rgba(255, 255, 255, .3);
}
#progressbar_m:not([style="width: 0%;"]) {
height: 38px;
border-radius: 5px;
margin-top: 80px;
float: none;
}
label {
display: block;
}
#files {
color: #999;
}
#logs .success {
color: #88ff88;
}
#logs .error {
color: #ff8888;
}
#logs .warning {
color: #ffc04d;
}
#logs .info {
color: #8888ff;
}
#logs .normal {
color: #999;
}
body.dark #logs .normal {
color: #ccc;
}
`}).appendTo('head');
/* Files */
$('<label>', {
for: 'files',
text: 'Files'
}).appendTo(mangadex_uploader);
filepicker.find('input[type="file"]').attr({id: 'files', multiple: ''});
filepicker.appendTo(mangadex_uploader);
/* Regex */
$('<label>', {
for: 'regex',
text: 'Regex'
}).appendTo(mangadex_uploader);
$('<input>', {
type: 'text',
class: 'form-control',
id: 'regex',
placeholder: 'Regex',
value: /.*?(?:\[([^\]]+)\].*)?v[^\dc]*?(\.?\d+(?:\.\d+)*[a-zA-Z]?(?!\d)).*?c[^\dv]*?(\.?\d+(?:\.\d+)*[a-zA-Z]?(?!\d)).*?(?:\[([^\]]+)\].*)?\.(?:zip|cbz)$/i
}).appendTo(mangadex_uploader);
/* Fallbacks */
$('<label>', {
for: 'fallback_manga',
text: 'Fallback manga ID'
}).appendTo(mangadex_uploader);
$('<input>', {
type: 'number',
class: 'form-control',
id: 'fallback_manga',
placeholder: 'Fallback manga ID'
}).appendTo(mangadex_uploader);
$('<label>', {
for: 'group_id',
text: 'Fallback group'
}).appendTo(mangadex_uploader);
grouppicker.appendTo(mangadex_uploader);
/* Language */
$('<label>', {
for: 'lang_id',
text: 'Language'
}).appendTo(mangadex_uploader);
langpicker.appendTo(mangadex_uploader);
/* Chapter titles */
$('<label>', {
for: 'chapter_titles',
text: 'Chapter titles'
}).appendTo(mangadex_uploader);
$('<textarea>', {
class: 'form-control',
id: 'chapter_titles',
placeholder: `1:Naruto Uzumaki!!
2:Konohamaru!!
3:Sasuke Uchiha!!
4:Kakashi Hatake!!
5:Unpreparedness is One's Greatest Enemy!!`
}).appendTo(mangadex_uploader);
/* Manga DB */
$('<label>', {
for: 'manga_db',
text: 'Manga DB'
}).appendTo(mangadex_uploader);
$('<textarea>', {
class: 'form-control',
id: 'manga_db',
placeholder: `5:naruto
153:detective conan
188:nichijou
69:xblade`
}).appendTo(mangadex_uploader);
/* Group DB */
$('<label>', {
for: 'group_db',
text: 'Group DB'
}).appendTo(mangadex_uploader);
$('<textarea>', {
class: 'form-control',
id: 'group_db',
placeholder: `1334:iem
621:fh
128:inp mangaz
1196:kefi
1335:wtf
645:bushido
577:binktopia
937:inane
1370:project_88
1371:wek`
}).appendTo(mangadex_uploader);
/* Back/upload buttons */
actions.find('#upload_button').attr('id', 'start_uploading');
actions.find('[href^="/manga/"]').attr('href', '?manual');
actions.appendTo(mangadex_uploader);
/* Progress bar */
$('<div>', {
id: 'progressbar_m',
role: 'progressbar',
'aria-valuemin': '0',
'aria-valuemax': '100',
'aria-valuenow': '0',
style: 'width: 0%;',
class: 'progress-bar progress-bar-info'
}).appendTo(mangadex_uploader);
/* Logs */
$('<label>', {
text: 'Logs'
}).appendTo(mangadex_uploader);
$('<div>', {
id: 'logs'
}).appendTo(mangadex_uploader);
mangadex_uploader.appendTo('.panel:last-child .panel-body');
['manga_db', 'group_db'].forEach(function(db) {
if(localStorage.getItem(db)) {
let text = '';
const entries = JSON.parse(localStorage.getItem(db));
Object.keys(entries).forEach(function(entry) {
text += entries[entry] + ':' + entry + '\n';
});
$('#' + db).val(text);
}
});
$('#start_uploading').click(function() {processFiles();});
$('.btn-file :file').on('fileselect', function(event, numFiles, label) {
let input = $(this).parents('.input-group').find(':text'),
log = numFiles > 1 ? numFiles + ' files selected' : label;
if(input.length) {
input.val(log);
}
$("#upload_button").focus();
});
})();