- // ==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);
- });