您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Block Bots in Blooket!
当前为
// ==UserScript== // @name Blooket Bot Blocker (BBB) // @namespace https://tameprmonkey.net/ // @version 0.6 // @description Block Bots in Blooket! // @author Thundercatcher // @match *://*dashboard.blooket.com/play/* // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com // @grant none // ==/UserScript== if ( window.fireLoaded || window.parent?.kantibotEnabled || window.parent?.fireLoaded ) { throw "[ANTIBOT] - page is loaded"; } if (window.localStorage.extraCheck) { console.log("[ANTIBOT] - Detected PIN Checker"); } if (window.localStorage.kahootThemeScript) { console.log("[ANTIBOT] - Detected KonoSuba Theme"); } let writePromise = new Promise(() => {}); // Should allow for default behavior and reload page if (location.pathname.includes("/oauth2/")) { setTimeout(() => { location.reload(); }, 3e3); } else { writePromise = new Promise((res) => setTimeout(() => { document.write(` <p id="antibot-loading-notice">[ANTIBOT] - Patching Kahoot. Please wait.</p> <p>If this screen stays blank for a long time, report an issue in <a href="https://discord.gg/pPdvXU6">Discord</a>, <a href="https://github.com/theusaf/kantibot">GitHub</a>, or <a href="https://greasyfork.org/en/scripts/374093-kantibot">Greasyfork</a>.</p> `); res(); }, 250) ); } window.antibotAdditionalScripts = window.antibotAdditionalScripts || []; window.antibotAdditionalReplacements = window.antibotAdditionalReplacements || []; window.kantibotEnabled = true; /** * External Libraries * Note: This script requires https://raw.githubusercontent.com/theusaf/a-set-of-english-words/master/index.js * - This is a script that loads 275k english words into a set. (about 30MB ram?) * @see https://github.com/theusaf/a-set-of-english-words * * Also, it requires https://raw.githubusercontent.com/theusaf/random-name/master/names.js * - Loads a bunch of names into sets. * @see https://raw.githubusercontent.com/theusaf/random-name * * If these get removed or fail to load, this should not break the service, but will cause certain features to not work */ const url = window.location.href, requiredAssets = [ "https://raw.githubusercontent.com/theusaf/a-set-of-english-words/master/index.js", "https://raw.githubusercontent.com/theusaf/random-name/master/names.js" ], importBlobURLs = {}; window.importBlobURLs = importBlobURLs; /** * Creates a blob url from a string. * * @param {string} script The text to convert to a blob url * @returns {string} The blob url */ function createBlobURL(script) { return URL.createObjectURL( new Blob([script], { type: "application/javascript" }) ); } /** * Patches the imported url and resolves with the patched script. * * @param {string} url The url being imported * @returns {Promise<any>} The imported script's exports */ async function antibotImport(url) { const importBlobURLs = window.importBlobURLs ?? window.parent?.importBlobURLs ?? window.windw?.importBlobURLs, makeHttpRequest = window.kantibotMakeHTTPRequest ?? window.parent?.kantibotMakeHTTPRequest ?? window.windw?.kantibotMakeHTTPRequest, createBlobURL = window.kantibotCreateBlobURL ?? window.parent?.kantibotCreateBlobURL ?? window.windw?.kantibotCreateBlobURL; console.log(`[ANTIBOT] - Handling import of ${url}`); // We need to intercept any requests and modify them! if (url.startsWith(".")) { url = `https://assets-cdn.kahoot.it/player/v2/assets${url.substring(1)}`; } if (importBlobURLs[url]) { console.log(`[ANTIBOT] - ${url} already exists.`); return import(importBlobURLs[url]); } else { // check if this url needs to be patched! let { response: moduleCode } = await makeHttpRequest(url), needsEdit = false; const importFunctionRegex = /\bimport\b\(/g, importStatementRegex = /\bimport\b[\w.\-{}\s[\],:]*?\bfrom\b"[\w.\-/]*?"/g; // if it has dynamic import statements if (importFunctionRegex.test(moduleCode)) { needsEdit = true; moduleCode = moduleCode.replace(importFunctionRegex, "antibotImport("); moduleCode = `${antibotImport.toString()}${moduleCode}`; } // if it has a regular import statement if (importStatementRegex.test(moduleCode)) { // Check if url exists for the import for (const imp of moduleCode.match(importStatementRegex)) { const [, impURL] = imp.match(/"([\w.\-/]*?)"/); let editedImportURL = impURL; if (editedImportURL.startsWith(".")) { editedImportURL = `https://assets-cdn.kahoot.it/player/v2/assets${editedImportURL.substring( 1 )}`; } if (importBlobURLs[editedImportURL]) { needsEdit = true; moduleCode = moduleCode.replace( impURL, importBlobURLs[editedImportURL] ); } } } if (needsEdit) { // Create a blob url and import it! console.log(`[ANTIBOT] - Modifying ${url} and creating blob url.`); const blobURL = createBlobURL(moduleCode); importBlobURLs[url] = blobURL; return import(blobURL); } else { console.log(`[ANTIBOT] - ${url} does not require a blob version.`); // So we don't keep checking it each time importBlobURLs[url] = url; return import(url); } } } /** * Makes an http request, and resolves with the request after the response is received. * * @param {string} url The url to request * @returns {Promise<XMLHttpRequest>} */ function makeHttpRequest(url) { const request = new XMLHttpRequest(); request.open("GET", url); request.send(); return new Promise((resolve, reject) => { request.onerror = request.onload = () => { if (request.readyState === 4 && request.status === 200) { resolve(request); } else { reject(request); } }; }); } /** * Fetches the main page, and fetches the assets to be modified. * * @returns {Promise<{ * page: string, * mainScriptURL: string * }>} The page's content (patched) and the main script's url */ async function fetchMainPage() { const mainPageRequest = await makeHttpRequest(url), [mainScriptURL] = mainPageRequest.response.match( /\/\/assets-cdn.*\/v2\/assets\/index.*?(?=")/ ), originalPage = mainPageRequest.response.replace( /<script type="module".*?<\/script>/, "" ); return { page: originalPage, mainScriptURL: mainScriptURL.startsWith("//") ? `https:${mainScriptURL}` : mainScriptURL }; } /** * Fetches the main script and patches it to gain access to internal data. * * @param {string} mainScriptURL The url of the main script * @returns {Promise<string>} The main script (patched) */ async function fetchMainScript(mainScriptURL) { const mainScriptRequest = await makeHttpRequest(mainScriptURL); let mainScript = mainScriptRequest.response; // Access the currentQuestionTimer and change the question time const currentQuestionTimerRegex = /currentQuestionTimer:([$\w]+)\.payload\.questionTime/, [, currentQuestionTimerLetter] = mainScript.match( currentQuestionTimerRegex ); mainScript = mainScript.replace( currentQuestionTimerRegex, `currentQuestionTimer:${currentQuestionTimerLetter}.payload.questionTime + (()=>{ return (windw.antibotData.settings.teamtimeout * 1000) || 0; })()` ); // Access global functions. Also gains direct access to the controllers? const globalFuncRegex = /\({[^"`]*?quiz[^"`]*?startQuiz:([$\w]+).*?}\)=>{(?=var)/, [globalFuncMatch, globalFuncLetter] = mainScript.match(globalFuncRegex); mainScript = mainScript.replace( globalFuncRegex, `${globalFuncMatch}windw.antibotData.kahootInternals.globalFuncs = {startQuiz:${globalFuncLetter}};` ); // Access the fetched quiz information. Allows the quiz to be modified when the quiz is fetched! // Note to future maintainer: if code switches back to using a function(){} rather than arrow function, see v3.1.5 const fetchedQuizInformationRegex = /RETRIEVE_KAHOOT_ERROR",.*?{response:([$\w]+)}\)/, [fetchedQuizInformationCode, fetchedQuizInformationLetter] = mainScript.match(fetchedQuizInformationRegex); mainScript = mainScript.replace( fetchedQuizInformationRegex, `RETRIEVE_KAHOOT_ERROR",${ fetchedQuizInformationCode .split('RETRIEVE_KAHOOT_ERROR",')[1] .split("response:")[0] }response:(()=>{ windw.antibotData.kahootInternals.globalQuizData = ${fetchedQuizInformationLetter}; windw.antibotData.methods.extraQuestionSetup(${fetchedQuizInformationLetter}); return ${fetchedQuizInformationLetter}; })()})` ); // Access the core data const coreDataRegex = /([$\w]+)\.game\.core/, [, coreDataLetter] = mainScript.match(coreDataRegex); mainScript = mainScript.replace( coreDataRegex, `(()=>{ if(typeof windw !== "undefined"){ windw.antibotData.kahootInternals.kahootCore = ${coreDataLetter}; } return ${coreDataLetter}.game.core; })()` ); // Access game settings (somehow removed from the core data...) // 3.2.8 --> added back to core data // This code doesn't actually do anything, but is kept in case const gameSettingsRegex = /getGameOptions\(\){/; mainScript = mainScript.replace( gameSettingsRegex, `getGameOptions() { if (typeof windw !== "undefined") { windw.antibotData.kahootInternals.gameOptions = this; }` ); // Access two factor stuff const twoFactorRegex = /(const [$\w]+=)7,/; mainScript = mainScript.replace( twoFactorRegex, `${mainScript.match(twoFactorRegex)[1]}(()=>{ const antibotConfig = JSON.parse(localStorage.antibotConfig || "{}"), {twoFactorTime} = antibotConfig; if (twoFactorTime) { return +twoFactorTime; } else { return 7; } })(),` ); // Overwrite navigation (opens in parent, preventing iframe issues) const navigationRegex = /prototype\.navigate=function\(([$\w]+)\){(.{1,80}?window\.location\.replace)/, [, navigationLetter, navigationOriginalCode] = mainScript.match(navigationRegex); mainScript = mainScript.replace( navigationRegex, `prototype.navigate = function(${navigationLetter}) { if (${navigationLetter}.url && typeof windw !== "undefined") { windw.location.replace(${navigationLetter}.url); return; } ${navigationOriginalCode}` ); // access message socket // moved from "vendors" const patchedScriptRegex = /\.onMessage=function\(([$\w]+),([$\w]+)\)\{/, [, websocketMessageLetter1, websocketMessageLetter2] = mainScript.match(patchedScriptRegex); mainScript = mainScript.replace( patchedScriptRegex, `.onMessage = function(${websocketMessageLetter1},${websocketMessageLetter2}){ windw.antibotData.methods.websocketMessageHandler(${websocketMessageLetter1},${websocketMessageLetter2});` ); // other replacements for (const func of window.antibotAdditionalReplacements) { mainScript = func(mainScript); } // modified import mainScript = mainScript.replace(/\bimport\b\(/g, "antibotImport("); mainScript = `${antibotImport.toString()}${mainScript}`; return mainScript; } const kantibotProgramCode = () => { class EvilBotJoinedError extends Error { constructor() { super("Bot Banned, Ignore Join"); } } class AnsweredTooQuicklyError extends Error { constructor() { super("Answer was too quick!"); } } const windw = window.parent; window.windw = windw; /** * createSetting - Creates a setting option string * * @param {String} name The name of the option * @param {String} type The type of the option * @param {String} id The id of the option * @param {String} description The description of the option * @param {String} default The default value of the option * @param {Function} setup A function to modify the input,label * @param {Function} callback A function to call when the value changes * @returns {String} The resulting HTML for the option */ function createSetting( name, type, id, description, def = null, setup = () => {}, callback = () => {} ) { const label = document.createElement("label"), input = type === "textarea" ? document.createElement("textarea") : document.createElement("input"), container = document.createElement("div"); if (type !== "textarea") { input.setAttribute("type", type); } else { input.setAttribute( "onclick", ` this.className = "antibot-textarea"; ` ); input.setAttribute( "onblur", ` this.className = ""; ` ); } input.id = label.htmlFor = `antibot.config.${id}`; label.id = input.id + ".label"; label.title = description; label.innerHTML = name; if (type === "checkbox") { container.append(input, label); input.setAttribute( "onclick", ` const value = event.target.checked; windw.antibotData.methods.setSetting(event.target.id.match(/\\w+$/)[0], value); (${callback.toString()})(event.target); ` ); } else { container.append(label, input); input.setAttribute( "onchange", ` const value = event.target.nodeName === "TEXTAREA" ? event.target.value.split("\\n") : event.target.type === "number" ? +event.target.value : event.target.value; windw.antibotData.methods.setSetting(event.target.id.match(/\\w+$/)[0], value); (${callback.toString()})(event.target); ` ); label.className = "antibot-input"; } if (def != null) { if (type === "checkbox") { if (def) { input.setAttribute("checked", ""); } } else { input.setAttribute("value", `${def}`); } } setup(input, label); return container.outerHTML; } // create watermark const UITemplate = document.createElement("template"); UITemplate.innerHTML = `<div id="antibotwtr"> <p>v3.5.0 ©theusaf</p> <p id="antibot-killcount">0</p> <details> <summary>config</summary> <div id="antibot-settings"> ${createSetting( "Block Fast Answers", "checkbox", "timeout", "Blocks answers sent 0.5 seconds after the question starts" )} ${createSetting( "Block Random Names", "checkbox", "looksRandom", "Blocks names that look random, such as 'rAnDOM naMe'", true )} ${createSetting( "Block Format F[.,-]L", "checkbox", "blockformat1", "Blocks names using the format [First][random char][Last]", true )} ${createSetting( "Additional Blocking Filters", "checkbox", "blockservice1", "Enables multiple additional blocking filters for some bot programs" )} ${createSetting( "Block Numbers", "checkbox", "blocknum", "Blocks names containing numbers, if multiple with numbers join within a short period of time" )} ${createSetting( "Force Alphanumeric Names", "checkbox", "forceascii", "Blocks names containing non-alphanumeric characters, if multiple join within a short period of time" )} ${createSetting( "Detect Patterns", "checkbox", "patterns", "Blocks bots spammed using similar patterns" )} ${createSetting( "Additional Question Time", "number", "teamtimeout", "Adds extra seconds to a question", 0, (input) => input.setAttribute("step", 1) )} ${createSetting( "Two-Factor Auth Timer", "number", "twoFactorTime", "Specify the number of seconds for the two-factor auth. The first iteration will use the default 7 seconds, then will use your input", 7, (input) => { input.setAttribute("step", 1); input.setAttribute("min", 1); }, () => { windw.antibotData.methods.kahootAlert( "Changes will only take effect upon reload." ); } )} ${createSetting( "Name Match Percent", "number", "percent", "The percent to check name similarity before banning the bot.", 0.6, (input) => input.setAttribute("step", 0.1) )} ${createSetting( "Word Blacklist", "textarea", "wordblock", "Block names containing any from a list of words. Separate by new line." )} ${createSetting( "Auto-Lock Threshold", "number", "ddos", "Specify the number of bots/minute to lock the game. Set to 0 to disable", 0, (input) => input.setAttribute("step", 1) )} ${createSetting( "Lobby Auto-Start Time", "number", "start_lock", "Specify the maximum amount of time for a lobby to stay open after a player joins. Set to 0 to disable", 0, (input) => input.setAttribute("step", 1) )} ${createSetting( "Show Antibot Timers", "checkbox", "counters", "Display Antibot Counters/Timers (Lobby Auto-Start, Auto-Lock, etc)" )} ${createSetting( "Counter Kahoot! Cheats", "checkbox", "counterCheats", "Adds an additional 5 second question at the end to counter cheats. Changing this mid-game may break the game or will not apply until refresh", null, undefined, () => { windw.antibotData.methods.kahootAlert( "Changes may only take effect upon reload." ); } )} ${createSetting( "Enable CAPTCHA", "checkbox", "enableCAPTCHA", "Adds a 30 second poll at the start of the quiz. If players don't answer it correctly, they get banned. Changing this mid-game may break the game or will not apply until refresh", null, undefined, () => { windw.antibotData.methods.kahootAlert( "Changes may only take effect upon reload." ); } )} ${createSetting( "Reduce False-Positivess", "checkbox", "reduceFalsePositives", "Makes some checks less strict to attempt to reduce the number of false-positives banned." )} </div> </details> </div> <style> #antibotwtr { position: fixed; bottom: 100px; right: 100px; font-size: 1rem; opacity: 0.4; transition: opacity 0.4s; z-index: 5000; background: white; text-align: center; border-radius: 0.5rem; } #antibotwtr summary { text-align: left; } #antibotwtr:hover { opacity: 1; } #antibotwtr p { display: inline-block; } #antibotwtr p:first-child { font-weight: 600; } #antibot-killcount { margin-left: 0.25rem; background: black; border-radius: 0.5rem; color: white; } #antibotwtr details { background: grey; } #antibotwtr input[type="checkbox"] { display: none; } #antibotwtr label { color: black; font-weight: 600; display: block; background: #c60929; border-radius: 0.5rem; height: 100%; word-break: break-word; } #antibotwtr .antibot-input { height: calc(100% - 1.5rem); background: #864cbf; color: white; } #antibotwtr input,textarea { position: absolute; bottom: 0; left: 0; width: 100%; height: 1rem; border-radius: 0.25rem; border: solid 1px black; font-family: "Montserrat", sans-serif; resize: none; } #antibotwtr input:checked+label { background: #26890c; } #antibot-settings { display: flex; flex-wrap: wrap; max-width: 25rem; max-height: 24rem; overflow: auto; } #antibot-settings > div { flex: 1; max-width: 33%; min-width: 33%; min-height: 6rem; box-sizing: border-box; position: relative; border: solid 0.5rem transparent; } #antibot-counters { position: absolute; right: 10rem; top: 11rem; font-size: 1.5rem; font-weight: 700; color: white; pointer-events: none; z-index: 1; } #antibot-counters div { background: rgba(0,0,0,0.5); padding: 0.5rem; border-radius: 0.5rem; margin-bottom: 0.5rem; } .antibot-count-num { display: block; text-align: center; } .antibot-count-desc { text-align: center; font-size: 1.25rem; display: block; } .antibot-textarea { position: fixed; width: 40rem; height: 30rem; margin: auto; left: 0; top: 0; margin-left: calc(50% - 20rem); z-index: 1; font-size: 1.5rem; font-weight: bold; } </style>`; const counters = document.createElement("div"); counters.id = "antibot-counters"; document.body.append(UITemplate.content.cloneNode(true), counters); function makeHttpRequest(url) { const request = new XMLHttpRequest(); request.open("GET", url); request.send(); return new Promise((resolve, reject) => { request.onerror = request.onload = () => { if (request.readyState === 4 && request.status === 200) { resolve(request); } else { reject(request); } }; }); } async function patchLobbyView(url) { const { response } = await makeHttpRequest(url), lobbyRegex = /\w=[a-z]\.startQuiz/, lobbyLetter = response.match(lobbyRegex)[0].match(/[a-z](?=\.)/)[0], patched = response.replace( response.match(lobbyRegex)[0], `${response.match(lobbyRegex)[0]}, ANTIBOT_PATCH = (() => { windw.antibotData.kahootInternals.globalFuncs = ${lobbyLetter}; })()` ), script = document.createElement("script"); script.innerHTML = patched; document.head.append(script); } function capitalize(string) { string = string.toLowerCase(); return string[0].toUpperCase() + string.slice(1); } function similarity(s1, s2) { // remove numbers from name if name is not only a number if (isNaN(s1) && typeof s1 !== "object" && !isUsingNamerator()) { s1 = s1.replace(/[0-9]/gm, ""); } if (isNaN(s2) && typeof s2 !== "object" && !isUsingNamerator()) { s2 = s2.replace(/[0-9]/gm, ""); } if (!s2) { return 0; } // if is a number of the same length if (s1) { if (!isNaN(s2) && !isNaN(s1) && s1.length === s2.length) { return 1; } } // apply namerator rules if (isUsingNamerator()) { if (!isValidNameratorName(s2)) { return -1; } else { // safe name return 0; } } if (!s1) { return; } // ignore case s1 = s1.toLowerCase(); s2 = s2.toLowerCase(); let longer = s1, shorter = s2; // begin math to determine similarity if (s1.length < s2.length) { longer = s2; shorter = s1; } const longerLength = longer.length; if (longerLength === 0) { return 1.0; } return ( (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength) ); } function isValidNameratorName(name) { const First = [ "Adorable", "Agent", "Agile", "Amazing", "Amazon", "Amiable", "Amusing", "Aquatic", "Arctic", "Awesome", "Balanced", "Blue", "Bold", "Brave", "Bright", "Bronze", "Captain", "Caring", "Champion", "Charming", "Cheerful", "Classy", "Clever", "Creative", "Cute", "Dandy", "Daring", "Dazzled", "Decisive", "Diligent", "Diplomat", "Doctor", "Dynamic", "Eager", "Elated", "Epic", "Excited", "Expert", "Fabulous", "Fast", "Fearless", "Flying", "Focused", "Friendly", "Funny", "Fuzzy", "Genius", "Gentle", "Giving", "Glad", "Glowing", "Golden", "Great", "Green", "Groovy", "Happy", "Helpful", "Hero", "Honest", "Inspired", "Jolly", "Joyful", "Kind", "Knowing", "Legend", "Lively", "Lovely", "Lucky", "Magic", "Majestic", "Melodic", "Mighty", "Mountain", "Mystery", "Nimble", "Noble", "Polite", "Power", "Prairie", "Proud", "Purple", "Quick", "Radiant", "Rapid", "Rational", "Rockstar", "Rocky", "Royal", "Shining", "Silly", "Silver", "Smart", "Smiling", "Smooth", "Snowy", "Soaring", "Social", "Space", "Speedy", "Stellar", "Sturdy", "Super", "Swift", "Tropical", "Winged", "Wise", "Witty", "Wonder", "Yellow", "Zany" ], Last = [ "Alpaca", "Ant", "Badger", "Bat", "Bear", "Bee", "Bison", "Boa", "Bobcat", "Buffalo", "Bunny", "Camel", "Cat", "Cheetah", "Chicken", "Condor", "Crab", "Crane", "Deer", "Dingo", "Dog", "Dolphin", "Dove", "Dragon", "Duck", "Eagle", "Echidna", "Egret", "Elephant", "Elk", "Emu", "Falcon", "Ferret", "Finch", "Fox", "Frog", "Gator", "Gazelle", "Gecko", "Giraffe", "Glider", "Gnu", "Goat", "Goose", "Gorilla", "Griffin", "Hamster", "Hare", "Hawk", "Hen", "Horse", "Ibex", "Iguana", "Impala", "Jaguar", "Kitten", "Koala", "Lark", "Lemming", "Lemur", "Leopard", "Lion", "Lizard", "Llama", "Lobster", "Macaw", "Meerkat", "Monkey", "Mouse", "Newt", "Octopus", "Oryx", "Ostrich", "Otter", "Owl", "Panda", "Panther", "Pelican", "Penguin", "Pigeon", "Piranha", "Pony", "Possum", "Puffin", "Quail", "Rabbit", "Raccoon", "Raven", "Rhino", "Rooster", "Sable", "Seal", "SeaLion", "Shark", "Sloth", "Snail", "Sphinx", "Squid", "Stork", "Swan", "Tiger", "Turtle", "Unicorn", "Urchin", "Wallaby", "Wildcat", "Wolf", "Wombat", "Yak", "Yeti", "Zebra" ], F = name.match(/[A-Z][a-z]+(?=[A-Z])/); if (F === null || !First.includes(F[0])) { return false; } const L = name.replace(F[0], ""); if (!Last.includes(L)) { return false; } return true; } function isFakeValid(name) { if (!windw.isUsingNamerator && isValidNameratorName(name)) { return true; } if (getSetting("blocknum") && /\d/.test(name)) { return true; } if (getSetting("forceascii") && /[^\d\s\w_-]/.test(name)) { return true; } return /(^([A-Z][a-z]+){2,3}\d{1,2}$)|(^([A-Z][^A-Z\n]+?)+?(\d[a-z]+\d*?)$)|(^[a-zA-Z]+\d{4,}$)/.test( name ); } function editDistance(s1, s2) { s1 = s1.toLowerCase(); s2 = s2.toLowerCase(); const costs = new Array(); for (let i = 0; i <= s1.length; i++) { let lastValue = i; for (let j = 0; j <= s2.length; j++) { if (i === 0) { costs[j] = j; } else { if (j > 0) { let newValue = costs[j - 1]; if (s1.charAt(i - 1) != s2.charAt(j - 1)) { newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1; } costs[j - 1] = lastValue; lastValue = newValue; } } } if (i > 0) { costs[s2.length] = lastValue; } } return costs[s2.length]; } function getPatterns(string) { const isLetter = (char) => { return /\p{L}/u.test(char); }, isUppercaseLetter = (char) => { return char.toUpperCase() === char; }, isNumber = (char) => { return /\p{N}/u.test(char); }; let output = "", mode = null, count = 0; for (let i = 0; i < string.length; i++) { const char = string[i]; let type = null; if (isLetter(char)) { if (isUppercaseLetter(char)) { type = "C"; } else { type = "L"; } } else if (isNumber(char)) { type = "D"; } else { // special character type = "U"; } if (type !== mode) { if (mode !== null) { output += Math.floor(count / 3); } count = 0; mode = type; output += type; } else { count++; if (i === string.length - 1) { output += Math.floor(count / 3); } } } return output; } function blacklist(name) { const list = getSetting("wordblock", []); for (let i = 0; i < list.length; i++) { if (list[i] === "") { continue; } if (name.toLowerCase().indexOf(list[i].toLowerCase()) !== -1) { return true; } } return false; } function getKahootSetting(id) { try { return antibotData.kahootInternals.kahootCore.game.core.gameSettings .gameOptions[id]; } catch (e) { try { return ( antibotData.kahootInternals.gameOptions.getGameOptions().optionsState[ id ].on ?? antibotData.kahootInternals.gameOptions.getGameOptions().optionsState[ id ] ); } catch (e) { try { return ( antibotData.kahootInternals.kahootCore.game.options.optionsState[ id ] ?? false ); } catch (e) { return false; } } } } function getSetting(id, def) { if (typeof antibotData.settings[id] !== "undefined") { return antibotData.settings[id]; } const elem = document.getElementById(`antibot.config.${id}`); if (elem.value === "") { if (elem.nodeName === "TEXTAREA") { return def ?? []; } if (elem.type === "checkbox") { return def ?? false; } if (elem.type === "number") { return def ?? 0; } return def ?? ""; } else { return elem.type === "checkbox" ? elem.checked : elem.nodeName === "TEXTAREA" ? elem.value.split("\n") : elem.type === "number" ? +elem.value : elem.value; } } function setSetting(id, value) { const elem = document.getElementById(`antibot.config.${id}`); if (elem.type === "checkbox") { value = !!value; elem.checked = value; } else if (Array.isArray(value)) { elem.value = value.join("\n"); } else if (elem.type === "number") { value = +value; elem.value = value; } else { value = `${value}`; elem.value = value; } // in case of certain things if (elem.nodeName === "TEXTAREA" && typeof value === "string") { value = value.split("\n"); } const localConfig = JSON.parse(windw.localStorage.antibotConfig || "{}"); localConfig[id] = value; windw.localStorage.antibotConfig = JSON.stringify(localConfig); antibotData.settings[id] = value; } function extraQuestionSetup(quiz) { if (getSetting("counterCheats")) { quiz.questions.push({ question: "[ANTIBOT] - This poll is for countering Kahoot cheating sites.", time: 5000, type: "survey", isAntibotQuestion: true, choices: [{ answer: "OK", correct: true }] }); } if (getSetting("enableCAPTCHA")) { const answers = ["red", "blue", "yellow", "green"], images = [ "361bdde0-48cd-4a92-ae9f-486263ba8529", // red "9237bdd2-f281-4f04-b4e5-255e9055a194", // blue "d25c9d13-4147-4056-a722-e2a13fbb4af9", // yellow "2aca62f2-ead5-4197-9c63-34da0400703a" // green ], imageIndex = Math.floor(Math.random() * answers.length); quiz.questions.splice(0, 0, { question: `[ANTIBOT] - CAPTCHA: Please select ${answers[imageIndex]}`, time: 30000, type: "quiz", isAntibotQuestion: true, AntibotCaptchaCorrectIndex: imageIndex, choices: [ { answer: "OK" }, { answer: "OK" }, { answer: "OK" }, { answer: "OK" } ], image: "https://media.kahoot.it/" + images[imageIndex], imageMetadata: { width: 512, height: 512, id: images[imageIndex], contentType: "image/png", resources: "" }, points: false }); } } function kahootAlert(notice) { // specialData.globalFuncs.showNotificationBar("error" or "notice", {defaultMessage: "the notice message", id:"antibot.notice"}, time (s), center (true/false, centers text), values (??), upsellhandler (?? function)); try { antibotData.kahootInternals.globalFuncs.showNotificationBar( "error", { defaultMessage: notice, id: "antibot.notice" }, 3 ); } catch (err) { // fall back to alert alert(notice); } } function kickController(id, reason = "", fallbackController) { const controller = getControllerById(id) ?? fallbackController, name = controller?.name?.length > 30 ? controller.name.substr(0, 30) + "..." : controller?.name, banishedCachedData = antibotData.runtimeData.unverifiedControllerNames.find((controller) => { return controller.cid === id; }); console.warn( `[ANTIBOT] - Kicked ${name || id}${reason ? ` - ${reason}` : ""}` ); sendData("/service/player", { cid: `${id}`, content: JSON.stringify({ kickCode: 1, quizType: "quiz" }), gameid: getPin(), host: "play.kahoot.it", id: 10, type: "message" }); antibotData.runtimeData.killCount++; if (banishedCachedData) { banishedCachedData.banned = true; banishedCachedData.time = 10; } // Removed to reduce the amount of memory consumed. // if (controller) {antibotData.kahootInternals.kahootCore.game.core.kickedControllers.push(controller);} delete getControllers()[id]; delete antibotData.runtimeData.controllerData[id]; } function isEventJoinEvent(event) { return event.data?.type === "joined"; } function isEventAnswerEvent(event) { return event.data?.id === 45; } function isEventTwoFactorEvent(event) { return event.data?.id === 50; } function isEventTeamJoinEvent(event) { return event.data?.id === 18; } const sendChecks = [ function questionStartCheck(socket, data) { if (data?.data?.id === 2) { antibotData.runtimeData.questionStartTime = Date.now(); antibotData.runtimeData.captchaIds = new Set(); } }, function restartCheck(socket, data) { if ( data?.data?.id === 5 || (data?.data?.id === 10 && data.data.content === "{}") ) { antibotData.runtimeData.lobbyLoadTime = 0; const shouldResetData = getKahootSetting("requireRejoin"); if (shouldResetData) { Object.assign(antibotData.runtimeData, { controllerData: {}, captchaIds: new Set(), englishWordDetectionData: new Set(), controllerNamePatternData: {}, verifiedControllerNames: new Set(), unverifiedControllerNames: [] }); } } }, function quizStartCheck(socket, data) { if (data?.data?.id === 9 && antibotData.runtimeData.startLockElement) { clearInterval(antibotData.runtimeData.startLockInterval); antibotData.runtimeData.startLockElement.remove(); antibotData.runtimeData.startLockElement = null; } }, function questionEndCheck(socket, data) { if ( data?.data?.id === 4 && getCurrentQuestionIndex() === 0 && getQuizData().questions[0].isAntibotQuestion ) { const controllers = getControllers(), answeredControllers = antibotData.runtimeData.captchaIds; batchData(() => { for (const id in controllers) { if (controllers[id].isGhost || controllers[id].hasLeft) { continue; } if (!answeredControllers.has(id)) { kickController(id, "Did not answer the CAPTCHA"); } } }); } } ], receiveChecks = [ function ddosCheck() { if ( !isLocked() && !antibotData.runtimeData.lockingGame && getSetting("ddos", 0) && antibotData.runtimeData.killCount - antibotData.runtimeData.oldKillCount > getSetting("ddos", 0) / 3 ) { lockGame(); console.error( "[ANTIBOT] - Detected bot spam, locking game for 1 minute" ); const lockEnforcingInterval = setInterval(() => { if (isLocked()) { clearInterval(lockEnforcingInterval); antibotData.runtimeData.lockingGame = false; } lockGame(); }, 250); if (getSetting("counters")) { const ddosCounterElement = document.createElement("div"); let timeLeft = 60; ddosCounterElement.innerHTML = ` <span class="antibot-count-num">60</span> <span class="antibot-count-desc">Until Unlock</span>`; counters.append(ddosCounterElement); const ddosCounterInterval = setInterval(() => { ddosCounterElement.querySelector(".antibot-count-num").innerHTML = --timeLeft; if (timeLeft <= 0) { clearInterval(ddosCounterInterval); ddosCounterElement.remove(); } }, 1e3); } setTimeout(unlockGame, 60e3); } }, function basicDataCheck(socket, data) { if (!isEventJoinEvent(data)) { return; } const player = data.data; if ( isNaN(player.cid) || Object.keys(player).length > 5 || player.name.length >= 16 ) { if (antibotData.runtimeData.controllerData[player.cid]) { return; } kickController(player.cid, "Invalid name or information", player); throw new EvilBotJoinedError(); } }, function firstClientNameratorCheck(socket, data) { if (!isEventJoinEvent(data)) { return; } const player = data.data; if (antibotData.runtimeData.unverifiedControllerNames.length === 0) { if (similarity(null, player.name) === -1) { kickController(player.cid, "Name violates namerator rules", player); throw new EvilBotJoinedError(); } } }, function nameSimilarityCheck(socket, data) { if (!isEventJoinEvent(data)) { return; } const player = data.data, usernames = antibotData.runtimeData.unverifiedControllerNames; if (similarity(null, player.name) === -1) { kickController(player.cid, "Name violates namerator rules"); throw new EvilBotJoinedError(); } for (const i in usernames) { if ( antibotData.runtimeData.verifiedControllerNames.has( usernames[i].name ) ) { continue; } if ( similarity(usernames[i].name, player.name) >= getSetting("percent", 0.6) ) { batchData(() => { kickController( player.cid, "Name similar to other clients", player ); if (!usernames[i].banned) { kickController( usernames[i].cid, "Name similar to other clients", usernames[i] ); } }); throw new EvilBotJoinedError(); } } }, function blacklistCheck(socket, data) { if (!isEventJoinEvent(data)) { return; } const player = data.data; if (blacklist(player.name)) { kickController(player.cid, "Name is blacklisted", player); throw new EvilBotJoinedError(); } }, function addNameIfNotBanned(socket, data) { if (!isEventJoinEvent(data)) { return; } const player = data.data; antibotData.runtimeData.unverifiedControllerNames.push({ name: player.name, cid: player.cid, time: 10, banned: false }); }, function patternSimilarityCheck(socket, data) { if ( !isEventJoinEvent(data) || isUsingNamerator() || !getSetting("patterns") ) { return; } const player = data.data, pattern = getPatterns(player.name), patternData = antibotData.runtimeData.controllerNamePatternData; if (getSetting("reduceFalsePositives")) { if (pattern[0] === "L") { if (!isNaN(pattern.slice(1))) { return; } } } if (typeof patternData[pattern] === "undefined") { patternData[pattern] = new Set(); } patternData[pattern].add({ playerData: player, timeAdded: Date.now() }); const PATTERN_SIZE_TEST = 15, PATTERN_REMOVE_TIME = 5e3; // remove removable controller data for (const controller of patternData[pattern]) { if (Date.now() - controller.timeAdded > PATTERN_REMOVE_TIME) { patternData[pattern].delete(controller); } } if (patternData[pattern].size >= PATTERN_SIZE_TEST) { batchData(() => { for (const controller of patternData[pattern]) { if (controller.playerData.banned) { continue; } kickController( controller.playerData.cid, "Names have very similar patterns", controller.playerData ); if (patternData[pattern].size >= PATTERN_SIZE_TEST + 10) { patternData[pattern].delete(controller); } else { controller.playerData.banned = true; controller.timeAdded = Date.now(); // updates the 'time added' to current time, since the spam is still ongoing } } }); throw new EvilBotJoinedError(); } }, function randomNameCheck(socket, data) { if (!isEventJoinEvent(data) || !getSetting("looksRandom")) { return; } const player = data.data, randomRegex = /(^(([^A-Z\n]*)?[A-Z]?([^A-Z\n]*)?){0,3}$)|^([A-Z]*)$/; if (!randomRegex.test(player.name)) { kickController(player.cid, "Name looks too random", player); throw new EvilBotJoinedError(); } }, function commonBotFormatCheck1(socket, data) { if (!isEventJoinEvent(data) || !getSetting("blockformat1")) { return; } const player = data.data; if (/[a-z0-9]+[^a-z0-9\s][a-z0-9]+/gi.test(player.name)) { kickController(player.cid, "Name fits common bot format #1", player); throw new EvilBotJoinedError(); } }, function specializedFormatCheck(socket, data) { if (!isEventJoinEvent(data) || !getSetting("blockservice1")) { return; } const player = data.data, englishWords = windw.aSetOfEnglishWords ?? new Set(), names = windw.randomName, split = player.name.split(/\s|(?=[A-Z0-9])/g), foundNames = Array.from( player.name.match(/([A-Z][a-z]+(?=[A-Z]|[^a-zA-Z]|$))/g) ?? [] ), detectionData = antibotData.runtimeData.englishWordDetectionData; if ( player.name.replace(/[ᗩᗷᑕᗪEᖴGᕼIᒍKᒪᗰᑎOᑭᑫᖇᔕTᑌᐯᗯ᙭Yᘔ]/g, "").length / player.name.length < 0.5 ) { kickController(player.cid, "Common bot bypass attempt", player); throw new EvilBotJoinedError(); } let findWord, findName; if (getSetting("reduceFalsePositives") && split.length > 1) { findWord = split.every( (word) => englishWords.has(word) || !isNaN(word) ); findName = split.every((word) => { if (!names) { return; } const name = capitalize(word); return ( names.first.has(name) || names.middle.has(name) || names.last.has(name) || !isNaN(word) ); }); } else { findWord = split.find((word) => englishWords.has(word)); findName = foundNames.find((word) => { if (!names) { return; } const name = capitalize(word); return ( names.first.has(name) || names.middle.has(name) || names.last.has(name) ); }); } const TOTAL_SPAM_AMOUNT_THRESHOLD = 20, TIME_TO_FORGET = 4e3; if (findWord || findName) { detectionData.add({ playerData: player, timeAdded: Date.now() }); for (const controller of detectionData) { if (Date.now() - controller.timeAdded > TIME_TO_FORGET) { detectionData.delete(controller); } } if (detectionData.size > TOTAL_SPAM_AMOUNT_THRESHOLD) { batchData(() => { for (const controller of detectionData) { if (controller.playerData.banned) { continue; } kickController( controller.playerData.cid, "Appears to be a spam of randomized names", controller.playerData ); if (detectionData.size >= TOTAL_SPAM_AMOUNT_THRESHOLD + 10) { detectionData.delete(controller); } else { controller.playerData.banned = true; controller.timeAdded = Date.now(); } } }); throw new EvilBotJoinedError(); } } }, function fakeValidNameCheck(socket, data) { if (!isEventJoinEvent(data) || isUsingNamerator()) { return; } const player = data.data, TIME_THRESHOLD = 5e3; if (isFakeValid(player.name)) { if ( Date.now() - antibotData.runtimeData.lastFakeLoginTime < TIME_THRESHOLD ) { batchData(() => { kickController( player.cid, "Uses a suspicious fake, 'valid' name" ); const previous = getControllerById( antibotData.runtimeData.lastFakeUserID ); if (previous) { kickController( previous.cid, "Uses a suspicious fake, 'valid' name", player ); } }); antibotData.runtimeData.lastFakeLoginTime = Date.now(); antibotData.runtimeData.lastFakeUserID = player.cid; throw new EvilBotJoinedError(); } antibotData.runtimeData.lastFakeLoginTime = Date.now(); antibotData.runtimeData.lastFakeUserID = player.cid; } }, function fastAnswerCheck(socket, data) { if (!isEventAnswerEvent(data)) { return; } const player = data.data, controllerData = antibotData.runtimeData.controllerData[player.cid]; if ( getCurrentQuestionIndex() === 0 && getQuizData().questions[0].isAntibotQuestion ) { antibotData.runtimeData.captchaIds.add(player.cid); let choice = -1; try { choice = JSON.parse(player.content).choice; } catch (err) { /* ignore */ } if ( choice !== getQuizData().questions[0].AntibotCaptchaCorrectIndex ) { kickController( player.cid, "Incorrectly answered the CAPTCHA", player ); return; } } if ( Date.now() - antibotData.runtimeData.questionStartTime < 500 && getSetting("timeout") ) { throw new AnsweredTooQuicklyError(); } if (controllerData && Date.now() - controllerData.loginTime < 1e3) { kickController( player.cid, "Answered immediately after joining!", player ); throw new AnsweredTooQuicklyError(); } }, function twoFactorCheck(socket, data) { if (!isEventTwoFactorEvent(data)) { return; } const player = data.data, controllerData = antibotData.runtimeData.controllerData[player.cid], MAX_ATTEMPTS = 3; if (controllerData) { controllerData.twoFactorAttempts++; if (controllerData.twoFactorAttempts > MAX_ATTEMPTS) { kickController( player.cid, "Attempted to answer the two-factor code using brute force", player ); } } }, function teamCheck(socket, data) { if (!isEventTeamJoinEvent(data)) { return; } const player = data.data, team = JSON.parse(player.content); if ( team.length === 0 || team.indexOf("") !== -1 || team.indexOf("Player 1") === 0 || team.join("") === "Youjustgotbotted" ) { kickController(player.cid, "Team names are suspicious", player); throw new EvilBotJoinedError(); } }, function lobbyAutoStartCheck(socket, data) { if (!isEventJoinEvent(data)) { return; } if ( antibotData.kahootInternals.kahootCore.game.navigation.page === "lobby" && getKahootSetting("automaticallyProgressGame") && getSetting("start_lock", 0) !== 0 ) { if (antibotData.runtimeData.lobbyLoadTime === 0) { antibotData.runtimeData.lobbyLoadTime = Date.now(); if (getSetting("counters")) { const container = document.createElement("div"); container.innerHTML = `<span class="antibot-count-num">${Math.round( getSetting("start_lock", 0) - (Date.now() - antibotData.runtimeData.lobbyLoadTime) / 1e3 )}</span> <span class="antibot-count-desc">Until Auto-Start</span>`; const startLockInterval = setInterval(() => { let time = Math.round( getSetting("start_lock", 0) - (Date.now() - antibotData.runtimeData.lobbyLoadTime) / 1e3 ); if (time < 0) { time = "Please Wait..."; } container.querySelector(".antibot-count-num").innerHTML = time; }, 1e3); counters.append(container); antibotData.runtimeData.startLockElement = container; antibotData.runtimeData.startLockInterval = startLockInterval; } } if ( Date.now() - antibotData.runtimeData.lobbyLoadTime > getSetting("start_lock", 0) * 1e3 ) { const controllers = getControllers(), realController = Object.values(controllers).find((controller) => { return !controller.isGhost && !controller.hasLeft; }); if (!realController) { antibotData.runtimeData.lobbyLoadTime = Date.now(); } else { antibotData.kahootInternals.globalFuncs.startQuiz(); if (antibotData.runtimeData.startLockElement) { clearInterval(antibotData.runtimeData.startLockInterval); antibotData.runtimeData.startLockElement.remove(); antibotData.runtimeData.startLockElement = null; } } } } } ]; function batchData(callback) { return antibotData.kahootInternals.kahootCore.network.websocketInstance.batch( callback ); } function lockGame() { antibotData.runtimeData.lockingGame = true; sendData("/service/player", { gameid: getPin(), type: "lock" }); } function unlockGame() { sendData("/service/player", { gameid: getPin(), type: "unlock" }); } function isLocked() { return antibotData.kahootInternals.kahootCore.game.core.isLocked; } function isUsingNamerator() { return getKahootSetting("namerator"); } function getCurrentQuestionIndex() { return antibotData.kahootInternals.kahootCore.game.navigation .currentGameBlockIndex; } function getQuizData() { return antibotData.kahootInternals.globalQuizData; } function getPin() { return antibotData.kahootInternals.kahootCore.game.core.pin; } function getControllerById(id) { return getControllers()[id]; } function getControllers() { return antibotData.kahootInternals.kahootCore.game.core.controllers; } function sendData(channel, data) { return antibotData.kahootInternals.kahootCore.network.websocketInstance.publish( channel, data ); } function websocketMessageHandler(socket, message) { try { PinCheckerCheckMethod(socket, message); } catch (err) { console.error(`[ANTIBOT] - Execution of PIN-CHECKER Failed: ${err}`); } antibotData.kahootInternals.socket = socket; if (!socket.webSocket.oldSend) { socket.webSocket.oldSend = socket.webSocket.send; socket.webSocket.send = function (data) { websocketSendMessageHandler(socket, data); socket.webSocket.oldSend(data); }; } const data = JSON.parse(message.data)[0]; for (const check of receiveChecks) { check(socket, data); } // if we get here, no errors thrown = bot not banned if (data.data?.type !== "joined") { return; } antibotData.runtimeData.controllerData[data.data.cid] = { loginTime: Date.now(), twoFactorAttempts: 0 }; } function websocketSendMessageHandler(socket, data) { data = JSON.parse(data)[0]; for (const check of sendChecks) { check(socket, data); } } const killCountElement = document.querySelector("#antibot-killcount"), antibotData = (windw.antibotData = { isUsingNamerator: false, methods: { websocketMessageHandler, extraQuestionSetup, kahootAlert, getSetting, setSetting, patchLobbyView, getKahootSetting }, settings: {}, runtimeData: { killCount: 0, oldKillCount: 0, controllerData: {}, verifiedControllerNames: new Set(), unverifiedControllerNames: [], controllerNamePatternData: {}, englishWordDetectionData: new Set(), lastFakeLoginTime: 0, lastFakeUserID: null, captchaIds: new Set(), questionStartTime: 0, lobbyLoadTime: 0, startLockElement: null, startLockInterval: null }, kahootInternals: {} }), localConfig = JSON.parse(windw.localStorage.antibotConfig || "{}"); for (const setting in localConfig) { try { const current = getSetting(setting); setSetting(setting, localConfig[setting] ?? current); } catch (err) { /* ignored */ } } setInterval(function updateStats() { killCountElement.innerHTML = antibotData.runtimeData.killCount; const unverifiedControllerNames = antibotData.runtimeData.unverifiedControllerNames, verifiedControllerNames = antibotData.runtimeData.verifiedControllerNames; for (const i in unverifiedControllerNames) { const data = unverifiedControllerNames[i]; if ( data.time <= 0 && !data.banned && !verifiedControllerNames.has(data.name) ) { verifiedControllerNames.add(data.name); continue; } if (data.time <= -20) { unverifiedControllerNames.splice(i, 1); continue; } data.time--; } }, 1e3); setInterval(function updateTwoFactorAuthInfo() { const controllerData = antibotData.runtimeData.controllerData; for (const cid in controllerData) { controllerData[cid].tries = 0; } }, 10e3); setInterval(function updateOldKillCount() { antibotData.runtimeData.oldKillCount = antibotData.runtimeData.killCount; }, 20e3); let PinCheckerCheckMethod = () => {}; try { if (windw.localStorage.extraCheck2) { PinCheckerCheckMethod = new Function( "return " + windw.localStorage.extraCheck2 )(); } } catch (err) { console.warn( "PIN-CHECKER Load ERR.\nThis is probably due to PIN-CHECKER not installed.\nThis warning can be ignored." ); console.warn(err); } try { new Function("return " + windw.localStorage.kahootThemeScript)()(); } catch (err) { console.warn( "Kahoot Theme Load ERR.\nThis is probably due to KAHOOT-THEME not installed.\nThis warning can be ignored." ); console.warn(err); } try { new Function("return " + windw.localStorage.extraCheck)()(); } catch (err) { console.warn( "PIN-CHECKER #2 Load ERR.\nThis is probably due to PIN-CHECKER not installed.\nThis warning can be ignored." ); console.warn(err); } // remove local storage functions, run external scripts delete localStorage.kahootThemeScript; delete localStorage.extraCheck; delete localStorage.extraCheck2; for (let i = 0; i < windw.antibotAdditionalScripts.length; i++) { try { Function( "return (" + windw.antibotAdditionalScripts[i].toString() + ")();" )(); } catch (err) { console.error(err); } } }; (async () => { try { console.log("[ANTIBOT] - loading"); // To prevent race condition issues. await writePromise; const { page, mainScriptURL } = await fetchMainPage(), patchedMainScript = await fetchMainScript(mainScriptURL), externalScripts = await Promise.all( requiredAssets.map((assetURL) => makeHttpRequest(assetURL).catch(() => "") ) ).then((data) => data .map( (result) => `<script data-antibot="external-script">${result.response}</script>` ) .join("") ), mainBlobURL = createBlobURL(patchedMainScript); let completePage = page.split("</body>"); completePage = `${completePage[0]} <p id="antibotpatchwait">patching completed successfully... please wait...</p> <script data-antibot="main"> import("${mainBlobURL}").then(() => { document.querySelector("#antibotpatchwait").remove(); }).catch((err) => { console.error(err); const template = document.createElement("template"), errorNotice = document.createElement("h3"); errorNotice.textContent = "Error while loading patched Kahoot!:"; template.innerHTML = \` <a href="${mainBlobURL}" download>Download Patched Code</a><br> \${err.stack.replace(/\\n/g, "<br/>")} \`; document.body.append( errorNotice, template.content.cloneNode(true) ); }); </script> <script data-antibot="fire-loader"> window.parent.fireLoaded = window.fireLoaded = true; (${kantibotProgramCode.toString()})(); </script> ${externalScripts} <script data-antibot="external-loader"> window.parent.aSetOfEnglishWords = window.aSetOfEnglishWords; window.parent.randomName = window.randomName; </script>`; console.log("[ANTIBOT] - loaded"); document.open(); document.write(`<style> body { margin: 0; } iframe { border: 0; width: 100%; height: 100%; } </style> <iframe src="about:blank"></iframe>`); document.close(); window.stop(); importBlobURLs[mainScriptURL] = mainBlobURL; window.kantibotImport = antibotImport; window.kantibotMakeHTTPRequest = makeHttpRequest; window.kantibotCreateBlobURL = createBlobURL; const doc = document.querySelector("iframe"); doc.contentDocument.write(completePage); document.title = doc.contentDocument.title; doc.addEventListener("load", () => { window.location.reload(); }); } catch (err) { console.error(err); document.write( '<h3 style="color: red">An error occured while patching Kahoot!:</h3>' ); document.write(err.stack.replace(/\n/g, "<br/>")); } })();