bitcores

/b/ackwash reloaded - foolz edit v1.03

Oct 31st, 2011
136
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name           /b/ackwash reloaded
  3. // @namespace      http://userscripts.org/users/64431
  4. // @description    /b/ackwash + backlinks + inline quotes
  5. // @include        http://boards.4chan.org/*
  6. // @include        http://dis.4chan.org/*
  7. // @include        http://4chanarchive.org/brchive/*
  8. // @include        http://suptg.thisisnotatrueending.com/archive/*
  9. // @include        http://archive.easymodo.net/*
  10. // @include        http://archive.foolz.us/*
  11. // @include        http://archive.no-ip.org/*
  12. // @include        http://green-oval.net/cgi-board.pl/*
  13. // @copyright      2009, 2010, James Campos
  14. // @license        GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
  15. // @version        5.5.7
  16. // @compatibility  Firefox 3.5, Opera 10, Chrome 4, Uzbl git
  17. // ==/UserScript==
  18.  
  19. //start preferences
  20. const inline = 2;//inline quotes - 0 = never, 1 = front page, 2 = always
  21. const backlinkify = true;//backlinks
  22. const backtext = ' >>%';//% = id of quoter
  23. //end preferences
  24.  
  25. //todo - smarter xhr, backlinking on textboards, backlink highlighting
  26.  
  27. function inBefore(root, node) {
  28.   root.parentNode.insertBefore(node, root);
  29. }
  30.  
  31. function inAfter(root, node) {
  32.   root.parentNode.insertBefore(node, root.nextSibling);
  33. }
  34.  
  35. function remove(node) {
  36.   node.parentNode.removeChild(node);
  37. }
  38.  
  39. function tag(str) {
  40.   return document.createElement(str);
  41. }
  42.  
  43. //xxx - this is 2-3 times slower than native. don't use in loops
  44. function create(css) {//create a structured dom node
  45.   var nodes = css.split(' ');
  46.   var root = c(nodes[0]);
  47.   var current = root;
  48.   for (var i = 1, l = nodes.length; i < l; i++) {
  49.     var child = c(nodes[i]);
  50.     current.appendChild(child);
  51.     current = child;
  52.   }
  53.   return root;
  54.  
  55.   function c(css) {//lol private function. create single nodes
  56.     var matches = css.match(/(\w+)(#\w+)?(\.\w+)?/);//xxx - only allows 0-1 classes
  57.     var tag = matches[1];
  58.     var id = matches[2];
  59.     var className = matches[3];
  60.     var node = document.createElement(tag);
  61.     if (id)
  62.       node.id = id.substring(1);
  63.     if (className)
  64.       node.className = className.substring(1);
  65.     return node;
  66.   }
  67. }
  68.  
  69. function $(selector, root) {
  70.   if (!root) root = document.body;
  71.   return root.querySelector(selector);
  72. }
  73.  
  74. function $$(selector, root) {
  75.   if (!root) root = document.body;
  76.   var result = root.querySelectorAll(selector);
  77.   var a = []
  78.   for (var i = 0, l = result.length; i < l; i++)
  79.     a.push(result[i])
  80.   return a
  81. }
  82.  
  83. function x(xpath, root) {
  84.   if (!root) root = document.body;
  85.   return document.
  86.     evaluate(xpath, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).
  87.     singleNodeValue;
  88. }
  89.  
  90. function X(xpath, root) {
  91.   if (!root) root = document.body;
  92.   var result = document.
  93.     evaluate(xpath, root, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
  94.   var a = [], item;
  95.   while (item = result.iterateNext())
  96.     a.push(item);
  97.   return a;
  98. }
  99.  
  100. //main
  101. var cache = [];
  102. var oldID, bwHeight, winHeight, winWidth;
  103. const OFFSET_Y = 120;
  104. const OFFSET_X = 45;
  105. const FLIPPER = 400;
  106.  
  107. var server = location.hostname.match(/(\w+)?/)[0];
  108. if (location.hostname.match(/\.foolz\.us$/)) //foolz.us
  109.     server = 'foolz';
  110. if (server == 'green')//green-oval
  111.   server = 'archive';
  112.  
  113. const reply = /res|read|thread|archive\//.test(location.pathname);
  114. //imageboard|textboard|4chanarchive+easymodo+foolz|suptgarchive
  115. if (reply)
  116.   var threadOP = location.href.match(/\d{2,}/)[0];
  117. else if (server != 'dis')
  118.   const checkOP = true;
  119. const xpathOP =
  120.     x("ancestor::div[@id]", $('td.reply')) ?//threading
  121.         "ancestor::div[@id]" :
  122.         "ancestor-or-self::td[@id]/ancestor::table[1]/preceding-sibling::*[@id][1]";
  123.  
  124. var bw = create('div#backwash_tooltip table tr td#backwash_tipcell div#backwash_tipcell');
  125. bw.setAttribute('style',
  126.   'display:none;' +
  127.   'position:fixed;' +
  128.   'border:1px solid #AAA;' +
  129.   'background-color: inherit');
  130. var bwtd = $('td', bw);
  131. bwtd.style.padding = '10px';
  132. document.body.appendChild(bw);
  133.  
  134. var getContent;
  135. var insertSelector;//controls where the backlinks appear
  136. var postSelector;
  137. switch(server) {
  138.   case 'archive'://easymodo
  139.     getContent = getEasymodo;
  140.     insertSelector = reply ? 'a.js + a' : 'a.js';
  141.     postSelector = 'td.reply, td.subreply';
  142.     break;
  143.     case 'foolz'://foolz
  144.     getContent = getEasymodo;
  145.     insertSelector = reply ? 'a.js + a' : 'a.js';
  146.     postSelector = 'div.reply, div.subreply';
  147.     break;
  148.   case 'dis'://text boards
  149.     getContent = getTB;
  150.     insertSelector = '';//todo
  151.     postSelector = 'div.post';
  152.     bw.style.backgroundColor = '#F0F0F0';
  153.     break;
  154.   default:
  155.     getContent = getChan;
  156.     insertSelector = 'span[id]';
  157.     postSelector = 'td.reply';
  158.     break;
  159. }
  160. $$(postSelector).forEach(wash);
  161.  
  162. document.body.addEventListener('DOMNodeInserted',
  163.   function (e) {
  164.     var target = e.target;
  165.     if (target.nodeName == 'TABLE')
  166.       wash($('td.reply', target));
  167.   },
  168.   true);
  169.  
  170. //funks
  171. function wash(post) {
  172.   var links = X(".//a[starts-with(text(), '>>')]", post);
  173.   if (!links.length)
  174.     return;
  175.   if (checkOP)
  176.     threadOP = x(xpathOP, post).id.match(/\d+$/)[0];
  177.   var postID = post.id;
  178.   oldID = null;
  179.   links.forEach(function(link) {
  180.     listenTo(link);
  181.     if (!postID)
  182.       return;//inlined quotes, but also textboard posts!
  183.     var matches = link.href.match(/(\d+)?\D+([\d_]+)$/);
  184.     if (!matches)
  185.       return;//todo - check performance of try/catch instead
  186.     var op = matches[1];
  187.     var id = matches[2];
  188.     if (id == threadOP)
  189.       link.innerHTML += ' (OP)';
  190.     else if (reply && (op != threadOP))
  191.       link.innerHTML += ' (Duckroll?)';
  192.  
  193.     //add backlinks
  194.     if (!backlinkify || (oldID == id))
  195.       return;//don't add multiple backlinks when source and target are the same. not a rigorous check
  196.     oldID = id;
  197.     if (server == 'archive' || server == 'foolz')
  198.       id = 'p' + id;
  199.     var target = document.getElementById(id);
  200.     if (target) {
  201.       var backlink = tag('a');
  202.       backlink.className = 'backlink';
  203.       listenTo(backlink);
  204.       backlink.textContent = backtext.replace('%', postID);
  205.       backlink.href = '#' + postID;
  206.       var insertPoint = $(insertSelector, target);
  207.       inAfter(insertPoint, backlink);
  208.     }
  209.   })
  210. }
  211.  
  212. function listenTo(link) {
  213.   link.addEventListener('click', click, true);
  214.   link.addEventListener('mouseover', mouseover, true);
  215.   link.addEventListener('mousemove', mousemove, true);
  216.   link.addEventListener('mouseout', mouseout, true);
  217. }
  218.  
  219. function click(e) {
  220.   if (!inline || (inline == 1 && reply))
  221.     return;
  222.  
  223.   e.preventDefault();
  224.   var next = (server == 'dis') ? this.nextSibling : this.parentNode.nextSibling;
  225.   if (next && next.nodeName == 'TABLE')
  226.     remove(next);
  227.   else if (this.className == 'backlink') {//we know there's a forwardpost
  228.         var id = this.hash.substring(1);
  229.         var target = document.getElementById(id);
  230.         var tp = target.parentNode;
  231.         if (tp.parentNode == this.parentNode) {
  232.             remove(tp);
  233.             target = document.getElementById(id);
  234.             if (server == 'foolz')
  235.                 target.style.display = '';
  236.             else
  237.                 target.parentNode.style.display = '';
  238.         }
  239.         else {
  240.             var clone = target.cloneNode(true)
  241.             clone.style.border = '1px dashed';
  242.             var links = X(".//a[starts-with(text(), '>>')]", clone);
  243.             links.forEach(function(link) { listenTo(link); });
  244.             var tr = tag('tr')
  245.             tr.appendChild(clone)
  246.             if (server == 'foolz'){
  247.                 var insertPoint = x('following-sibling::*[self::tr or self::blockquote or self::br or self::p]', this);
  248.                 target.style.display = 'none'
  249.             }
  250.             else {
  251.                 var insertPoint = x('following-sibling::*[self::tr or self::blockquote or self::br]', this);
  252.                 tp.style.display = 'none'
  253.             }
  254.             inBefore(insertPoint, tr);
  255.         }
  256.     }
  257.  
  258.     else {
  259.     var table = create('table tr td');
  260.     td = $('td', table);
  261.     td.style.border = '1px dashed';
  262.     var insertPoint = (server == 'dis') ? this : this.parentNode;
  263.     inAfter(insertPoint, table);
  264.     getContent(td, this);//hack - we don't want other scripts touching this, so add content after inserting
  265.     wash(td);
  266.   }
  267. }
  268.  
  269. function mouseover(e) {
  270.   getContent(bwtd, this);
  271.   var input = $('input', bwtd);
  272.   if (input)
  273.     remove(input);
  274.   if (this.className == 'backlink') {
  275.     var postHash = '#' + this.parentNode.id;
  276.     var links = X(".//a[starts-with(text(), '>>')]", bwtd);
  277.     links.forEach(function (x) {
  278.       if (x.hash == postHash)
  279.         x.className = 'forwardlink';
  280.     })
  281.   }
  282.   bw.style.display = '';
  283.   bwHeight = bw.offsetHeight;//can i get this and position the div w/o displaying it first?
  284.   winHeight = Math.min(document.documentElement.clientHeight, document.body.clientHeight);//easymodo + dis
  285.   winWidth = Math.min(document.documentElement.clientWidth, document.body.clientWidth);
  286.   mousemove(e);
  287. }
  288.  
  289. function getEasymodo(td, link) {
  290.   var matches = link.href.match(/(post|thread|#).*?([\d_]+)$/);//ghost bros use _ in ids
  291.   var type = matches[1];
  292.   var id = 'p' + matches[2];//fucking easymodo
  293.   switch (type) {
  294.     case '#':
  295.       var target = document.getElementById(id);
  296.       td.innerHTML = target.innerHTML;
  297.       break;
  298.     case 'thread':
  299.       if (reply) {
  300.         var target = document.getElementById(id);
  301.         td.innerHTML = target.innerHTML;
  302.       } else {
  303.         var url = link.href.match(/[^#]+/)[0];
  304.         checkCache(td, id, url);
  305.       } break;
  306.     case 'post':
  307.       if (reply)
  308.         url = link.href;
  309.       else {//assume that hidden post is in same thread so we can cache
  310.         var op = x('ancestor::table/preceding-sibling::*[@id][1]', link).id.match(/\d+$/)[0];
  311.         var url = link.href.replace(/post.+/, 'thread/' + op);
  312.       }
  313.       checkCache(td, id, url);
  314.       break;
  315.   }
  316. }
  317.  
  318. function getTB(td, link) {
  319.   var matches = link.href.match(/(.+\/)(?:\d+[-,])*(\d+)$/);
  320.   var url = matches[1];
  321.   var id = matches[2];
  322.   var target = x('ancestor::div[@class="thread"]//span[@class="postnum"]/a[text()="' + id + '"]/ancestor::*[self::div or self::table][1]', link)
  323.   if (target)
  324.     td.innerHTML = target.innerHTML;
  325.   else
  326.     checkCache(td, id, url);
  327. }
  328.  
  329. function getChan(td, link) {
  330.   var matches = link.href.match(/(.+\/(\d+).*)?#(\d+)$/);
  331.   var url = matches[1];
  332.   var op = matches[2];
  333.   var id = matches[3];
  334.  
  335.   if (op == id)
  336.     matchOP(td, id, document.body.innerHTML, url);
  337.   else {
  338.     var target = document.getElementById(id);
  339.     if (target)
  340.       td.innerHTML = target.innerHTML;
  341.     else
  342.       checkCache(td, id, url);
  343.   }
  344. }
  345.  
  346. function checkCache(td, id, url, isOP) {
  347.   var match = isOP ? matchOP : matchPost;
  348.   for (var i in cache)
  349.     if (url == cache[i].url) {
  350.       var responseText = cache[i].responseText;
  351.       break;
  352.     }
  353.   if (responseText) {//cached
  354.     match(td, id, responseText);
  355.     rewrite(td, url);
  356.   } else//send a request
  357.     xhr(td, id, url, isOP);
  358. }
  359.  
  360. function xhr(td, id, url, isOP) {
  361.   td.innerHTML = 'Loading ' + id + '...';
  362.   var r = new XMLHttpRequest();
  363.   r.onreadystatechange = function() {
  364.     if (this.readyState == 4) {
  365.       if (this.status == 200) {
  366.         var responseText = this.responseText;
  367.         cache.push({ url: url, responseText: responseText });
  368.         if (td.innerHTML != 'Loading ' + id + '...')
  369.           return;//we've moved on already
  370.         var match = isOP ? matchOP : matchPost;
  371.         match(td, id, responseText);
  372.         rewrite(td, url);
  373.         wash(td);//we need to manually wash b/c we're not inserting a table, just modifying one.
  374.       } else {
  375.         if (td.innerHTML != 'Loading ' + id + '...')
  376.           return;//we've moved on already
  377.         td.innerHTML = 'Error ' + this.status + ': ' + this.statusText;
  378.       }
  379.     }
  380.   }
  381.   r.open('get', url, true);
  382.   r.send();
  383. }
  384.  
  385. function rewrite(td, url) {//fixme - this should be in matchPost, but if I send a url to match() and it's an op, we get an infinite loop. awkward.
  386.   if (server == 'dis')
  387.     return;
  388.   var links = X(".//a[starts-with(text(), '>>')]", td);
  389.   links.forEach(function(link) {
  390.     link.href = url + link.hash;
  391.   });
  392. }
  393.  
  394. function matchPost(td, id, responseText) {
  395.   var body = tag('body');
  396.   body.innerHTML = responseText;
  397.   var xpath = (server == 'dis') ?
  398.     ".//span[@class='postnum']/a[text()='" + id + "']/ancestor::div[1]" :
  399.     './/*[@id="' + id + '"]';
  400.   var target = x(xpath, body);
  401.   if (target)
  402.     td.innerHTML = target.innerHTML;
  403.   else
  404.     td.innerHTML = 'Post not found';
  405. }
  406.  
  407. function matchOP(td, id, text, url) {
  408.   var re = new RegExp('<span class="filesize">.+\n<input.+?name="' + id + '[^_][\\s\\S]+?<\/blockquote>', 'i')//[^_] for 4chanarchive
  409.   var target = text.match(re);
  410.   if (target)
  411.     td.innerHTML = target[0];
  412.   else if (url)
  413.     xhr(td, id, url, true);
  414.   else//shouldn't happen
  415.     td.innerHTML = 'Post not found';
  416. }
  417.  
  418. function mousemove(e) {
  419.   var bwTop = e.clientY - OFFSET_Y;
  420.   var bwBot = bwTop + bwHeight;
  421.   bw.style.top =
  422.     winHeight < bwHeight || bwTop < 0 ?
  423.       '0px' :
  424.     bwBot > winHeight ?
  425.       winHeight - bwHeight + 'px' :
  426.       bwTop + 'px';
  427.  
  428.   var x = e.clientX;
  429.   if (x > winWidth - FLIPPER) {
  430.     bw.style.left = ''
  431.     bw.style.right = winWidth + OFFSET_X - x + 'px'
  432.   } else {
  433.     bw.style.right = ''
  434.     bw.style.left = x + OFFSET_X + 'px'
  435.   }
  436. }
  437.  
  438. function mouseout() {
  439.   bw.style.display = 'none';
  440. }
Advertisement
Add Comment
Please, Sign In to add comment