ETI MAL Integration

Integrates MyAnimeList anime history into posts

  1. // ==UserScript==
  2. // @name ETI MAL Integration
  3. // @namespace pendevin
  4. // @description Integrates MyAnimeList anime history into posts
  5. // @include http://endoftheinter.net/inboxthread.php*
  6. // @include http://boards.endoftheinter.net/postmsg.php*
  7. // @include http://boards.endoftheinter.net/showmessages.php*
  8. // @include https://endoftheinter.net/inboxthread.php*
  9. // @include https://boards.endoftheinter.net/postmsg.php*
  10. // @include https://boards.endoftheinter.net/showmessages.php*
  11. // @require http://code.jquery.com/jquery-2.1.3.min.js
  12. // @version 3.3
  13. // @grant GM_xmlhttpRequest
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // ==/UserScript==
  17.  
  18.  
  19. //enter the characters you want to replace with your last watched show
  20. //MAKE SURE THIS IS UNIQUE IN YOUR SIG
  21. const EPISODE_REPLACER='/et';
  22.  
  23. //enter the characters you want to replace with your episode count for the day
  24. //MAKE SURE THIS IS UNIQUE IN YOUR SIG
  25. const COUNT_REPLACER='/ec';
  26.  
  27. //enter the url for your MyAnimeList history page
  28. //if you only want to use anime or manga updates, add /anime or /manga to the end of the url
  29. const HISTORY_URL='http://myanimelist.net/history/pendevin';
  30.  
  31. //ll breaks without noconflict jquery
  32. this.$=this.jQuery=jQuery.noConflict(true);
  33.  
  34. //i got this from shoecream's userscript autoupdater at http://userscripts.org/scripts/show/45904
  35. var XHR={
  36. // r.doc is the returned page
  37. // r.respose is the response element
  38. createDoc:function(response,callback,optional){
  39. var doc=document.implementation.createDocument('','',null);
  40. var html=document.createElement("html");
  41. html.innerHTML=response.responseText;
  42. doc.appendChild(html);
  43. var r={};
  44. r.response=response;
  45. r.doc=doc;
  46. callback(r,optional);
  47. },
  48.  
  49. //sends the XHR request, callback is the function to call on the returned page
  50. get:function(url,callback,optional){
  51. if(optional==undefined)optional=null;
  52. GM_xmlhttpRequest({
  53. method:'GET',
  54. url:url,
  55. headers:{
  56. 'User-Agent':navigator.userAgent,
  57. 'Content-Type':'application/x-www-form-urlencoded',
  58. 'Host':'myanimelist.net',
  59. 'Accept':'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*',
  60. 'Pragma':'no-cache'
  61. },
  62. onload:function(r){XHR.createDoc(r,callback,optional);}
  63. });
  64. }
  65. };
  66.  
  67. //finds the last index of a regular expression value
  68. //takes a string and a regex object
  69. //kinda slow :(
  70. function reLastIndex(string,regex){
  71. var index=-1;
  72. //we're going backwards from the end and searching for the first occurrence we find
  73. for(var i=string.length-1;i>0;i++){
  74. //once we find it, we're outta here
  75. if(string.substring(i).search(regex)!=-1){
  76. index=i;
  77. break;
  78. }
  79. }
  80. return index;
  81. }
  82.  
  83. function reEscape(str){
  84. var specials=new RegExp("[.*+?|()\\[\\]{}\\\\]","g"); // .*+?|()[]{}\
  85. return str.replace(specials,"\\$&");
  86. }
  87.  
  88. //parse a mal date into a javascript date thingy
  89. //takses a string, motherfucker, and returns a Date object
  90. function parseMalDate(time){
  91. var now=new Date;
  92. var clock;
  93. //some date case
  94. if(time.match(/\w\w\w \d\d?,/)){
  95. clock=time.match(/(\w\w\w) (\d\d?), (\d\d?):(\d\d) ([AP]M)/);
  96. //adjust for am/pm
  97. clock[3]=parseInt(clock[3]!=12?clock[3]:0)+(clock[5]=='PM'?12:0);
  98. //gotta parse the month because ugh
  99. var monthKey={
  100. 'Jan':0,
  101. 'Feb':1,
  102. 'Mar':2,
  103. 'Apr':3,
  104. 'May':4,
  105. 'Jun':5,
  106. 'Jul':6,
  107. 'Aug':7,
  108. 'Sep':8,
  109. 'Oct':9,
  110. 'Nov':10,
  111. 'Dec':11
  112. };
  113. clock[1]=monthKey[clock[1]];
  114. //date object is (year (4 digits), month (0-11), date (1-31), hour (0-23), minute (0-59), second (0-59))
  115. return new Date(now.getFullYear(),clock[1],parseInt(clock[2],10),clock[3],parseInt(clock[4]),0);
  116. }
  117. //some time yesterday case
  118. else if(time.match(/Yesterday,/)){
  119. clock=time.match(/Yesterday, (\d\d?):(\d\d) ([AP]M)/);
  120. clock[1]=parseInt(clock[1]!=12?clock[1]:0)+(clock[3]=='PM'?12:0);
  121. return new Date(now.getFullYear(),now.getMonth(),now.getDate()-1,clock[1],parseInt(clock[2]),0);
  122. }
  123. //some time today case
  124. else if(time.match(/Today,/)){
  125. clock=time.match(/Today, (\d\d?):(\d\d) ([AP]M)/);
  126. clock[1]=parseInt(clock[1]!=12?clock[1]:0)+(clock[3]=='PM'?12:0);
  127. return new Date(now.getFullYear(),now.getMonth(),now.getDate(),clock[1],parseInt(clock[2]),0);
  128. }
  129. //hour(s) ago case
  130. else if(time.match(/hours? ago/)){
  131. return new Date(now.getTime()-parseInt(time.match(/\d\d?/))*3600000);
  132. }
  133. //minute(s) ago case
  134. else if(time.match(/minutes? ago/)){
  135. return new Date(now.getTime()-parseInt(time.match(/\d\d?/))*60000);
  136. }
  137. //second(s) ago case
  138. else if(time.match(/seconds? ago/)){
  139. return new Date(now.getTime()-parseInt(time.match(/\d\d?/))*1000);
  140. }
  141. return now;
  142. }
  143.  
  144. //figure out how long ago something was, with expanding time scales
  145. //takes a Date object!!, and tells you how long ago it was in the form ' (<time(s)> ago)''
  146. //now returns an object with the numeral ago and the unit as attributes
  147. function differenceEngine(then){
  148. var diff={
  149. diff:new Date().getTime()-then.getTime()
  150. };
  151. //days difference
  152. if(diff.diff>86400000){
  153. diff.numeral=(diff.diff-diff.diff%86400000)/86400000;
  154. diff.unit='day'+(diff.numeral>1?'s':'');
  155. }
  156. //hours difference
  157. else if(diff.diff>3600000){
  158. diff.numeral=(diff.diff-diff.diff%3600000)/3600000;
  159. diff.unit='hour'+(diff.numeral>1?'s':'');
  160. }
  161. //minutes difference
  162. else if(diff.diff>60000){
  163. diff.numeral=(diff.diff-diff.diff%60000)/60000;
  164. diff.unit='minute'+(diff.numeral>1?'s':'');
  165. }
  166. //seconds difference
  167. else if(diff.diff>1000){
  168. diff.numeral=(diff.diff-diff.diff%1000)/1000;
  169. diff.unit='second'+(diff.numeral>1?'s':'');
  170. }
  171. //guess it could have been less than a second ago but i can't imagine why
  172. else{
  173. diff.numeral=1;
  174. diff.unit='second';
  175. }
  176. return diff;
  177. }
  178.  
  179. //when you get the history response, insert that info into the quickpost box
  180. function malShit(r){
  181. var doc=$(r.doc);
  182. //do shit
  183. //make sure there's a history
  184. if(doc.find('#horiznav_nav').next().text()=='No history found'){
  185. return null;
  186. }
  187. //grab the rows and shit
  188. var episodes=doc.find('#horiznav_nav + div tr');
  189. //find most recent anime update
  190. var anime={
  191. raw:episodes.has('td:contains(" ep. ")').first()
  192. };
  193. //parse anime update
  194. if(anime.raw.length){
  195. anime.name=anime.raw.find('td:first-child>a').text();
  196. anime.link=anime.raw.find('td:first-child>a').attr('href');
  197. anime.episode=anime.raw.find('td:first-child>strong').text();
  198. anime.date=parseMalDate(anime.raw.find('td:last-child').text());
  199. anime.elapsed=differenceEngine(anime.date);
  200. anime.type='anime';
  201. }
  202. //find most recent manga update
  203. var manga={
  204. raw:episodes.has('td:contains(" chap. ")').first()
  205. };
  206. //parse manga update
  207. if(manga.raw.length){
  208. manga.name=manga.raw.find('td:first-child>a').text();
  209. manga.link=manga.raw.find('td:first-child>a').attr('href');
  210. manga.episode=manga.raw.find('td:first-child>strong').text();
  211. manga.date=parseMalDate(manga.raw.find('td:last-child').text());
  212. manga.elapsed=differenceEngine(manga.date);
  213. manga.type='manga';
  214. }
  215. //get today's ep count
  216. //make sure that it's actually today'
  217. var count={
  218. raw:episodes.find('div.normal_header:contains("Today") small'),
  219. };
  220. count.number=count.raw[0]?count.raw.text().slice(1,-1):"0";
  221. //which update is more recent and also really there
  222. //ugh this is ungainly
  223. var upd8=null;
  224. if(anime.raw[0]&&manga.raw[0]){
  225. if(anime.elapsed.diff<manga.elapsed.diff){
  226. upd8=anime;
  227. }
  228. else{
  229. upd8=manga;
  230. }
  231. }
  232. else if(anime.raw[0]){
  233. upd8=anime;
  234. }
  235. else if(manga.raw[0]){
  236. upd8=manga;
  237. }
  238. //now that we've extracted our data, check to see if it's different from what we've got
  239. if(upd8&&(cachedData.name!=upd8.name||cachedData.episode!=upd8.episode||count.number!=cachedData.count)){
  240. //if the episode changed
  241. if(upd8&&(cachedData.name!=upd8.name||cachedData.episode!=upd8.episode)){
  242. //cache our shit
  243. cachedData=upd8;
  244. }
  245. //i suck cocks
  246. cachedData.count=count.number;
  247. //make a display string i guess
  248. var display=cachedData.name+(cachedData.type=='anime'?' ep. ':' chap. ')+cachedData.episode+' ('+cachedData.elapsed.numeral+' '+cachedData.elapsed.unit+' ago)';
  249. //remember the quickpost box position
  250. var scrollPosition=quickpost.prop('scrollTop');
  251. var cursorStart=quickpost.prop('selectionStart');
  252. var cursorEnd=quickpost.prop('selectionEnd');
  253. //insert the shit
  254. //gotta do our stored shit because fucking cocks
  255. quickpost.val(quickpost.val().replace(rxCocks,'$1'+(quickpost.remember.replace(rxEpisode,'$1'+display+'$2').replace(rxCount,'$1'+cachedData.count+'$2'))));
  256. //restore the quickpost box position
  257. quickpost.prop('scrollTop',scrollPosition);
  258. quickpost.prop('selectionStart',cursorStart);
  259. quickpost.prop('selectionEnd',cursorEnd);
  260. }
  261. //save the data
  262. var gmCache={
  263. name:cachedData.name,
  264. link:cachedData.link,
  265. episode:cachedData.episode,
  266. date:cachedData.date.valueOf(),
  267. count:cachedData.count,
  268. type:cachedData.type
  269. }
  270. GM_setValue('cached',JSON.stringify(gmCache));
  271. return null;
  272. }
  273.  
  274. //when the quickpost box opens, send off a request to mal for the history
  275. function onFocus(e){
  276. //don't do this too often pls
  277. quickpost.off('focus.mal');
  278. //why the fuck doesn't this always work
  279. //maybe it has to do with gm hating event calls but not timeouts, which is whack
  280. if(!GM_getValue('test',true)){
  281. window.setTimeout(onFocus,500);
  282. return null;
  283. }
  284. //get a new elapsed time since now is different
  285. cachedData.elapsed=differenceEngine(new Date(cachedData.date));
  286. //make a display string i guess
  287. var display=cachedData.name!=''?
  288. cachedData.name+(cachedData.type=='anime'?' ep. ':' chap. ')+cachedData.episode+' ('+cachedData.elapsed.numeral+' '+cachedData.elapsed.unit+' ago)':
  289. HISTORY_URL
  290. ;
  291. //save the position of the cursor and scrollbar
  292. var scrollPosition=quickpost.prop('scrollTop');
  293. var cursorStart=quickpost.prop('selectionStart');
  294. var cursorEnd=quickpost.prop('selectionEnd');
  295. //remember quickpost value because cocks
  296. quickpost.remember=quickpost.val().match(rxCocks)[2];
  297. //insert our data
  298. quickpost.val(quickpost.val().replace(rxEpisode,'$1'+display+'$2').replace(rxCount,'$1'+cachedData.count+'$2'));
  299. //restore the position of the cursor and scrollbar
  300. quickpost.prop('scrollTop',scrollPosition);
  301. quickpost.prop('selectionStart',cursorStart);
  302. quickpost.prop('selectionEnd',cursorEnd);
  303. //send off our get request to mal
  304. XHR.get(HISTORY_URL,malShit);
  305. return null;
  306. }
  307.  
  308. //find the quickpost box and listen for it to open
  309. //this gets the quickpost box and also the message box on postmsg.php
  310. var quickpost=$('.quickpost textarea[name="message"], textarea#message');
  311. //null data
  312. var cachedData=JSON.parse(GM_getValue('cached','{"name":"","episode":"","date":0,"count":"0"}'));
  313. //make a regexps
  314. var rxEpisode=new RegExp('(---[\\w\\W]+)'+reEscape(EPISODE_REPLACER)+'([\\w\\W]*)');
  315. var rxCount=new RegExp('(---[\\w\\W]+)'+reEscape(COUNT_REPLACER)+'([\\w\\W]*)');
  316. var rxCocks=new RegExp('([\\w\\W]*)(---[\\w\\W]+)');
  317. //listen for shit
  318. quickpost.on('focus.mal',onFocus);
  319. //reinitialize after posting
  320. $('form.quickpost input[name="post"]').on('click.mal',function(){
  321. quickpost.on('focus.mal',onFocus);
  322. });