Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Endchan 8ball and more
- // @namespace https://endchan.org/
- // @version 1.4
- // @description Adds #8ball, #dX and #flip functionality to endchan
- // @author Anonymous (You)
- // @match http://endchan.org/*/res/*
- // @match https://endchan.org/*/res/*
- // @match http://magrathea.endchan.net/*/thread/*
- // @match https://magrathea.endchan.net/*/thread/*
- // @grant none
- // @downloadURL https://pastebin.com/raw/LGpcq1wJ
- // @updateURL https://pastebin.com/raw/LGpcq1wJ
- // ==/UserScript==
- /*
- Finds, evaluates and replaces occurrences of # commands in posts.
- Supports:
- - #8ball
- - #dX and #XdY (dice)
- - #flip (coin)
- - #slap, #bonk
- - #blur
- 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.
- 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.
- 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.
- Quick styling adjustments can be done with the consts below, for more in depth customization use custom CSS.
- The evaluated results are placed in a span that has a class attribute, use the .megu8Ball {} .meguDice {} .meguCoin {} .meguBlur {} selectors.
- Additionally crit fail/success on dice have .meguDiceFail {} and .meguDiceSuccess {} selectors which only apply on single dice rolls.
- #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.
- 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.
- Hovering over a blurred post will reveal it for the duration.
- */
- /*
- 1.4 - added #blur
- 1.3 - added #slap #bonk
- 1.2 - support for magrathea
- 1.1 - support for #XdY type dice & #flip
- */
- const defaultColor = "#E0FFFF";
- const critFailColor = "#A0522D";
- const critSuccessColor = "#B22222";
- const fontWeight = "700";
- const blurStrength = 4;
- const maxNumDice = 20;
- function addStyle(css) {
- const style = document.getElementById("EC_8ball_addedStyle") || (function() {
- const style = document.createElement('style');
- style.type = 'text/css';
- style.id = "EC_8ball_addedStyle";
- document.head.appendChild(style);
- return style;
- })();
- style.innerHTML = style.innerHTML + css + "\n";
- }
- (function() {
- 'use strict';
- // add css for flashing text
- addStyle(".flashRed{animation:0.3s 10 ease-in-out alternate flashRed;color:tomato;}");
- addStyle("@keyframes flashRed{from{color:tomato}to{color:white}}");
- addStyle(".flashOrange{animation:0.3s 10 ease-in-out alternate flashOrange;color:orange;}");
- addStyle("@keyframes flashOrange{from{color:orange}to{color:white}}");
- addStyle(".meguBlurrable{filter:blur(" + blurStrength + "px);}");
- addStyle(".meguBlurrable:hover{filter:none;}");
- // parse all pre-existing posts when page loads
- processPosts(document.body);
- // callback function to execute when mutations are observed
- let callback = function(mutationsList) {
- for(let mutation of mutationsList) {
- for (let node of mutation.addedNodes) {
- // make sure we can get elements by class name on node
- if (typeof node.getElementsByClassName === "function") {
- processPosts(node);
- }
- }
- }
- };
- // observe page for changes to sauce new posts (all articles/posts should be inside the a tag with the id "divThreads")
- var observer = new MutationObserver(callback);
- let observedNode = document.getElementById("divThreads");
- if (!observedNode) {
- observedNode = document.getElementById("threadsContainer"); // magrathea
- }
- observer.observe(observedNode, { childList: true, subtree:true });
- })();
- // process all posts in subtree of given node
- function processPosts(node) {
- let posts = node.getElementsByClassName("innerPost");
- let markedPosts = node.getElementsByClassName("markedPost");
- if (!posts || posts.length == 0) {
- posts = node.getElementsByClassName("post-container"); // magrathea
- }
- for (let post of posts) {
- processPost(post);
- }
- for (let markedPost of markedPosts) {
- processPost(markedPost);
- }
- }
- // process a single post
- function processPost(post) {
- let linkQuote = post.getElementsByClassName("linkQuote");
- let id = linkQuote && linkQuote.length > 0 ? linkQuote[0].innerHTML : "no-value";
- if (id == "no-value") { // magrathea
- let postInfo = post.getElementsByClassName("post-info");
- if (postInfo && postInfo.length > 0) {
- let match = /No. (\d+)/g.exec(postInfo[0].innerHTML);
- if (match && match.length > 1) {
- id = match[1];
- }
- }
- }
- let messageDiv = post.getElementsByClassName("divMessage");
- if (!messageDiv || messageDiv.length == 0) {
- messageDiv = post.getElementsByClassName("post-message"); // magrathea
- }
- let message = messageDiv && messageDiv.length > 0 ? messageDiv[0].innerHTML : null;
- if (!message) {
- return;
- }
- // at this point we know there's a message present in the messageDiv & id is set to some value -> find and replace any commands
- // 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
- // reseting the index between commands makes them independat from each other (adding commands won't interfere with existing results)
- // reset to random so that commands don't all seed with the same value and produce predictably similar results
- let nextIdx = randomEngine(id, 0);
- let idx = nextIdx();
- message = message.replace(/#8ball/g, function(_) { return eightBall(id, idx++); });
- idx = nextIdx();
- message = message.replace(/#(\d+)?d(\d+)/g, function(match, group1, group2) { return dice(id, idx++, match, group1, group2); });
- idx = nextIdx();
- message = message.replace(/#flip/g, function(_) { return coin(id, idx++); });
- message = message.replace(/#slap/ig, '<span class="flashRed">#SLAP</span>');
- message = message.replace(/#bonk/ig, '<span class="flashOrange">#bonk</span>');
- message = message.replace(/#blur/g, function(_, idx) { return blur(message, idx); });
- messageDiv[0].innerHTML = message;
- }
- // blur command needs a reference to the message and index of where the #blur occurred in the msg
- function blur(message, idx) {
- let regexMatch = Array.from(message.slice(0, idx).matchAll(/>>>\/?(\d+)\/?<\/a>/g), m => m[1]); // [1] is the matched group
- if (regexMatch && regexMatch.length > 0) {
- let postNumToBlur = regexMatch[regexMatch.length - 1];
- if (postNumToBlur) {
- blurPost(postNumToBlur);
- }
- }
- return '<span class="meguBlur" style="font-weight: ' + fontWeight + '; color: ' + defaultColor + ';">#blur</span>';
- }
- // find the post with the specified number in the page and apply the blurrable class to it
- function blurPost(num) {
- let post = undefined;
- let linkQuotes = Array.from(document.getElementsByClassName("linkQuote"));
- if (linkQuotes && linkQuotes.length > 0) {
- let linkQuote = linkQuotes.find(element => element.innerHTML.includes(num));
- if (linkQuote) {
- post = linkQuote.parentElement;
- }
- } else {
- console.log(num)
- // magrathea
- let postInfos = document.getElementsByClassName("post-info");
- let link = undefined;
- for (postInfo of postInfos) {
- let links = postInfo.getElementsByTagName("a");
- if (links && links.length > 0 && links[0].innerHTML.includes(num)) {
- link = links[0];
- }
- if (link) {
- console.log(link)
- post = link.parentElement.parentElement;
- break;
- }
- }
- }
- if (post) {
- post.classList.add("meguBlurrable");
- }
- }
- // 8ball just needs the seed values for random, match is always #8ball
- function eightBall(id, idx) {
- let outcome = Math.floor(randomEngine(id, idx)() * 7);
- let replacement = "";
- switch(outcome) {
- case 0:
- replacement = "#8ball (Yes)";
- break;
- case 1:
- replacement = "#8ball (No)";
- break;
- case 2:
- replacement = "#8ball (Maybe)";
- break;
- case 3:
- // pastebin doesn't like using swear words :(
- replacement = "#8ball (Hell Yeah, " + eval('"' + '\\u004d\\u006f\\u0074\\u0068\\u0065\\u0072\\u0066\\u0075\\u0063\\u006b\\u0065\\u0072' + '"') + "!)";
- break;
- case 4:
- replacement = "#8ball (Outlook not so good)";
- break;
- case 5:
- replacement = "#8ball (Signs point to yes)";
- break;
- case 6:
- replacement = "#8ball (It can't be helped)";
- break;
- default: // this shouldn't happen
- replacement = "#8ball (There is no spoon)";
- break;
- }
- replacement = '<span class="megu8Ball" style="font-weight: ' + fontWeight + '; color: ' + defaultColor + ';">' + replacement + '</span>';
- return replacement;
- }
- // needs seed values for random & the match to determine the number of sides on the dice
- function dice(id, idx, match, numDice, sizeDice) {
- let n = numDice;
- if (!n || n < 1) {
- n = 1;
- } else if (n > maxNumDice) {
- return match;
- }
- let sides = sizeDice;
- if (!sides || sides < 1) {
- return match;
- }
- let nextRandom = randomEngine(id, idx);
- let outcomes = [];
- let outcomeSum = 0;
- for (let i = 0; i < n; i++) {
- // +1 as 0 is not a dice side
- let value = Math.floor(nextRandom() * sides) + 1;
- outcomes.push(value);
- outcomeSum += value;
- }
- let outcome = outcomeSum;
- if (outcomes.length > 1) {
- outcome = outcomes.join(" + ") + " = " + outcomeSum;
- }
- let replacement = match + " (" + outcome + ")";
- let color = defaultColor;
- let additionalClasses = "";
- if (outcomes.length == 1 && outcome == 1) {
- color = critFailColor;
- additionalClasses = " meguDiceFail";
- } else if (outcomes.length == 1 && outcome == sides) {
- color = critSuccessColor;
- additionalClasses = " meguDiceSuccess";
- }
- replacement = '<span class="meguDice' + additionalClasses + '" style="font-weight: ' + fontWeight + '; color: ' + color + ';">' + replacement + '</span>';
- return replacement;
- }
- // coin just needs the seed values for random, match is always #flip
- function coin(id, idx) {
- let outcome = Math.floor(randomEngine(id, idx)() * 2);
- let replacement = "";
- switch(outcome) {
- case 0:
- replacement = "#flip (Heads)";
- break;
- case 1:
- replacement = "#flip (Tails)";
- break;
- default: // this shouldn't happen
- replacement = "#flip (Edge)";
- break;
- }
- replacement = '<span class="meguCoin" style="font-weight: ' + fontWeight + '; color: ' + defaultColor + ';">' + replacement + '</span>';
- return replacement;
- }
- // don't touch this
- // this is the random generator, if it changes for some people and not others then results will desync
- function randomEngine(param1, param2) {
- // hash up seed params
- let seedValue = [param1, param2].join("::");
- let seedHash = 0;
- if (seedValue.length === 0) return seedHash;
- for (let i = 0; i < seedValue.length; i++) {
- let char = seedValue.charCodeAt(i);
- seedHash = ((seedHash << 5) - seedHash) + char;
- seedHash = seedHash & seedHash;
- }
- // ensure hash is not negative
- seedHash = seedHash & 0xFFFFFFFF;
- seedHash = seedHash >>> 0;
- // init other values used in pseudo-rand calc
- var m = Math.pow(2, 32);
- var a = 1103515245;
- var c = 12345;
- // return function that produces a random number between 0 and 1 when called
- return function() {
- seedHash = (a * seedHash + c) % m;
- return seedHash / m;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement