// ==UserScript==
// @name KissAnime-Bookmarks to MyAnimeList export [UPDATED 2020]
// @namespace https://greasyfork.org/users/412318
// @version 3.7
// @description Generates an xml-File from your KA-Bookmarks that can be imported to MAL
// @author Hentai God and henrik9999
// @match *://kissanime.ru/BookmarkList*
// @connect api.malsync.moe
// @connect graphql.anilist.co
// @grant GM.xmlHttpRequest
// @grant GM_xmlHttpRequest
// @run-at document-idle
// ==/UserScript==
/* beautify preserve:start */
function XMLWriter(a,b){if(a)this.encoding=a;if(b)this.version=b};(function(){XMLWriter.prototype={encoding:'ISO-8859-1',version:'1.0',formatting:'indented',indentChar:'\t',indentation:1,newLine:'\n',writeStartDocument:function(a){this.close();this.stack=[];this.standalone=a},writeEndDocument:function(){this.active=this.root;this.stack=[]},writeDocType:function(a){this.doctype=a},writeStartElement:function(c,d){if(d)c=d+':'+c;var a=this,b=a.active,e={n:c,a:{},c:[]};if(b){b.c.push(e);this.stack.push(b)}else a.root=e;a.active=e},writeEndElement:function(){this.active=this.stack.pop()||this.root},writeAttributeString:function(a,b){if(this.active)this.active.a[a]=b},writeString:function(a){if(this.active)this.active.c.push(a)},writeElementString:function(a,b,c){this.writeStartElement(a,c);this.writeString(b);this.writeEndElement()},writeCDATA:function(a){this.writeString('<![CDATA['+a+']]>')},writeComment:function(a){this.writeString('<!-- '+a+' -->')},flush:function(){var a=this,b='',c='',d=a.indentation,e=a.formatting.toLowerCase()=='indented',f='<?xml version="'+a.version+'" encoding="'+a.encoding+'"';if(a.stack&&a.stack[0])a.writeEndDocument();if(a.standalone!==undefined)f+=' standalone="'+!!a.standalone+'"';f+=' ?>';f=[f];if(a.doctype&&a.root)f.push('<!DOCTYPE '+a.root.n+' '+a.doctype+'>');if(e){while(d--)b+=a.indentChar}if(a.root)k(a.root,c,b,f);return f.join(e?a.newLine:'')},close:function(){var a=this;if(a.root)j(a.root);a.active=a.root=a.stack=null},getDocument:window.ActiveXObject?function(){var a=new ActiveXObject('Microsoft.XMLDOM');a.async=!1;a.loadXML(this.flush());return a}:function(){return(new DOMParser()).parseFromString(this.flush(),'text/xml')}};function j(a){var l=a.c.length;while(l--){if(typeof a.c[l]=='object')j(a.c[l])}a.n=a.a=a.c=null};function k(a,b,c,d){var e=b+'<'+a.n,f=a.c.length,g,h,i=0;for(g in a.a)e+=' '+g+'="'+a.a[g]+'"';e+=f?'>':' />';d.push(e);if(f){do{h=a.c[i++];if(typeof h=='string'){if(f==1)return d.push(d.pop()+h+'</'+a.n+'>');else d.push(b+c+h)}else if(typeof h=='object')k(h,b+c,c,d)}while(i<f);d.push(b+'</'+a.n+'>')}}})();
var blobData = (function() {
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
return function(data) {
var xml = data,
blob = new Blob([xml], {
type: "octet/stream"
}),
url = window.URL.createObjectURL(blob);
return url;
};
}());
/* beautify preserve:end */
const api = {};
if (typeof GM_xmlhttpRequest !== 'undefined') {
api.GM_xmlhttpRequest = GM_xmlhttpRequest;
} else if (
typeof GM !== 'undefined' &&
typeof GM.xmlHttpRequest !== 'undefined'
) {
api.GM_xmlhttpRequest = GM.xmlHttpRequest;
}
const all = document.getElementsByClassName("trAnime");
const animes = document.getElementsByClassName("aAnime");
var current = 0;
var watchedcomplete = 0;
var unwatched = 0;
var watching = 0;
var total = all.length;
var v = new XMLWriter();
v.encoding = "UTF-8";
v.writeStartDocument(true);
v.writeStartElement('myanimelist');
v.writeComment("Document generated by the KA-BookMarks to MAL Script v3.7!");
v.writeComment("Created by TheTh0rus!");
v.writeComment("Updated by https://greasyfork.org/en/users/412318-henrik9999");
function init() {
document.documentElement.innerHTML += "<div style='position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:white;color:black;text-align:center;z-index:100000;' id='BMMAL'></div>";
var span = document.createElement("span");
span.id = "counter";
var bar = document.createElement("progress");
bar.setAttribute("value", 0);
bar.setAttribute("max", total);
bar.style = "width: 100%;";
bar.id = "bar";
var alertEl = document.createElement("span");
alertEl.id = "alertHolder";
alertEl.style = "color: red;";
//var button = document.createElement("button");
//button.innerHTML = "Export button incase of being stuck at the end (do not use only if stuck)";
//button.addEventListener("click", function() {
// finished(true)
//});
document.getElementById("BMMAL").appendChild(bar);
document.getElementById("BMMAL").appendChild(span);
document.getElementById("BMMAL").appendChild(document.createElement("br"));
//document.getElementById("BMMAL").appendChild(button);
document.getElementById("BMMAL").appendChild(document.createElement("br"));
document.getElementById("BMMAL").appendChild(alertEl);
mainLoop();
}
function mainLoop() {
var i = current;
if (i < animes.length) {
var title = animes[i].innerHTML;
title = title.replace(/ *(\(dub\)|\(sub\)|\(uncensored\)|\(uncut\)|\(subbed\)|\(dubbed\))/i, '');
title = title.replace(/ *\([^)]+audio\)/i, '');
title = title.replace(/ BD( |$)/i, '');
title = title.trim();
var id = animes[i].getAttribute("href").split("/")[2].toLowerCase();
//if completed and watched
if (all[i].innerHTML.indexOf("Completed") > -1 && all[i].innerHTML.indexOf('style="display: inline" class="aRead"') > -1) {
watchedcomplete += 1;
current += 1;
searchMalSync(id, title, "Completed");
return;
}
//if airing and watched
if (all[i].innerHTML.indexOf("Completed") == -1 && all[i].innerHTML.indexOf('style="display: inline" class="aUnRead"') == -1 && all[i].innerHTML.indexOf("Episode") > -1) {
let episode = all[i].innerHTML.match(/episode\s\d+/i)
if (episode && episode[0] && episode[0].replace(/\D+/g, '')) {
episode = Number(episode[0].replace(/\D+/g, ''));
watching += 1;
current += 1;
searchMalSync(id, title, "Watching", episode);
return;
}
}
//if not watching
if (all[i].innerHTML.indexOf('style="display: inline" class="aUnRead"') > -1) {
unwatched += 1;
current += 1;
searchMalSync(id, title, "Plan to Watch");
return;
}
current += 1;
mainLoop();
} else {
finished();
}
}
async function searchMalSync(kissId, animeTitle, status, episode = 0) {
console.log("searching malsync", kissId)
const data = await new Promise((resolve, reject) => {
api.GM_xmlhttpRequest({
method: "GET",
url: "https://api.malsync.moe/page/Kissanime/" + kissId,
onload: function(response) {
if (response.status === 200 && response.responseText) {
resolve(JSON.parse(response.responseText));
} else {
resolve();
}
},
onerror: function(response) {
resolve();
}
});
})
if (data && data.malUrl) {
getAnilistFromMalId(data.malUrl.split("/")[4], animeTitle, status, episode)
} else {
searchAnilist(animeTitle, status, episode)
}
}
async function getAnilistFromMalId(malId, animeTitle, status, episode) {
console.log("getting anilistdata for", malId);
const query =
`query ($id: Int, $type: MediaType) {
Media(idMal: $id, type: $type) {
idMal
title {
romaji
}
episodes
}
}`
const queryData = JSON.stringify({
query,
variables: {
id: malId,
type: "ANIME"
}
});
const data = await new Promise((resolve, reject) => {
api.GM_xmlhttpRequest({
method: "POST",
url: "https://graphql.anilist.co",
data: queryData,
headers: {
'content-type': 'application/json',
accept: 'application/json'
},
onload: function(response) {
if (response.status === 200 && response.responseText) {
resolve(JSON.parse(response.responseText));
} else {
resolve();
}
},
onerror: function(response) {
resolve();
}
});
})
if (data && data.data && data.data.Media && data.data.Media.idMal) {
handleAnime(data.data.Media, animeTitle, status, episode)
} else {
handleError("could not find " + animeTitle + " on AniList")
}
}
async function searchAnilist(animeTitle, status, episode) {
console.log("searching anilist for", animeTitle);
const query = `
query ($search: String) {
anime: Page (perPage: 10) {
results: media (type: ANIME, search: $search) {
idMal
title {
romaji
}
episodes
}
}
}
`;
const queryData = JSON.stringify({
query,
variables: {
search: animeTitle
}
});
const data = await new Promise((resolve, reject) => {
api.GM_xmlhttpRequest({
method: "POST",
url: "https://graphql.anilist.co",
data: queryData,
headers: {
'content-type': 'application/json',
accept: 'application/json'
},
onload: function(response) {
if (response.status === 200 && response.responseText) {
resolve(JSON.parse(response.responseText));
} else {
resolve();
}
},
onerror: function(response) {
resolve();
}
});
})
if (data && data.data && data.data.anime && data.data.anime.results && data.data.anime.results[0] && data.data.anime.results[0].idMal) {
handleAnime(data.data.anime.results[0], animeTitle, status, episode)
} else {
handleError("could not find " + animeTitle + " on AniList")
}
}
function handleAnime(data, animeTitle, status, episode) {
try {
console.log("handling anime:", data, animeTitle, status);
var watched = 0;
if (status == "Completed") {
watched = data.episodes;
}
if (status == "Watching") {
watched = episode;
}
v.writeStartElement('anime');
v.writeComment("kissanime title " + animeTitle);
v.writeComment("found title " + data.title.romaji);
v.writeElementString('series_animedb_id', "" + data.idMal + "");
v.writeElementString('my_status', "" + status + "");
v.writeElementString('update_on_import', '1');
v.writeElementString('my_watched_episodes', "" + watched + "");
v.writeEndElement();
} catch (e) {
handleError("Scraping-error at " + animeTitle, false);
}
if (document.getElementById("bar") && document.getElementById("counter")) {
document.getElementById("bar").value = current;
document.getElementById("counter").innerHTML = "<b>" + current + " / " + total + "</b>";
}
setTimeout(function() {
mainLoop();
}, 1000);
}
function finished(skip = false) {
if (current == total || skip) {
v.writeStartElement('myinfo');
v.writeElementString('user_export_type', '1');
v.writeElementString('user_total_anime', "" + total + "");
v.writeElementString('user_total_watching', "" + watching + "");
v.writeElementString('user_total_completed', "" + watchedcomplete + "");
v.writeElementString('user_total_onhold', '0');
v.writeElementString('user_total_dropped', '0');
v.writeElementString('user_total_plantowatch', "" + unwatched + "");
v.writeEndElement();
v.writeEndElement();
v.writeEndDocument();
let alerts = document.getElementById("alertHolder").innerHTML;
document.getElementById("BMMAL").innerHTML = "<h3><a href='" + blobData(v.flush().replace('"true"', '"yes"')) + "' id='MALdl' style='color:blue;font-weight:bold;' download='importToMAL.xml'>Download MyAnimeList XML</a></h3>";
document.getElementById("BMMAL").innerHTML += alerts;
current = total + 1;
}
}
function handleError(msg, next = true) {
console.error(msg);
addAlert(msg);
if (next) {
mainLoop();
}
}
function addAlert(txt) {
document.getElementById("alertHolder").innerHTML += "<br>" + txt;
}
init();