// ==UserScript==
// @name ETI MAL Integration
// @namespace pendevin
// @description Integrates MyAnimeList anime history into posts
// @include http://endoftheinter.net/inboxthread.php*
// @include http://boards.endoftheinter.net/postmsg.php*
// @include http://boards.endoftheinter.net/showmessages.php*
// @include https://endoftheinter.net/inboxthread.php*
// @include https://boards.endoftheinter.net/postmsg.php*
// @include https://boards.endoftheinter.net/showmessages.php*
// @require http://code.jquery.com/jquery-2.1.3.min.js
// @version 3.3
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
//enter the characters you want to replace with your last watched show
//MAKE SURE THIS IS UNIQUE IN YOUR SIG
const EPISODE_REPLACER='/et';
//enter the characters you want to replace with your episode count for the day
//MAKE SURE THIS IS UNIQUE IN YOUR SIG
const COUNT_REPLACER='/ec';
//enter the url for your MyAnimeList history page
//if you only want to use anime or manga updates, add /anime or /manga to the end of the url
const HISTORY_URL='http://myanimelist.net/history/pendevin';
//ll breaks without noconflict jquery
this.$=this.jQuery=jQuery.noConflict(true);
//i got this from shoecream's userscript autoupdater at http://userscripts.org/scripts/show/45904
var XHR={
// r.doc is the returned page
// r.respose is the response element
createDoc:function(response,callback,optional){
var doc=document.implementation.createDocument('','',null);
var html=document.createElement("html");
html.innerHTML=response.responseText;
doc.appendChild(html);
var r={};
r.response=response;
r.doc=doc;
callback(r,optional);
},
//sends the XHR request, callback is the function to call on the returned page
get:function(url,callback,optional){
if(optional==undefined)optional=null;
GM_xmlhttpRequest({
method:'GET',
url:url,
headers:{
'User-Agent':navigator.userAgent,
'Content-Type':'application/x-www-form-urlencoded',
'Host':'myanimelist.net',
'Accept':'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*',
'Pragma':'no-cache'
},
onload:function(r){XHR.createDoc(r,callback,optional);}
});
}
};
//finds the last index of a regular expression value
//takes a string and a regex object
//kinda slow :(
function reLastIndex(string,regex){
var index=-1;
//we're going backwards from the end and searching for the first occurrence we find
for(var i=string.length-1;i>0;i++){
//once we find it, we're outta here
if(string.substring(i).search(regex)!=-1){
index=i;
break;
}
}
return index;
}
function reEscape(str){
var specials=new RegExp("[.*+?|()\\[\\]{}\\\\]","g"); // .*+?|()[]{}\
return str.replace(specials,"\\$&");
}
//parse a mal date into a javascript date thingy
//takses a string, motherfucker, and returns a Date object
function parseMalDate(time){
var now=new Date;
var clock;
//some date case
if(time.match(/\w\w\w \d\d?,/)){
clock=time.match(/(\w\w\w) (\d\d?), (\d\d?):(\d\d) ([AP]M)/);
//adjust for am/pm
clock[3]=parseInt(clock[3]!=12?clock[3]:0)+(clock[5]=='PM'?12:0);
//gotta parse the month because ugh
var monthKey={
'Jan':0,
'Feb':1,
'Mar':2,
'Apr':3,
'May':4,
'Jun':5,
'Jul':6,
'Aug':7,
'Sep':8,
'Oct':9,
'Nov':10,
'Dec':11
};
clock[1]=monthKey[clock[1]];
//date object is (year (4 digits), month (0-11), date (1-31), hour (0-23), minute (0-59), second (0-59))
return new Date(now.getFullYear(),clock[1],parseInt(clock[2],10),clock[3],parseInt(clock[4]),0);
}
//some time yesterday case
else if(time.match(/Yesterday,/)){
clock=time.match(/Yesterday, (\d\d?):(\d\d) ([AP]M)/);
clock[1]=parseInt(clock[1]!=12?clock[1]:0)+(clock[3]=='PM'?12:0);
return new Date(now.getFullYear(),now.getMonth(),now.getDate()-1,clock[1],parseInt(clock[2]),0);
}
//some time today case
else if(time.match(/Today,/)){
clock=time.match(/Today, (\d\d?):(\d\d) ([AP]M)/);
clock[1]=parseInt(clock[1]!=12?clock[1]:0)+(clock[3]=='PM'?12:0);
return new Date(now.getFullYear(),now.getMonth(),now.getDate(),clock[1],parseInt(clock[2]),0);
}
//hour(s) ago case
else if(time.match(/hours? ago/)){
return new Date(now.getTime()-parseInt(time.match(/\d\d?/))*3600000);
}
//minute(s) ago case
else if(time.match(/minutes? ago/)){
return new Date(now.getTime()-parseInt(time.match(/\d\d?/))*60000);
}
//second(s) ago case
else if(time.match(/seconds? ago/)){
return new Date(now.getTime()-parseInt(time.match(/\d\d?/))*1000);
}
return now;
}
//figure out how long ago something was, with expanding time scales
//takes a Date object!!, and tells you how long ago it was in the form ' (<time(s)> ago)''
//now returns an object with the numeral ago and the unit as attributes
function differenceEngine(then){
var diff={
diff:new Date().getTime()-then.getTime()
};
//days difference
if(diff.diff>86400000){
diff.numeral=(diff.diff-diff.diff%86400000)/86400000;
diff.unit='day'+(diff.numeral>1?'s':'');
}
//hours difference
else if(diff.diff>3600000){
diff.numeral=(diff.diff-diff.diff%3600000)/3600000;
diff.unit='hour'+(diff.numeral>1?'s':'');
}
//minutes difference
else if(diff.diff>60000){
diff.numeral=(diff.diff-diff.diff%60000)/60000;
diff.unit='minute'+(diff.numeral>1?'s':'');
}
//seconds difference
else if(diff.diff>1000){
diff.numeral=(diff.diff-diff.diff%1000)/1000;
diff.unit='second'+(diff.numeral>1?'s':'');
}
//guess it could have been less than a second ago but i can't imagine why
else{
diff.numeral=1;
diff.unit='second';
}
return diff;
}
//when you get the history response, insert that info into the quickpost box
function malShit(r){
var doc=$(r.doc);
//do shit
//make sure there's a history
if(doc.find('#horiznav_nav').next().text()=='No history found'){
return null;
}
//grab the rows and shit
var episodes=doc.find('#horiznav_nav + div tr');
//find most recent anime update
var anime={
raw:episodes.has('td:contains(" ep. ")').first()
};
//parse anime update
if(anime.raw.length){
anime.name=anime.raw.find('td:first-child>a').text();
anime.link=anime.raw.find('td:first-child>a').attr('href');
anime.episode=anime.raw.find('td:first-child>strong').text();
anime.date=parseMalDate(anime.raw.find('td:last-child').text());
anime.elapsed=differenceEngine(anime.date);
anime.type='anime';
}
//find most recent manga update
var manga={
raw:episodes.has('td:contains(" chap. ")').first()
};
//parse manga update
if(manga.raw.length){
manga.name=manga.raw.find('td:first-child>a').text();
manga.link=manga.raw.find('td:first-child>a').attr('href');
manga.episode=manga.raw.find('td:first-child>strong').text();
manga.date=parseMalDate(manga.raw.find('td:last-child').text());
manga.elapsed=differenceEngine(manga.date);
manga.type='manga';
}
//get today's ep count
//make sure that it's actually today'
var count={
raw:episodes.find('div.normal_header:contains("Today") small'),
};
count.number=count.raw[0]?count.raw.text().slice(1,-1):"0";
//which update is more recent and also really there
//ugh this is ungainly
var upd8=null;
if(anime.raw[0]&&manga.raw[0]){
if(anime.elapsed.diff<manga.elapsed.diff){
upd8=anime;
}
else{
upd8=manga;
}
}
else if(anime.raw[0]){
upd8=anime;
}
else if(manga.raw[0]){
upd8=manga;
}
//now that we've extracted our data, check to see if it's different from what we've got
if(upd8&&(cachedData.name!=upd8.name||cachedData.episode!=upd8.episode||count.number!=cachedData.count)){
//if the episode changed
if(upd8&&(cachedData.name!=upd8.name||cachedData.episode!=upd8.episode)){
//cache our shit
cachedData=upd8;
}
//i suck cocks
cachedData.count=count.number;
//make a display string i guess
var display=cachedData.name+(cachedData.type=='anime'?' ep. ':' chap. ')+cachedData.episode+' ('+cachedData.elapsed.numeral+' '+cachedData.elapsed.unit+' ago)';
//remember the quickpost box position
var scrollPosition=quickpost.prop('scrollTop');
var cursorStart=quickpost.prop('selectionStart');
var cursorEnd=quickpost.prop('selectionEnd');
//insert the shit
//gotta do our stored shit because fucking cocks
quickpost.val(quickpost.val().replace(rxCocks,'$1'+(quickpost.remember.replace(rxEpisode,'$1'+display+'$2').replace(rxCount,'$1'+cachedData.count+'$2'))));
//restore the quickpost box position
quickpost.prop('scrollTop',scrollPosition);
quickpost.prop('selectionStart',cursorStart);
quickpost.prop('selectionEnd',cursorEnd);
}
//save the data
var gmCache={
name:cachedData.name,
link:cachedData.link,
episode:cachedData.episode,
date:cachedData.date.valueOf(),
count:cachedData.count,
type:cachedData.type
}
GM_setValue('cached',JSON.stringify(gmCache));
return null;
}
//when the quickpost box opens, send off a request to mal for the history
function onFocus(e){
//don't do this too often pls
quickpost.off('focus.mal');
//why the fuck doesn't this always work
//maybe it has to do with gm hating event calls but not timeouts, which is whack
if(!GM_getValue('test',true)){
window.setTimeout(onFocus,500);
return null;
}
//get a new elapsed time since now is different
cachedData.elapsed=differenceEngine(new Date(cachedData.date));
//make a display string i guess
var display=cachedData.name!=''?
cachedData.name+(cachedData.type=='anime'?' ep. ':' chap. ')+cachedData.episode+' ('+cachedData.elapsed.numeral+' '+cachedData.elapsed.unit+' ago)':
HISTORY_URL
;
//save the position of the cursor and scrollbar
var scrollPosition=quickpost.prop('scrollTop');
var cursorStart=quickpost.prop('selectionStart');
var cursorEnd=quickpost.prop('selectionEnd');
//remember quickpost value because cocks
quickpost.remember=quickpost.val().match(rxCocks)[2];
//insert our data
quickpost.val(quickpost.val().replace(rxEpisode,'$1'+display+'$2').replace(rxCount,'$1'+cachedData.count+'$2'));
//restore the position of the cursor and scrollbar
quickpost.prop('scrollTop',scrollPosition);
quickpost.prop('selectionStart',cursorStart);
quickpost.prop('selectionEnd',cursorEnd);
//send off our get request to mal
XHR.get(HISTORY_URL,malShit);
return null;
}
//find the quickpost box and listen for it to open
//this gets the quickpost box and also the message box on postmsg.php
var quickpost=$('.quickpost textarea[name="message"], textarea#message');
//null data
var cachedData=JSON.parse(GM_getValue('cached','{"name":"","episode":"","date":0,"count":"0"}'));
//make a regexps
var rxEpisode=new RegExp('(---[\\w\\W]+)'+reEscape(EPISODE_REPLACER)+'([\\w\\W]*)');
var rxCount=new RegExp('(---[\\w\\W]+)'+reEscape(COUNT_REPLACER)+'([\\w\\W]*)');
var rxCocks=new RegExp('([\\w\\W]*)(---[\\w\\W]+)');
//listen for shit
quickpost.on('focus.mal',onFocus);
//reinitialize after posting
$('form.quickpost input[name="post"]').on('click.mal',function(){
quickpost.on('focus.mal',onFocus);
});