Advertisement
anonymous_you

Endchan 8ball and more

Dec 30th, 2022 (edited)
955
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Endchan 8ball and more
  3. // @namespace    https://endchan.org/
  4. // @version      1.4
  5. // @description  Adds #8ball, #dX and #flip functionality to endchan
  6. // @author       Anonymous (You)
  7. // @match        http://endchan.org/*/res/*
  8. // @match        https://endchan.org/*/res/*
  9. // @match        http://magrathea.endchan.net/*/thread/*
  10. // @match        https://magrathea.endchan.net/*/thread/*
  11. // @grant        none
  12. // @downloadURL  https://pastebin.com/raw/LGpcq1wJ
  13. // @updateURL    https://pastebin.com/raw/LGpcq1wJ
  14. // ==/UserScript==
  15.  
  16. /*
  17. Finds, evaluates and replaces occurrences of # commands in posts.
  18. Supports:
  19.     - #8ball
  20.     - #dX and #XdY (dice)
  21.     - #flip (coin)
  22.     - #slap, #bonk
  23.     - #blur
  24. As long as the random generation logic remains intact, the same results should persist everywhere, even with page refreshes and without having to store anything anywhere.
  25.  
  26. When doing #XdY the maximum X (number of dice) it'll accept is 20 by default. Any more and the command won't evaluate to prevent stupidity like #1000d20 and getting spammed with output.
  27. The default can be adujsted with the maxNumDice const - this should have no effect on the random generation, those with a value set lower simply won't see the results that are out of bounds.
  28.  
  29. Quick styling adjustments can be done with the consts below, for more in depth customization use custom CSS.
  30. The evaluated results are placed in a span that has a class attribute, use the .megu8Ball {} .meguDice {} .meguCoin {} .meguBlur {} selectors.
  31. Additionally crit fail/success on dice have .meguDiceFail {} and .meguDiceSuccess {} selectors which only apply on single dice rolls.
  32.  
  33. #blur will apply a blur filter to the last quoted post before the command. It intentionally only applies to one post so that it's sligtly more difficult to blur vast portions of the thread.
  34. As with all other commands the blur will only apply for people running the script, but it will blur for everyone with the script not just yourself. The #blur post probably also needs to not be filtered to apply.
  35. Hovering over a blurred post will reveal it for the duration.
  36. */
  37.  
  38. /*
  39. 1.4 - added #blur
  40. 1.3 - added #slap #bonk
  41. 1.2 - support for magrathea
  42. 1.1 - support for #XdY type dice & #flip
  43. */
  44.  
  45. const defaultColor = "#E0FFFF";
  46. const critFailColor = "#A0522D";
  47. const critSuccessColor = "#B22222";
  48. const fontWeight = "700";
  49.  
  50. const blurStrength = 4;
  51.  
  52. const maxNumDice = 20;
  53.  
  54. function addStyle(css) {
  55.   const style = document.getElementById("EC_8ball_addedStyle") || (function() {
  56.     const style = document.createElement('style');
  57.     style.type = 'text/css';
  58.     style.id = "EC_8ball_addedStyle";
  59.     document.head.appendChild(style);
  60.     return style;
  61.   })();
  62.   style.innerHTML = style.innerHTML + css + "\n";
  63. }
  64.  
  65. (function() {
  66.     'use strict';
  67.  
  68.     // add css for flashing text
  69.     addStyle(".flashRed{animation:0.3s 10 ease-in-out alternate flashRed;color:tomato;}");
  70.     addStyle("@keyframes flashRed{from{color:tomato}to{color:white}}");
  71.     addStyle(".flashOrange{animation:0.3s 10 ease-in-out alternate flashOrange;color:orange;}");
  72.     addStyle("@keyframes flashOrange{from{color:orange}to{color:white}}");
  73.     addStyle(".meguBlurrable{filter:blur(" + blurStrength + "px);}");
  74.     addStyle(".meguBlurrable:hover{filter:none;}");
  75.  
  76.     // parse all pre-existing posts when page loads
  77.     processPosts(document.body);
  78.  
  79.     // callback function to execute when mutations are observed
  80.     let callback = function(mutationsList) {
  81.         for(let mutation of mutationsList) {
  82.             for (let node of mutation.addedNodes) {
  83.                 // make sure we can get elements by class name on node
  84.                 if (typeof node.getElementsByClassName === "function") {
  85.                     processPosts(node);
  86.                 }
  87.             }
  88.         }
  89.     };
  90.  
  91.     // observe page for changes to sauce new posts (all articles/posts should be inside the a tag with the id "divThreads")
  92.     var observer = new MutationObserver(callback);
  93.     let observedNode = document.getElementById("divThreads");
  94.     if (!observedNode) {
  95.         observedNode = document.getElementById("threadsContainer"); // magrathea
  96.     }
  97.     observer.observe(observedNode, { childList: true, subtree:true });
  98. })();
  99.  
  100. // process all posts in subtree of given node
  101. function processPosts(node) {
  102.     let posts = node.getElementsByClassName("innerPost");
  103.     let markedPosts = node.getElementsByClassName("markedPost");
  104.     if (!posts || posts.length == 0) {
  105.         posts = node.getElementsByClassName("post-container"); // magrathea
  106.     }
  107.     for (let post of posts) {
  108.         processPost(post);
  109.     }
  110.     for (let markedPost of markedPosts) {
  111.         processPost(markedPost);
  112.     }
  113. }
  114.  
  115. // process a single post
  116. function processPost(post) {
  117.     let linkQuote = post.getElementsByClassName("linkQuote");
  118.     let id = linkQuote && linkQuote.length > 0 ? linkQuote[0].innerHTML : "no-value";
  119.     if (id == "no-value") { // magrathea
  120.         let postInfo = post.getElementsByClassName("post-info");
  121.         if (postInfo && postInfo.length > 0) {
  122.             let match = /No. (\d+)/g.exec(postInfo[0].innerHTML);
  123.             if (match && match.length > 1) {
  124.                 id = match[1];
  125.             }
  126.         }
  127.     }
  128.     let messageDiv = post.getElementsByClassName("divMessage");
  129.     if (!messageDiv || messageDiv.length == 0) {
  130.         messageDiv = post.getElementsByClassName("post-message"); // magrathea
  131.     }
  132.     let message = messageDiv && messageDiv.length > 0 ? messageDiv[0].innerHTML : null;
  133.     if (!message) {
  134.         return;
  135.     }
  136.     // at this point we know there's a message present in the messageDiv & id is set to some value -> find and replace any commands
  137.  
  138.     // the match function can get an index param (idx where in the string the match occurred) but it's safer to keep track of the idx separately - not sure if innerHtml is the same for everyone
  139.     // reseting the index between commands makes them independat from each other (adding commands won't interfere with existing results)
  140.     // reset to random so that commands don't all seed with the same value and produce predictably similar results
  141.     let nextIdx = randomEngine(id, 0);
  142.     let idx = nextIdx();
  143.     message = message.replace(/#8ball/g, function(_) { return eightBall(id, idx++); });
  144.     idx = nextIdx();
  145.     message = message.replace(/#(\d+)?d(\d+)/g, function(match, group1, group2) { return dice(id, idx++, match, group1, group2); });
  146.     idx = nextIdx();
  147.     message = message.replace(/#flip/g, function(_) { return coin(id, idx++); });
  148.     message = message.replace(/#slap/ig, '<span class="flashRed">#SLAP</span>');
  149.     message = message.replace(/#bonk/ig, '<span class="flashOrange">#bonk</span>');
  150.     message = message.replace(/#blur/g, function(_, idx) { return blur(message, idx); });
  151.     messageDiv[0].innerHTML = message;
  152. }
  153.  
  154. // blur command needs a reference to the message and index of where the #blur occurred in the msg
  155. function blur(message, idx) {
  156.     let regexMatch = Array.from(message.slice(0, idx).matchAll(/>&gt;&gt;\/?(\d+)\/?<\/a>/g), m => m[1]); // [1] is the matched group
  157.     if (regexMatch && regexMatch.length > 0) {
  158.         let postNumToBlur = regexMatch[regexMatch.length - 1];
  159.         if (postNumToBlur) {
  160.             blurPost(postNumToBlur);
  161.         }
  162.     }
  163.     return '<span class="meguBlur" style="font-weight: ' + fontWeight + '; color: ' + defaultColor + ';">#blur</span>';
  164. }
  165.  
  166. // find the post with the specified number in the page and apply the blurrable class to it
  167. function blurPost(num) {
  168.     let post = undefined;
  169.     let linkQuotes = Array.from(document.getElementsByClassName("linkQuote"));
  170.     if (linkQuotes && linkQuotes.length > 0) {
  171.         let linkQuote = linkQuotes.find(element => element.innerHTML.includes(num));
  172.         if (linkQuote) {
  173.             post = linkQuote.parentElement;
  174.         }
  175.     } else {
  176.         console.log(num)
  177.         // magrathea
  178.         let postInfos = document.getElementsByClassName("post-info");
  179.         let link = undefined;
  180.         for (postInfo of postInfos) {
  181.             let links = postInfo.getElementsByTagName("a");
  182.             if (links && links.length > 0 && links[0].innerHTML.includes(num)) {
  183.                 link = links[0];
  184.             }
  185.             if (link) {
  186.                 console.log(link)
  187.                 post = link.parentElement.parentElement;
  188.                 break;
  189.             }
  190.         }
  191.     }
  192.     if (post) {
  193.         post.classList.add("meguBlurrable");
  194.     }
  195. }
  196.  
  197. // 8ball just needs the seed values for random, match is always #8ball
  198. function eightBall(id, idx) {
  199.     let outcome = Math.floor(randomEngine(id, idx)() * 7);
  200.     let replacement = "";
  201.     switch(outcome) {
  202.         case 0:
  203.             replacement = "#8ball (Yes)";
  204.             break;
  205.         case 1:
  206.             replacement = "#8ball (No)";
  207.             break;
  208.         case 2:
  209.             replacement = "#8ball (Maybe)";
  210.             break;
  211.         case 3:
  212.             // pastebin doesn't like using swear words :(
  213.             replacement = "#8ball (Hell Yeah, " + eval('"' + '\\u004d\\u006f\\u0074\\u0068\\u0065\\u0072\\u0066\\u0075\\u0063\\u006b\\u0065\\u0072' + '"') + "!)";
  214.             break;
  215.         case 4:
  216.             replacement = "#8ball (Outlook not so good)";
  217.             break;
  218.         case 5:
  219.             replacement = "#8ball (Signs point to yes)";
  220.             break;
  221.         case 6:
  222.             replacement = "#8ball (It can't be helped)";
  223.             break;
  224.         default: // this shouldn't happen
  225.             replacement = "#8ball (There is no spoon)";
  226.             break;
  227.     }
  228.  
  229.     replacement = '<span class="megu8Ball" style="font-weight: ' + fontWeight + '; color: ' + defaultColor + ';">' + replacement + '</span>';
  230.     return replacement;
  231. }
  232.  
  233. // needs seed values for random & the match to determine the number of sides on the dice
  234. function dice(id, idx, match, numDice, sizeDice) {
  235.     let n = numDice;
  236.     if (!n || n < 1) {
  237.         n = 1;
  238.     } else if (n > maxNumDice) {
  239.         return match;
  240.     }
  241.     let sides = sizeDice;
  242.     if (!sides || sides < 1) {
  243.         return match;
  244.     }
  245.  
  246.     let nextRandom = randomEngine(id, idx);
  247.     let outcomes = [];
  248.     let outcomeSum = 0;
  249.     for (let i = 0; i < n; i++) {
  250.         // +1 as 0 is not a dice side
  251.         let value = Math.floor(nextRandom() * sides) + 1;
  252.         outcomes.push(value);
  253.         outcomeSum += value;
  254.     }
  255.     let outcome = outcomeSum;
  256.     if (outcomes.length > 1) {
  257.         outcome = outcomes.join(" + ") + " = " + outcomeSum;
  258.     }
  259.  
  260.     let replacement = match + " (" + outcome + ")";
  261.     let color = defaultColor;
  262.     let additionalClasses = "";
  263.     if (outcomes.length == 1 && outcome == 1) {
  264.         color = critFailColor;
  265.         additionalClasses = " meguDiceFail";
  266.     } else if (outcomes.length == 1 && outcome == sides) {
  267.         color = critSuccessColor;
  268.         additionalClasses = " meguDiceSuccess";
  269.     }
  270.     replacement = '<span class="meguDice' + additionalClasses + '" style="font-weight: ' + fontWeight + '; color: ' + color + ';">' + replacement + '</span>';
  271.     return replacement;
  272. }
  273.  
  274. // coin just needs the seed values for random, match is always #flip
  275. function coin(id, idx) {
  276. let outcome = Math.floor(randomEngine(id, idx)() * 2);
  277.     let replacement = "";
  278.     switch(outcome) {
  279.         case 0:
  280.             replacement = "#flip (Heads)";
  281.             break;
  282.         case 1:
  283.             replacement = "#flip (Tails)";
  284.             break;
  285.         default: // this shouldn't happen
  286.             replacement = "#flip (Edge)";
  287.             break;
  288.     }
  289.  
  290.     replacement = '<span class="meguCoin" style="font-weight: ' + fontWeight + '; color: ' + defaultColor + ';">' + replacement + '</span>';
  291.     return replacement;
  292. }
  293.  
  294.  
  295.  
  296. // don't touch this
  297. // this is the random generator, if it changes for some people and not others then results will desync
  298. function randomEngine(param1, param2) {
  299.     // hash up seed params
  300.     let seedValue = [param1, param2].join("::");
  301.     let seedHash = 0;
  302.     if (seedValue.length === 0) return seedHash;
  303.     for (let i = 0; i < seedValue.length; i++) {
  304.         let char = seedValue.charCodeAt(i);
  305.         seedHash = ((seedHash << 5) - seedHash) + char;
  306.         seedHash = seedHash & seedHash;
  307.     }
  308.     // ensure hash is not negative
  309.     seedHash = seedHash & 0xFFFFFFFF;
  310.     seedHash = seedHash >>> 0;
  311.  
  312.     // init other values used in pseudo-rand calc
  313.     var m = Math.pow(2, 32);
  314.     var a = 1103515245;
  315.     var c = 12345;
  316.  
  317.     // return function that produces a random number between 0 and 1 when called
  318.     return function() {
  319.         seedHash = (a * seedHash + c) % m;
  320.         return seedHash / m;
  321.     }
  322. }
  323.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement