Facebook
From Gruff Sheep, 5 Years ago, written in Plain Text.
Embed
Download Paste or View Raw
Hits: 221
  1. // ==UserScript==
  2. // @author Ross Hill <rosshill.ca>
  3. // @connect skribbler.herokuapp.com
  4. // @grant GM_xmlhttpRequest
  5. // @grant GM.xmlHttpRequest
  6. // @homepageURL https://github.com/rosslh/skribbler
  7. // @icon https://skribbl.io/res/favicon.png
  8. // @licence MIT
  9. // @match *://skribbl.io/*
  10. // @name Skribbler
  11. // @namespace https://rosshill.ca
  12. // @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
  13. // @supportURL https://github.com/rosslh/skribbler/issues
  14. // @version 2.6.3
  15. // ==/UserScript==
  16. const state = {
  17.     content: document.createElement("span"),
  18.     links: document.createElement("strong"),
  19.     pattern: "",
  20.     prevAnswer: "",
  21.     prevClue: "",
  22.     wordsList: $(document.createElement("ul"))
  23. };
  24. unsafeWindow.dictionary = {
  25.     confirmed: [],
  26.     guessed: [],
  27.     oneOffWords: [],
  28.     standard: [],
  29.     validAnswers: []
  30. };
  31. function scrollDown() {
  32.     if ($("#screenGame").is(":visible") &&
  33.         $("html").scrollTop() < $("#screenGame").offset().top) {
  34.         $("html, body").animate({
  35.             scrollTop: $("#screenGame").offset().top
  36.         }, 1000);
  37.     }
  38. }
  39. function getPlayer() {
  40.     const nameElem = $('.info .name[style="color: rgb(0, 0, 255);')[0];
  41.     if (typeof nameElem !== "undefined") {
  42.         return nameElem.innerText.split(" (")[0];
  43.     }
  44.     return "";
  45. }
  46. function validClue(clue, minCharsFound) {
  47.     const someoneDrawing = $(".drawing").is(":visible");
  48.     const charsFound = clue.replace(/_|-| /g, "").length;
  49.     const noUnderscores = clue.replace(/_/g, "").length;
  50.     if (someoneDrawing &&
  51.         (unsafeWindow.dictionary.oneOffWords.length > 0 ||
  52.             (charsFound >= minCharsFound && noUnderscores !== clue.length))) {
  53.         return true;
  54.     }
  55.     if (!someoneDrawing) {
  56.         unsafeWindow.dictionary.validAnswers = [];
  57.         unsafeWindow.dictionary.guessed = [];
  58.         unsafeWindow.dictionary.oneOffWords = [];
  59.     }
  60.     return false;
  61. }
  62. function wordGuessed() {
  63.     if ($('.guessedWord .info .name[style="color: rgb(0, 0, 255);"]').length) {
  64.         unsafeWindow.dictionary.validAnswers = [];
  65.         unsafeWindow.dictionary.guessed = [];
  66.         unsafeWindow.dictionary.oneOffWords = [];
  67.         return true;
  68.     }
  69.     return false;
  70. }
  71. function missingChar(short, long) {
  72.     for (let i = 1; i < long.length + 1; i++) {
  73.         if (short === long.substring(0, i - 1) + long.substring(i, long.length)) {
  74.             return true;
  75.         }
  76.     }
  77.     return false;
  78. }
  79. function oneOff(listWord, guessedWord) {
  80.     if (listWord.length === guessedWord.length) {
  81.         let wrongLetters = 0;
  82.         for (let i = 0; i < listWord.length; i++) {
  83.             if (listWord.charAt(i) !== guessedWord.charAt(i)) {
  84.                 wrongLetters += 1;
  85.             }
  86.             if (wrongLetters > 1) {
  87.                 return false;
  88.             }
  89.         }
  90.         return wrongLetters === 1;
  91.     }
  92.     if (listWord.length === guessedWord.length - 1) {
  93.         return missingChar(listWord, guessedWord);
  94.     }
  95.     if (guessedWord.length === listWord.length - 1) {
  96.         return missingChar(guessedWord, listWord);
  97.     }
  98.     return false;
  99. }
  100. function checkPastGuesses(notOBO, word) {
  101.     if (unsafeWindow.dictionary.guessed.indexOf(word) !== -1) {
  102.         return false;
  103.     }
  104.     for (const oneOffWord of unsafeWindow.dictionary.oneOffWords) {
  105.         if (!oneOff(word, oneOffWord)) {
  106.             return false;
  107.         }
  108.     }
  109.     for (const str of notOBO) {
  110.         if (oneOff(word, str)) {
  111.             return false;
  112.         }
  113.     }
  114.     return true;
  115. }
  116. function getRegex(clue) {
  117.     return new RegExp(`^${clue.replace(/_/g, "[^- ]")}$`);
  118. }
  119. function filterWords(words, notOBO, clue) {
  120.     return words
  121.         .filter(word => word.length === clue.length &&
  122.         state.pattern.test(word) &&
  123.         checkPastGuesses(notOBO, word))
  124.         .sort();
  125. }
  126. function getWords(clue) {
  127.     const dict = unsafeWindow.dictionary;
  128.     let words;
  129.     if (dict.validAnswers.length === 0) {
  130.         // && dict.guessed.length === 0
  131.         words = dict.confirmed.slice();
  132.         for (const item of dict.standard) {
  133.             if (words.indexOf(item) === -1) {
  134.                 words.push(item);
  135.             }
  136.         }
  137.     }
  138.     else {
  139.         words = dict.validAnswers;
  140.     }
  141.     state.pattern = getRegex(clue);
  142.     const notOBO = [];
  143.     for (const word of dict.guessed) {
  144.         if (dict.oneOffWords.indexOf(word) === -1) {
  145.             notOBO.push(word);
  146.         }
  147.     }
  148.     if (!wordGuessed()) {
  149.         dict.validAnswers = filterWords(words, notOBO, clue);
  150.     }
  151.     else {
  152.         dict.validAnswers = [];
  153.     }
  154.     return dict.validAnswers;
  155. }
  156. function constructWordsList(clue) {
  157.     const newList = $(document.createElement("ul"));
  158.     if (validClue(clue, 0) && !wordGuessed()) {
  159.         const words = getWords(clue);
  160.         for (const word of words) {
  161.             const item = document.createElement("li");
  162.             const child = $(`<span onClick="submitGuess('${word}')">${word}</span>`);
  163.             child.css({ cursor: 'pointer', textDecoration: 'underline', textDecorationStyle: 'dotted' });
  164.             if (unsafeWindow.dictionary.confirmed.indexOf(word) > -1) {
  165.                 child.css({ fontWeight: 'bold' });
  166.             }
  167.             $(item).append(child);
  168.             newList.append(item);
  169.         }
  170.     }
  171.     state.wordsList.html(newList.html());
  172.     state.wordsList.css({
  173.         width: `${$(document).width() - $("#containerChat").width() - 40}px`
  174.     });
  175. }
  176. function getClue() {
  177.     return $("#currentWord");
  178. }
  179. function getClueText() {
  180.     return getClue()[0].textContent.toLowerCase();
  181. }
  182. function findGuessedWords() {
  183.     const player = getPlayer();
  184.     if (player) {
  185.         const guesses = $(`#boxMessages p[style='color: rgb(0, 0, 0);'] b:contains(${player}:)`)
  186.             .parent()
  187.             .find("span")
  188.             .not(".skribblerHandled")
  189.             .slice(-10);
  190.         guesses.each((i, elem) => {
  191.             const guessText = elem.innerText;
  192.             if (unsafeWindow.dictionary.guessed.indexOf(guessText) === -1) {
  193.                 unsafeWindow.dictionary.guessed.push(guessText);
  194.                 elem.classList.add("skribblerHandled");
  195.                 constructWordsList(getClueText());
  196.             }
  197.         });
  198.     }
  199. }
  200. function findCloseWords() {
  201.     const close = $("#boxMessages p[style='color: rgb(204, 204, 0); font-weight: bold;'] span:contains( is close!)")
  202.         .not(".skribblerHandled")
  203.         .slice(-10);
  204.     close.each((i, elem) => {
  205.         const text = elem.innerText.split("'")[1];
  206.         if (unsafeWindow.dictionary.oneOffWords.indexOf(text) === -1) {
  207.             unsafeWindow.dictionary.oneOffWords.push(text);
  208.             elem.classList.add("skribblerHandled");
  209.             constructWordsList(getClueText());
  210.         }
  211.     });
  212. }
  213. unsafeWindow.getInput = () => $("#inputChat");
  214. function validateInput() {
  215.     const word = getClueText();
  216.     const input = unsafeWindow.getInput()[0];
  217.     const remaining = word.length - input.value.length;
  218.     state.content.textContent = remaining;
  219.     state.content.style.color = "unset";
  220.     if (remaining > 0) {
  221.         state.content.textContent = `+${state.content.textContent}`;
  222.         state.content.style.color = "green";
  223.     }
  224.     else if (remaining < 0) {
  225.         state.content.style.color = "red";
  226.     }
  227.     state.pattern = getRegex(word);
  228.     const short = getRegex(word.substring(0, input.value.length));
  229.     if (state.pattern.test(input.value.toLowerCase())) {
  230.         input.style.border = "3px solid green";
  231.     }
  232.     else if (short.test(input.value.toLowerCase())) {
  233.         input.style.border = "3px solid orange";
  234.     }
  235.     else {
  236.         input.style.border = "3px solid red";
  237.     }
  238. }
  239. function showDrawLinks(clueText) {
  240.     if (clueText.length > 0 && clueText.indexOf("_") === -1) {
  241.         state.links.innerHTML = `<a style='color: blue' target='_blank'
  242. href='https://www.google.ca/search?tbm=isch&q=${clueText}'>Images</a>, `;
  243.         state.links.innerHTML += `<a style='color: blue' target='_blank'
  244. href='https://www.google.ca/search?tbm=isch&tbs=itp:lineart&q=${clueText}'>Line art</a>`;
  245.     }
  246.     else {
  247.         state.links.innerHTML = "";
  248.     }
  249. }
  250. function clueChanged() {
  251.     const clue = getClueText();
  252.     if (clue !== state.prevClue) {
  253.         state.prevClue = clue;
  254.         validateInput();
  255.         constructWordsList(clue);
  256.         showDrawLinks(clue);
  257.     }
  258. }
  259. function answerShown(username, password) {
  260.     let answer = $("#overlay .content .text")[0].innerText;
  261.     if (answer.slice(0, 14) === "The word was: ") {
  262.         answer = answer.slice(14);
  263.         if (answer !== state.prevAnswer) {
  264.             state.prevAnswer = answer;
  265.             unsafeWindow.dictionary.oneOffWords = [];
  266.             unsafeWindow.dictionary.guessed = [];
  267.             unsafeWindow.dictionary.validAnswers = [];
  268.             if (admin) {
  269.                 handleWord(answer, username, password);
  270.             }
  271.         }
  272.     }
  273. }
  274. function makeGuess(clue) {
  275.     if (validClue(clue, 1) && !wordGuessed()) {
  276.         const words = unsafeWindow.dictionary.validAnswers;
  277.         const confWords = [];
  278.         for (const item of words) {
  279.             if (unsafeWindow.dictionary.confirmed.indexOf(item) > -1) {
  280.                 confWords.push(item);
  281.             }
  282.         }
  283.         let guess;
  284.         if (confWords.length > 0) {
  285.             guess = confWords[Math.floor(Math.random() * confWords.length)];
  286.         }
  287.         else {
  288.             guess = words[Math.floor(Math.random() * words.length)];
  289.         }
  290.         guessWord(guess, clue);
  291.     }
  292. }
  293. unsafeWindow.submitGuess = (guess) => {
  294.     const submitProp = Object.keys(unsafeWindow.formChat).filter((k) => ~k.indexOf("jQuery") // tslint:disable-line no-bitwise
  295.     )[0];
  296.     unsafeWindow.getInput().val(guess);
  297.     unsafeWindow.formChat[submitProp].events.submit[0].handler();
  298. };
  299. function guessWord(guess, clue) {
  300.     window.setTimeout(() => {
  301.         if (unsafeWindow.getInput().val() === "" && validClue(clue, 1) && !wordGuessed()) {
  302.             unsafeWindow.submitGuess(guess);
  303.         }
  304.     }, Math.floor(Math.random() * (Number($("#guessRate").val()) / 3)));
  305. }
  306. function toggleWordsList() {
  307.     if ($(state.wordsList).is(":visible")) {
  308.         if (state.wordsList.children().length === 0 ||
  309.             wordGuessed() ||
  310.             !validClue(getClueText(), 0)) {
  311.             state.wordsList.hide();
  312.         }
  313.     }
  314.     else if (state.wordsList.children().length > 0 &&
  315.         !wordGuessed() &&
  316.         validClue(getClueText(), 0)) {
  317.         state.wordsList.show();
  318.     }
  319. }
  320. function stillHere() {
  321.     if (document.hidden &&
  322.         $(".modal-dialog:contains(Are you still here?)").is(":visible")) {
  323.         alert("Action required.");
  324.     }
  325. }
  326. function main(username, password) {
  327.     $("#audio").css({
  328.         left: "unset",
  329.         right: "0px"
  330.     }); // so it doesn't cover timer
  331.     window.setInterval(scrollDown, 2000);
  332.     $(state.links).css({
  333.         padding: "0 1em 0 1em"
  334.     });
  335.     getClue().after(state.links);
  336.     const formArea = $("#formChat")[0];
  337.     $(state.content).css({
  338.         left: "295px",
  339.         position: "relative",
  340.         top: "-25px"
  341.     });
  342.     state.wordsList.css({
  343.         "background-color": "#eee",
  344.         "border-radius": "2px",
  345.         columns: "4",
  346.         "list-style-position": "inside",
  347.         "margin-top": "10px",
  348.         padding: "4px",
  349.         width: "70%"
  350.     });
  351.     formArea.appendChild(state.content);
  352.     $("#screenGame")[0].appendChild(state.wordsList[0]);
  353.     const input = unsafeWindow.getInput()[0];
  354.     input.style.border = "3px solid orange";
  355.     window.setInterval(() => {
  356.         clueChanged();
  357.         answerShown(username, password);
  358.         findCloseWords();
  359.         findGuessedWords();
  360.         toggleWordsList();
  361.         stillHere();
  362.     }, 1000);
  363.     $("#boxChatInput").append($(`<div style="background-color:#eee; position:relative;
  364. top:-20px; padding:0 5px; width:auto; margin:0;">
  365. <input id="guessEnabled" name="guessEnabled" style="width:6px; height:6px;" type="checkbox">
  366. <label for="guessEnabled" style="all: initial; padding-left:5px;">Enable auto-guesser</label><br>
  367. <label for="guessRate" style="all: initial; padding-right:5px;">Guess frequency (seconds):</label>
  368. <input id="guessRate" name="guessRate" type="number" step="0.5" min="1" value="1.5" style="width:4em;"></div>`));
  369.     let lastGuess = 0;
  370.     let lastTyped = 0;
  371.     window.setInterval(() => {
  372.         if ($("#guessEnabled").is(":checked") &&
  373.             Date.now() - lastTyped >= 1500 &&
  374.             Date.now() - lastGuess >= 1000 * Number($("#guessRate").val())) {
  375.             lastGuess = Date.now();
  376.             makeGuess(getClueText());
  377.         }
  378.     }, 500);
  379.     unsafeWindow.getInput().keyup(() => {
  380.         lastTyped = Date.now();
  381.     });
  382.     unsafeWindow.getInput().keyup(validateInput);
  383. }
  384. function fetchWords(username, password) {
  385.     GM.xmlHttpRequest({
  386.         method: "GET",
  387.         url: "https://skribbler.herokuapp.com/api/words",
  388.         onload(res) {
  389.             const response = JSON.parse(res.responseText);
  390.             unsafeWindow.dictionary.standard = response.default;
  391.             unsafeWindow.dictionary.confirmed = response.confirmed;
  392.             const run = window.setInterval(() => {
  393.                 if (getClue()) {
  394.                     clearInterval(run);
  395.                     main(username, password);
  396.                 }
  397.             }, 1000);
  398.         }
  399.     });
  400. }
  401. $(document).ready(() => {
  402.     if (typeof GM === "undefined") {
  403.         // polyfill GM4
  404.         GM = {
  405.             xmlHttpRequest: GM_xmlhttpRequest
  406.         };
  407.     }
  408.     let activate;
  409.     if (admin) {
  410.         activate = $("<button>Activate skribbler (admin)</button>");
  411.     }
  412.     else
  413.         activate = $("<button>Activate skribbler</button>");
  414.     activate.css({
  415.         "font-size": "0.6em"
  416.     });
  417.     $(".loginPanelTitle")
  418.         .first()
  419.         .append(activate);
  420.     activate.click(() => {
  421.         activate.hide();
  422.         if (admin) {
  423.             getLoginDetails();
  424.         }
  425.         else {
  426.             fetchWords("", "");
  427.         }
  428.     });
  429. });
  430. const handleWord = (clue, username, password) => { };
  431. const getLoginDetails = () => { };
  432. const admin = false;