您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A powerful membean helper that uses a variety of tools to answer membean questions.
// ==UserScript== // @name Membean Tracker // @namespace http://tampermonkey.net/ // @version 0.1.0 // @description A powerful membean helper that uses a variety of tools to answer membean questions. // @author Squidtoon99 (https://squid.pink) // @include https://membean.com/training_sessions/*/user_state // @include https://membean.com/mywords/* // @include https://membean.com/training_sessions/new // @icon https://www.google.com/s2/favicons?domain=membean.com // @grant GM_xmlhttpRequest // @connect squid.pink // ==/UserScript== var cache = {}; var apiURL = "https://api.squid.pink"; var q; const plugins = [ autoSelectHTMLPlugin, questionPlugin, checkExampleSingleNode, checkExampleMultiNode, definitionOnHoverPlugin, autoNextPlugin, ]; const persistent_plugins = [storeCorrectAnswerPlugin, autoTypeNewWordPlugin]; const non_question_plugins = [ autoAnswerTextInputPlugin, autoStartNewSessionPlugin, ]; setInterval(() => { // Checking if there is a new question var questions = document.getElementsByClassName("question"); if (questions.length > 0) { var question = questions[0]; var iter; if (question !== q) { q = question; cache.q = question; iter = plugins; // the plugins that only need to run when the question renders cache.answer = false; } else { iter = persistent_plugins; // the plugins that need to be constantly running } for (var z = 0; z < iter.length; z++) { iter[z](); } if (cache.answer == false) { var choices = document.getElementsByClassName("choice"); setTimeout(() => { if ( choices === document.getElementsByClassName("choice") && document.getElementsByClassName("choice correct").length == 0 ) { //console.log("autoclicking"); //document.getElementsByClassName("choice")[0].click(); } }, 500); } } else { for (var x = 0; x < non_question_plugins.length; x++) { non_question_plugins[x](); } } if (Math.floor(Math.random() * 1000) === 5) { location.reload(); window.console.log("reloading"); } }, 500); setInterval(() => { if (document.getElementsByClassName("take_a_break").length == 1) { console.log("taking a break"); document.getElementById("Click_me_to_stop").click(); setTimeout( () => (window.location.href = "https://membean.com/training_sessions/new"), 1000 ); } }, 1000); function answer(answer_num, reason) { var o = Object.values(document.getElementsByClassName("choice"))[answer_num]; cache.answer = true; console.log(`Found correct answer (${answer_num}): [${reason}]`); o.className = "choice correct"; setTimeout(() => o.click(), 8000); // set this timeout to how long you want to wait (ms) before answering questions //o.click(); } function autoNextPlugin() { var c = document.getElementById("next-btn"); if (c) { setTimeout(() => c.click(), 1000); } } function autoSelectHTMLPlugin() { // This plugin simply will select the choice answer html selector var c = document.getElementsByClassName("choice answer"); if (c.length > 0) { c[0].click(); } } function questionPlugin() { // This queries the api to see if there is a value stored var url = `${apiURL}/membean/${encodeURIComponent(q.textContent.trim())}`; GM_xmlhttpRequest({ method: "GET", url: url, headers: { "Content-Type": "application/json", }, responseType: "json", onload: (rspObj) => { if (rspObj.status == 404) { // temp autoclicking var c = document.getElementsByClassName("choice"); return; } else if (rspObj.status != 200) { reportAJAX_Error(rspObj); return; } let data = rspObj.response; console.log(data); if (data.answer) { var choices = document.getElementsByClassName("choice"); for (var iter = 0; iter < choices.length; iter++) { if (choices[iter].textContent.trim() == data.answer.trim()) { answer(iter, "stored question"); } } window.console.log(`[${q}] Answer: ${data.answer[0].answer}`); } else { window.console.log("No storage for this question"); } }, onabort: reportAJAX_Error, onerror: reportAJAX_Error, ontimeout: reportAJAX_Error, }); } function storeCorrectAnswerPlugin() { // stores the correct answer whenever html is updated var formatter = document.getElementsByClassName( "single-column-layout with-image" ); var correct = document.getElementsByClassName("choice correct"); if (correct.length > 0) { var e = correct[0]; if (cache.e === e) { return; } cache.e = e; var data; if (formatter.length == 1) { // Images suck why membean why var img = formatter[0].children[1]; data = { question: img.src, answer: e.textContent }; } else { // Not an image question just a standard one data = { question: cache.q.textContent.trim(), answer: e.textContent.trim(), }; } if (data.question.includes("Try again!")) { return; } GM_xmlhttpRequest({ method: "POST", url: `${apiURL}/membean/`, data: JSON.stringify(data), headers: { "Content-Type": "application/json", }, responseType: "json", onload: (rspObj) => { if (rspObj.status == 201) { window.console.log("Stored answer for: " + data.question); } else { console.log(rspObj); } }, onabort: reportAJAX_Error, onerror: reportAJAX_Error, ontimeout: reportAJAX_Error, }); } } function checkExampleSingleNode() { // In case they italize a word we think it's the word that's the answer and then make it look "correct" var nodes = cache.q.children; if (nodes.length == 2 && nodes[1].nodeName === "EM") { var word = nodes[1].textContent; if ( word[word.length - 1] === "s" && !"aeious".split("").includes(word[word.length - 2]) ) { word = word.slice(0, word.length - 1); } if (word.replaceAll("_", "").trim() === "") { return; } console.log(`Question about ${word}`); GM_xmlhttpRequest({ method: "GET", url: `https://membean.com/mywords/${word}`, responseType: "json", onload: (rspObj) => { var parser = new DOMParser(); var htmlDoc = parser.parseFromString(rspObj.responseText, "text/html"); var canswer = htmlDoc.getElementsByClassName("choice answer")[0]; if (!canswer) { return; } var choices = document.getElementsByClassName("choice"); var choicesParsed = {}; for (var i = 0; i < choices.length; i++) { if (choices[i].textContent === canswer.textContent) { answer(i, "singleNode example"); return; } else { choicesParsed[i] = levenshtein( choices[i].textContent, canswer.textContent ); // parsing the most probable answer } } let bestChoice = Object.keys(choicesParsed).reduce((a, b) => choicesParsed[a] > choicesParsed[b] ? a : b ); if ( choicesParsed[bestChoice] >= 10 || bestChoice == Object.keys(bestChoice).length - 1 ) { return; // Not a good enough result } answer(bestChoice, "single node best example"); }, onabort: reportAJAX_Error, onerror: reportAJAX_Error, ontimeout: reportAJAX_Error, }); } else if (nodes.length === 3 && nodes[1].nodeName === "EM") { word = document.getElementsByClassName("question")[0].children[2].textContent; console.log("stupid question about roots: " + word); GM_xmlhttpRequest({ method: "GET", url: `https://membean.com/mywords/${word}`, responseType: "json", onload: (rspObj) => { var parser = new DOMParser(); var htmlDoc = parser.parseFromString(rspObj.responseText, "text/html"); var defData = {}; Object.values( htmlDoc.getElementById("word-structure").children[1].children[0] .children[0].children ).forEach((key) => { defData[key.children[0].textContent.trim()] = key.children[2].textContent.trim(); }); for (let [key, value] of Object.entries(defData)) { if (nodes[1].textContent.trim() === key.trim()) { console.log( `checking ${key}: ${value} ? ${nodes[1].textContent.trim()}` ); var choices = document.getElementsByClassName("choice"); for (let [index] of Object.keys(choices)) { var answerChoice = choices[index]; var choiceNodes = answerChoice.textContent.trim().split(", "); if (choiceNodes.some((k) => value.includes(k))) { answer(index, "root structure"); return; } } } } }, onabort: reportAJAX_Error, onerror: reportAJAX_Error, ontimeout: reportAJAX_Error, }); } } function checkExampleMultiNode() { // In case they italize a word we think it's the word that's the answer and then make it look "correct" var words = document.getElementsByClassName("choice-word"); if ( !Object.values(words).every((word) => { word.textContent.trim().indexOf(" ") >= 2 || word.textContent.trim() == "I'm not sure"; }) ) { // all of them are words without spaces //window.console.log("multi-node answers are words"); var bestChoices = {}; for (let iter = 0; iter < words.length; iter++) { let word = words[iter].textContent.trim(); GM_xmlhttpRequest({ method: "GET", url: `https://membean.com/mywords/${word}`, responseType: "json", onload: (rspObj) => { var parser = new DOMParser(); var htmlDoc = parser.parseFromString( rspObj.responseText, "text/html" ); var canswer = htmlDoc.getElementsByClassName("choice answer")[0]; if (canswer) { var choices = document.getElementsByClassName("choice"); var choicesParsed = {}; for (var i = 0; i < choices.length; i++) { if (choices[i].textContent === canswer.textContent) { answer(i, "multi node answer"); return; } else { choicesParsed[i] = levenshtein( choices[i].textContent, canswer.textContent ); // parsing the most probable answer } } let bestChoice = Object.keys(choicesParsed).reduce((a, b) => choicesParsed[a] > choicesParsed[b] ? a : b ); if (choicesParsed[bestChoice] >= 20) { return; // Not a good enough result } answer(bestChoice, "best choice parsed for multi node"); } }, onabort: reportAJAX_Error, onerror: reportAJAX_Error, ontimeout: reportAJAX_Error, }); } } } function cdnStoragePlugin() { var formatter = document.getElementsByClassName( "single-column-layout with-image" ); if (formatter.length == 1) { var img = formatter[0].children[1]; if (img.alt == "constellation question") { // It is a constellation question - tesseract can't parse so I'm kinda fcked for now var src = img.src; GM_xmlhttpRequest({ method: "GET", url: `${apiURL}/membean/${encodeURIComponent(src)}`, headers: { "Content-Type": "application/json", }, responseType: "json", onload: (rspObj) => { (rspObj) => { if (rspObj.status != 200) { reportAJAX_Error(rspObj); return; } let data = rspObj.response; console.log(data); if (data.answer) { var choices = document.getElementsByClassName("choice"); for (var iter = 0; iter < choices.length; iter++) { if (choices[iter].textContent.trim() == data.answer.trim()) { answer(iter, "stored question"); } } window.console.log( `[${q}] Answer: ${rspObj.response.answer[0].answer}` ); } }; }, onabort: reportAJAX_Error, onerror: reportAJAX_Error, ontimeout: reportAJAX_Error, }); } } } function definitionOnHoverPlugin() { // In case they italize a word we think it's the word that's the answer and then make it look "correct" var words = document.getElementsByClassName("choice-word"); if ( !Object.values(words).every((word) => { word.textContent.trim().indexOf(" ") >= 2 || word.textContent.trim() == "I'm not sure"; }) ) { // all of them are words without spaces //window.console.log("answers are sentences"); for (let iter = 0; iter < words.length; iter++) { let word = words[iter].textContent.trim(); GM_xmlhttpRequest({ method: "GET", url: `https://membean.com/mywords/${word}`, responseType: "json", onload: (rspObj) => { var parser = new DOMParser(); var htmlDoc = parser.parseFromString( rspObj.responseText, "text/html" ); var definition = htmlDoc.getElementsByClassName("def-text"); if (definition.length > 0) { words[iter].title = definition[0].textContent.trim(); // Checking here for filler questions like 1 + _ = 3 and the def we get is 1 + 2 = 3 var processed_question = cache.q.textContent .replace("_", word) .replaceAll("_", "") .trim(); var sentences = Object.values( htmlDoc .getElementById("context-paragraph") .textContent.split(". ") ).map((val) => val.trim() + "."); sentences.push(definition[0].textContent.trim()); for (var s_iter = 0; s_iter < sentences.length; s_iter++) { var similarity = levenshtein( sentences[s_iter], processed_question.trim() ); if (similarity < 40) { window.console.log( `Similarity: ${similarity}: ${definition[0].textContent.trim()} / ${processed_question.trim()}` ); answer(iter, `best similarity for def hover: ${similarity}`); } else { //window.console.log( // `Invalid Hint-Def: ${similarity} - ${sentences[s_iter]} != ${processed_question.trim()}` // ); } } //window.console.log(`Set title for ${word}`); } }, onabort: reportAJAX_Error, onerror: reportAJAX_Error, ontimeout: reportAJAX_Error, }); } } else { //window.console.log("answers are words"); var hint_nodes = cache.q.children; if (cache.hint_nodes !== hint_nodes) { cache.hint_nodes = hint_nodes; if (hint_nodes.length == 2 && hint_nodes[1].nodeName === "EM") { var word = hint_nodes[1].textContent; if ( word[word.length - 1] === "s" && !"aeious".split("").includes(word[word.length - 2]) ) { word = word.slice(0, word.length - 1); } window.console.log("Italicised word " + word); GM_xmlhttpRequest({ method: "GET", url: `https://membean.com/mywords/${word}`, responseType: "json", onload: (rspObj) => { var parser = new DOMParser(); var htmlDoc = parser.parseFromString( rspObj.responseText, "text/html" ); var def = htmlDoc.getElementsByClassName("def-text"); if (def.length > 0) { var processed_question = cache.q.textContent .replace("_", word) .replaceAll("_", ""); var sentences = Object.values( htmlDoc .getElementById("context-paragraph") .textContent.split(". ") ).map((val) => val.trim() + "."); sentences.push(def[0].textContent.trim()); for (var iter = 0; iter < sentences.length; iter++) { var similarity = levenshtein( sentences[iter], processed_question.trim() ); if (similarity < 30) { window.console.log( `Similarity: ${similarity}: ${definition[0].textContent.trim()} / ${processed_question.trim()}` ); answer(iter, "best similarity for multi def hover"); return; } else { //window.console.log( // `Invalid Hint-Def: ${similarity} - ${ // sentences[iter] // } != ${processed_question.trim()}` // ); } } cache.q.title = def[0].textContent.trim(); } else { window.console.log("No def"); } }, onabort: reportAJAX_Error, onerror: reportAJAX_Error, ontimeout: reportAJAX_Error, }); } } } } function autoTypeNewWordPlugin() { var letters = document.getElementsByClassName("rest-letters"); if (letters.length >= 1) { var word = document.getElementById("pronounce-sound"); if (word) { var text = word.attributes[3].value.split("-")[1]; setTimeout(() => (document.getElementById("choice").value = text), 1000); } } } function autoAnswerTextInputPlugin() { var valid = document.getElementsByClassName("single-column-layout cloze"); if (valid.length > 0) { var v = valid[0]; if (v === cache.v) { return; } cache.v = v; console.log("typing question"); //setTimeout(() => document.getElementById("notsure").click(), 600); } } function autoStartNewSessionPlugin() { var valid = document.getElementsByClassName("button-to"); if (valid.length > 0) { Object.values(valid).reverse()[0].children[0].click(); } } function processJSON_Response(rspObj) { if (rspObj.status != 200) { reportAJAX_Error(rspObj); } else { window.console.log(rspObj.responseText); } } function reportAJAX_Error(rspObj) { console.log(rspObj); window.console.log( `TM scrpt (membean-tracker) => Error ${rspObj.status}! ${rspObj.statusText} (${rspObj.url})` ); } const levenshtein = function (a, b) { // difference between strings for helping w/ similar membean answers if (a.length == 0) return b.length; if (b.length == 0) return a.length; // swap to save some memory O(min(a,b)) instead of O(a) if (a.length > b.length) { var tmp = a; a = b; b = tmp; } var row = []; // init the row for (var i = 0; i <= a.length; i++) { row[i] = i; } // fill in the rest for (var z = 1; z <= b.length; z++) { var prev = z; for (var j = 1; j <= a.length; j++) { var val; if (b.charAt(z - 1) == a.charAt(j - 1)) { val = row[j - 1]; // match } else { val = Math.min( row[j - 1] + 1, // substitution prev + 1, // insertion row[j] + 1 ); // deletion } row[j - 1] = prev; prev = val; } row[a.length] = prev; } return row[a.length]; };