// ==UserScript==
// @name Kahoot AntiBot
// @namespace http://tampermonkey.net/
// @version 2.14.4
// @icon https://cdn.discordapp.com/icons/641133408205930506/31c023710d468520708d6defb32a89bc.png
// @description Remove all bots from a kahoot game.
// @author theusaf
// @copyright 2018-2021, Daniel Lau (https://github.com/theusaf/kahoot-antibot)
// @match *://play.kahoot.it/*
// @exclude *://play.kahoot.it/v2/assets/*
// @grant none
// @run-at document-start
// @license MIT; https://opensource.org/licenses/MIT
// ==/UserScript==
if(window.fireLoaded || (window.parent && window.parent.page)){
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");
}
document.write("[ANTIBOT] - Patching Kahoot. Please wait. If this screen stays blank for long periods of time, please force reload or clear your cache.");
window.url = window.location.href;
window.page = new XMLHttpRequest();
window.page.open("GET",window.url);
window.page.send();
window.page.onload = ()=>{
const scriptURL = window.page.response.match(/><\/script><script .*?vendors.*?><\/script>/mg)[0].substr(9).split("src=\"")[1].split("\"")[0],
script2 = window.page.response.match(/\/v2\/assets\/js\/main.*?(?=")/mg)[0];
let originalPage = window.page.response.replace(/><\/script><script .*?vendors.*?><\/script>/mg,"></script>");
originalPage = originalPage.replace(/\/v2\/assets\/js\/main.*?(?=")/mg,"data:text/javascript,");
const script = new XMLHttpRequest();
script.open("GET","https://play.kahoot.it/"+scriptURL);
script.send();
script.onload = ()=>{
const patchedScriptRegex = /\.onMessage=function\([a-z],[a-z]\)\{/mg,
letter1 = script.response.match(patchedScriptRegex)[0].match(/[a-z](?=,)/g)[0],
letter2 = script.response.match(patchedScriptRegex)[0].match(/[a-z](?=\))/g)[0],
patchedScript = script.response.replace(script.response.match(patchedScriptRegex)[0],`.onMessage=function(${letter1},${letter2}){window.globalMessageListener(${letter1},${letter2});`),
code = ()=>{
const windw = window.parent;
window.windw = windw;
// create watermark
const container = document.createElement("div");
container.id = "antibotwtr";
const waterMark = document.createElement("p");
waterMark.innerHTML = "v2.14.4 @theusaf";
const botText = document.createElement("p");
botText.innerHTML = "0";
botText.id = "killcount";
const menu = document.createElement("details");
menu.innerHTML = `<summary>config</summary>
<div id="antibot-settings">
<!-- Timeout -->
<div>
<input type="checkbox" id="antibot.config.timeout"></input>
<label id="antibot.config.timeoutlbl" onclick="windw.specialData.config.timeout = !windw.specialData.config.timeout;if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.timeout = windw.specialData.config.timeout;windw.localStorage.antibotConfig = JSON.stringify(a);" for="antibot.config.timeout" title="Blocks answers that are sent before 0.5 seconds after the question starts">Min Answer Timeout</label>
</div>
<!-- Random Names -->
<div>
<input type="checkbox" id="antibot.config.looksRandom" checked="checked"></input>
<label id="antibot.config.lookrandlbl" onclick="windw.specialData.config.looksRandom = !windw.specialData.config.looksRandom;if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.looksRandom = windw.specialData.config.looksRandom;windw.localStorage.antibotConfig = JSON.stringify(a);" for="antibot.config.looksRandom" title="Blocks names that seem 'random', such as 'OmEGaboOt'">Block Random Names</label>
</div>
<!-- Blocking Format 1 -->
<div>
<input type="checkbox" id="antibot.config.blockformat1" checked="checked"></input>
<label id="antibot.config.blockformat1lbl" onclick="windw.specialData.config.banFormat1 = !windw.specialData.config.banFormat1;if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.banFormat1 = windw.specialData.config.banFormat1;windw.localStorage.antibotConfig = JSON.stringify(a);" for="antibot.config.blockformat1" title="Blocks names using the format [First][random char][Last]">Block format First[._-,etc]Last</label>
</div>
<!-- Blocking kahootflood.weebly.com -->
<div>
<input type="checkbox" id="antibot.config.blockservice1"></input>
<label onclick="windw.specialData.config.blockservice1 = !windw.specialData.config.blockservice1;if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.blockservice1 = windw.specialData.config.blockservice1;windw.localStorage.antibotConfig = JSON.stringify(a);" for="antibot.config.blockservice1" title="A special filter focused on kahootflood.weebly.com">Block kahootflood.weebly.com</label>
</div>
<!-- Block Numbers -->
<div>
<input type="checkbox" id="antibot.config.blocknum"></input>
<label onclick="windw.specialData.config.blocknum = !windw.specialData.config.blocknum;if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.blocknum = windw.specialData.config.blocknum;windw.localStorage.antibotConfig = JSON.stringify(a);" for="antibot.config.blocknum" title="Marks names with numbers as suspicious. If multiple players join with numbers in their name in a short amount of time, they will be banned.">Block Numbers</label>
</div>
<!-- Block Non-Ascii -->
<div>
<input type="checkbox" id="antibot.config.forceascii"></input>
<label onclick="windw.specialData.config.forceascii = !windw.specialData.config.forceascii;if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.forceascii = windw.specialData.config.forceascii;windw.localStorage.antibotConfig = JSON.stringify(a);" for="antibot.config.forceascii" title="Marks names with non-alphanumeric characters as suspicious and bans them if multiple join.">Force Alphanumeric</label>
</div>
<!-- Additional Question Time -->
<div>
<label class="antibot-input" for="antibot.config.teamtimeout" title="Add extra seconds to the question.">Additional Question Time</label>
<input type="number" step="1" value="0" id="antibot.config.teamtimeout" onchange="windw.specialData.config.additionalQuestionTime = Number(document.getElementById('antibot.config.teamtimeout').value);if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.teamtime = windw.specialData.config.additionalQuestionTime;windw.localStorage.antibotConfig = JSON.stringify(a);">
</div>
<!-- Percent -->
<div>
<label class="antibot-input" for="antibot.config.percent" title="Specify the match percentage.">Match Percent</label>
<input type="number" step="0.1" value="0.6" id="antibot.config.percent" onchange="windw.specialData.config.percent = Number(document.getElementById('antibot.config.percent').value);if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.percent = windw.specialData.config.percent;windw.localStorage.antibotConfig = JSON.stringify(a);">
</div>
<!-- DDOS -->
<div>
<label class="antibot-input" for="antibot.config.ddos" title="Specify the number of bots/minute to lock the game. Set it to 0 to disable.">Auto Lock Threshold</label>
<input type="number" step="1" value="0" id="antibot.config.ddos" onchange="windw.specialData.config.ddos = Number(document.getElementById('antibot.config.ddos').value);if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.ddos = windw.specialData.config.ddos;windw.localStorage.antibotConfig = JSON.stringify(a);">
</div>
<!-- Auto-Start-Lock -->
<div>
<label class="antibot-input" for="antibot.config.start_lock" title="Specify the maximum time in seconds for a lobby to stay open after a player joins. Setting this to 0 will disable it.">Lobby Auto-Start Time</label>
<input type="number" step="1" value="0" id="antibot.config.start_lock" onchange="windw.specialData.config.start_lock = Number(document.getElementById('antibot.config.start_lock').value);if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.start_lock = windw.specialData.config.start_lock;windw.localStorage.antibotConfig = JSON.stringify(a);">
</div>
<!-- Toggling Streak Bonus -->
<div>
<input type="checkbox" id="antibot.config.streakBonus" onchange="windw.specialData.config.streakBonus = Number(document.getElementById('antibot.config.streakBonus').checked ? 1 : 2);if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.streakBonus = windw.specialData.config.streakBonus;localStorage.antibotConfig = JSON.stringify(a);alert('When modifying this option, reload the page for it to take effect')">
<label for="antibot.config.streakBonus" title="Toggle the Streak Bonus.">Toggle Streak Bonus</label>
</div>
<!-- Show Antibot Counters -->
<div>
<input type="checkbox" id="antibot.config.counters" onchange="windw.specialData.config.counters = document.getElementById('antibot.config.counters').checked;if(!windw.localStorage.antibotConfig){windw.localStorage.antibotConfig = JSON.stringify({});}const a = JSON.parse(windw.localStorage.antibotConfig);a.counters = windw.specialData.config.counters;localStorage.antibotConfig = JSON.stringify(a);">
<label for="antibot.config.counters" title="Shows Antibot Countdowns (Lobby Auto-Start/Auto-Lock)">Show Antibot Timers</label>
</div>
<!-- Counter cheats -->
<div>
<input type="checkbox" id="antibot.config.counterCheats" onchange="windw.specialData.config.counterCheats = document.getElementById('antibot.config.counterCheats').checked;
if(!windw.localStorage.antibotConfig){
windw.localStorage.antibotConfig = JSON.stringify({});
}
const a = JSON.parse(windw.localStorage.antibotConfig);
a.counterCheats = windw.specialData.config.counterCheats;
localStorage.antibotConfig = JSON.stringify(a);
if(a.counterCheats){
// enable cheats
alert('Changes may only take effect upon reload.');
}else{
// disable anti-cheat
const q = windw.specialData.globalQuizData.questions;
if(q[q.length - 1].isAntibotQuestion){
q.splice(-1,1);
delete windw.specialData.kahootCore.game.navigation.questionIndexMap[q.length];
}
}">
<label for="antibot.config.counterCheats" title="Adds an additional 5 second question at the end to counter cheats. Note: Changing this mid-game may break the game.">Counter Kahoot Cheats</label>
</div>
<!-- CAPTCHA -->
<div>
<input type="checkbox" id="antibot.config.enableCAPTCHA" onchange="windw.specialData.config.enableCAPTCHA = document.getElementById('antibot.config.enableCAPTCHA').checked;
if(!windw.localStorage.antibotConfig){
windw.localStorage.antibotConfig = JSON.stringify({});
}
const a = JSON.parse(windw.localStorage.antibotConfig);
a.enableCAPTCHA = windw.specialData.config.enableCAPTCHA;
localStorage.antibotConfig = JSON.stringify(a);
if(a.enableCAPTCHA){
// enable captcha
alert('Changes may only take effect upon reload.');
}else{
// disable captcha
const q = windw.specialData.globalQuizData.questions;
if(q[0].isAntibotQuestion){
q.splice(0,1);
delete windw.specialData.kahootCore.game.navigation.questionIndexMap[q.length];
}
}">
<label for="antibot.config.enableCAPTCHA" title="Adds a 30 second poll at the start of the quiz. If players don't answer it correctly, they get banned. Note: Changing this mid-game may break the game.">Enable CAPTCHA</label>
</div>
</div>`;
const counters = document.createElement("div");
counters.id = "antibot-counters";
const styles = document.createElement("style");
styles.type = "text/css";
styles.innerHTML = `#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;
}
#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{
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1rem;
border-radius: 0.25rem;
border: solid 1px black;
}
#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;
}
#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;
}`;
container.append(waterMark,botText,menu);
setTimeout(function(){
if(document.body.innerText.split("\n").length < 8){ // assume broken. (just the water mark)
const temp = document.createElement("template");
temp.innerHTML = `<div id="antibot-broken-page" style="color: red; position: fixed; left: 0; top: 0; font-size: 1.25rem;line-height:1.75rem">
<h2>[ANTIBOT] - Detected broken page. This message may appear due to slow internet. It will dissapear once the page loads. If the page doesn't load, try one of the following:</h2>
<hr/>
<h2>Reload the page</h2>
<h2>Go back to <a href="https://create.kahoot.it/details/${location.search.split("quizId=")[1].split("&")[0]}">the kahoot launch screen</a>.</h2><br/>
<h2>Clear the cache of this page and then reload.</h2><br/>
<h2>Disable Kahoot AntiBot, reload the page, then re-enable Kahoot Antibot and reload the page again</h2>
</div>`;
document.body.append(temp.content.cloneNode(true));
const RemoveBroke = setInterval(()=>{
if(document.body.innerText.split("\n").length >= 20){
clearInterval(RemoveBroke);
document.getElementById("antibot-broken-page").outerHTML = "";
}
},1000);
}
},2000);
document.body.append(container,styles,counters);
const killcount = document.getElementById("killcount");
windw.isUsingNamerator = false;
windw.cachedUsernames = [];
windw.confirmedPlayers = new Set;
windw.cachedData = {};
windw.loggedPlayers = {};
windw.specialData = {
/**
* extraQuestionSetup - Modifies quiz data (anti-cheat, bot-captcha)
*
* @param {Object} quiz The quiz to be modified
*/
extraQuestionSetup: (quiz)=>{
if(windw.specialData.config.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(windw.specialData.config.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: "survey",
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: ""
}
});
}
},
startTime: 0, // The question start time
lastFakeLogin: 0, // The time when the last "fake valid" joined
lastFakeUserID: 0, // The id of the last "fake valid"
lastFakeUserName: "", // The name of the last "fake valid"
config:{
timeout: false, // Minimum 0.5s answer time
blocknum: false, // Ban all numbers
looksRandom: true, // Ban "random" names
banFormat1: true, // Ban bots like Doctor_Robot123
additionalQuestionTime: null, // Adds time to a question
percent: 0.6, // The name match percent
streakBonus: 2, // Whether to enable streak points
ddos: 0, // Whether to auto-lock quizzes
start_lock: 0, // Time until auto-start activates
counters: false, // Shows antibot counters
forceascii: false, // Forces alpha-numeric characters
blockservice1: false, // Special filters against kahootflood.weebly.com
counterCheats: false, // Counters cheats by adding an extra question
enableCAPTCHA: false // Adds a captcha
},
inLobby: true, // Whether in the lobby
lobbyLoadTime: 0, // The time the first player joined the lobby
lockInterval: null, // The lock interval
kahootCore: null, // Kahoot's core data
globalFuncs: null, // Useful functions (starting quiz, etc)
CAPTCHA_IDS: null, // Players that answered the captcha
blockService1Data: new Set() // Bots that are suspicious in blockservice1
};
// loading localStorage info
if(windw.localStorage.antibotConfig){
const a = JSON.parse(windw.localStorage.antibotConfig);
if(a.timeout){
const t = document.getElementById("antibot.config.timeoutlbl");
if(t){
t.click();
}
}
if(a.blocknum){
const t = document.getElementById("antibot.config.blocknum");
t.checked = true;
windw.specialData.config.blocknum = true;
}
if(!a.looksRandom){
const t = document.getElementById("antibot.config.lookrandlbl");
if(t){
t.click();
}
}
if(a.teamtime){
document.getElementById("antibot.config.teamtimeout").value = Number(a.teamtime);
windw.specialData.config.additionalQuestionTime = Number(a.teamtime);
}
if(a.percent){
document.getElementById("antibot.config.percent").value = Number(a.percent);
windw.specialData.config.percent = Number(a.percent);
}
if(!a.banFormat1){
document.getElementById("antibot.config.blockformat1").checked = false;
windw.specialData.config.banFormat1 = false;
}
if(a.streakBonus == 1){
document.getElementById("antibot.config.streakBonus").checked = true;
windw.specialData.config.streakBonus = 1;
}
if(a.ddos){
document.getElementById("antibot.config.ddos").value = +a.ddos;
windw.specialData.config.ddos = +a.ddos;
}
if(a.start_lock){
document.getElementById("antibot.config.start_lock").value = +a.start_lock;
windw.specialData.config.start_lock = +a.start_lock;
}
if(a.counters){
document.getElementById("antibot.config.counters").checked = true;
windw.specialData.config.counters = true;
}
if(a.forceascii){
document.getElementById("antibot.config.forceascii").checked = true;
windw.specialData.config.forceascii = true;
}
if(a.blockservice1){
document.getElementById("antibot.config.blockservice1").checked = true;
windw.specialData.config.blockservice1 = true;
}
if(a.counterCheats){
document.getElementById("antibot.config.counterCheats").checked = true;
windw.specialData.config.counterCheats = true;
}
if(a.enableCAPTCHA){
document.getElementById("antibot.config.enableCAPTCHA").checked = true;
windw.specialData.config.enableCAPTCHA = true;
}
}
let messageId = 0,
clientId = null,
pin = null;
/**
* looksRandom - Blocks names like "KaHOotSmaSH"
*
* @param {String} name The name of the controller
* @returns {Boolean} Whether it looks "random"
*/
function looksRandom(name){
// Assumes player names have either all caps, no caps, or up to 3 capital letters
return !/(^(([^A-Z\n]*)?[A-Z]?([^A-Z\n]*)?){0,3}$)|^([A-Z]*)$/.test(name);
}
/**
* isFakeValid - Marks suspicious names that are not 100% bots, but could be.
*
* @example AmazingRabbit32
*
* @param {String} name The name of the controller
* @returns {Boolean} Whether the name is "suspicious"
*/
function isFakeValid(name){
if(windw.specialData.config.blocknum && /\d/.test(name)){
return true;
}
if(windw.specialData.config.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);
}
/**
* similarity - Checks the similarity between names and other stuff
*
* @param {String} s1 The name of the first player
* @param {String} s2 The name of the second player
* @returns {Number} The percent match between the players. if -1 is returned, its due to a different issue.
*/
function similarity(s1, s2) {
// remove numbers from name if name is not only a number
if(isNaN(s1) && typeof(s1) !== "object" && !windw.isUsingNamerator){
s1 = s1.replace(/[0-9]/mg,"");
}
if(isNaN(s2) && typeof(s2) !== "object" && !windw.isUsingNamerator){
s2 = s2.replace(/[0-9]/mg,"");
}
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(windw.isUsingNamerator){
if(!/^([A-Z][a-z]+){2,3}$/.test(s2)){
return -1;
}
}
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);
}
/**
* editDistance - Used in similarity
*
* @param {String} s1 String 1
* @param {String} s2 String 2
* @returns {Number} "Distance" to match
*/
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];
}
/**
* createKickPacket - Creates a packet to kick the player
*
* @param {String} id The id of the bot
* @returns {Object} A kick packet
*/
function createKickPacket(id){
messageId++;
return [{
channel: "/service/player",
clientId: clientId,
id: String(Number(messageId)),
data: {
cid: String(id),
content: JSON.stringify({
kickCode: 1,
quizType: "quiz"
}),
gameid: pin,
host: "play.kahoot.it",
id: 10,
type: "message"
},
ext: {}
}];
}
/**
* determineEvil - Checks the similarity between players and stuff
* Moved the name length check here to fix a severe issue!
*
* @param {Object} player The controller
* @param {WebSocket} socket The websocket
*/
function determineEvil(player,socket){
if(isNaN(player.cid) || Object.keys(player).length <= 5 || player.name.length < 16){ //if the id has not been cached yet or is an invalid id, and they are not a bot :p
if(windw.cachedData[player.cid]){ // now allowing reconnection
return;
}
const packet = createKickPacket(player.cid);
socket.send(JSON.stringify(packet));
if(player.name.length >= 50){
player.name = "[Name-Too-Long] - " + player.name.length;
}
console.warn(`[ANTIBOT] - Bot ${player.name} has been banished - invalid packet/name`);
killcount.innerHTML = +killcount.innerHTML + 1;
throw "[ANTIBOT] - Bot banned. Dont add";
}
if(windw.cachedUsernames.length === 0){
if(similarity(null,player.name) === -1){
const packet = createKickPacket(player.cid);
socket.send(JSON.stringify(packet));
console.warn(`[ANTIBOT] - Bot ${player.name} has been banished`);
killcount.innerHTML = +killcount.innerHTML + 1;
delete windw.cachedData[player.cid];
throw "[ANTIBOT] - Bot banned. Dont add";
}
windw.cachedUsernames.push({name: player.name, id:player.cid, time: 10, banned: false});
windw.loggedPlayers[player.cid] = true;
}else{
let removed = false;
if(similarity(null,player.name) === -1){
removed = true;
const packet1 = createKickPacket(player.cid);
socket.send(JSON.stringify(packet1));
console.warn(`[ANTIBOT] - Bot ${player.name} has been banished`);
killcount.innerHTML = +killcount.innerHTML + 1;
delete windw.cachedData[player.cid];
throw "[ANTIBOT] - Bot banned. Dont add";
}
for(const i in windw.cachedUsernames){
if(windw.confirmedPlayers.has(windw.cachedUsernames[i].name)){
continue;
}
if(similarity(windw.cachedUsernames[i].name,player.name) >= windw.specialData.config.percent){
removed = true;
const packet1 = createKickPacket(player.cid);
socket.send(JSON.stringify(packet1));
if(!windw.cachedUsernames[i].banned){
const packet2 =createKickPacket(windw.cachedUsernames[i].id);
windw.cachedUsernames[i].banned = true;
socket.send(JSON.stringify(packet2));
delete windw.specialData.kahootCore.game.core.controllers[windw.cachedUsernames[i].id];
killcount.innerHTML = +killcount.innerHTML + 1;
}
windw.cachedUsernames[i].time = 10;
console.warn(`[ANTIBOT] - Bots ${player.name} and ${windw.cachedUsernames[i].name} have been banished`);
killcount.innerHTML = +killcount.innerHTML + 1;
delete windw.cachedData[player.cid];
delete windw.cachedData[windw.cachedUsernames[i].id];
throw "[ANTIBOT] - Bot banned. Dont add";
}
}
if(!removed){
windw.cachedUsernames.push({name: player.name,id: player.cid, time: 10, banned: false});
windw.loggedPlayers[player.cid] = true;
}
}
}
/**
* specialBotDetector - Checks other information
*
* @param {String} type The type of action
* @param {Object} data The controller
* @param {WebSocket} socket The websocket
*/
function specialBotDetector(type,data,socket){
switch (type) {
case "joined":
// if looks random
if(windw.specialData.config.looksRandom){
if(looksRandom(data.name)){
const packet = createKickPacket(data.cid);
socket.send(JSON.stringify(packet));
killcount.innerHTML = +killcount.innerHTML + 1;
const banned = windw.cachedUsernames.find(o=>{
return o.id === data.cid;
});
if(banned){
banned.banned = true;
banned.time = 10;
}
throw `[ANTIBOT] - Bot ${data.name} banned; name too random.`;
}
}
// if ban format 1 is enabled
if(windw.specialData.config.banFormat1){
if(/[a-z0-9]+[^a-z0-9\s][a-z0-9]+/gi.test(data.name)){
const packet = createKickPacket(data.cid);
socket.send(JSON.stringify(packet));
killcount.innerHTML = +killcount.innerHTML + 1;
const banned = windw.cachedUsernames.find(o=>{
return o.id === data.cid;
});
if(banned){
banned.banned = true;
banned.time = 10;
}
throw `[ANTIBOT] - Bot ${data.name} banned; Name matches format [F][R][L].`;
}
}
// special filters for kahootflood.weebly.com
if(windw.specialData.config.blockservice1){
if(data.name.replace(/[ᗩᗷᑕᗪEᖴGᕼIᒍKᒪᗰᑎOᑭᑫᖇᔕTᑌᐯᗯ᙭Yᘔ]/g,"").length / data.name.length < 0.5){
const packet = createKickPacket(data.cid);
socket.send(JSON.stringify(packet));
killcount.innerHTML = +killcount.innerHTML + 1;
const banned = windw.cachedUsernames.find(o=>{
return o.id === data.cid;
});
if(banned){
banned.banned = true;
banned.time = 10;
}
throw `[ANTIBOT] - Bot ${data.name} banned; likely from kahootflood.weebly.com.`;
}
if((windw.aSetOfEnglishWords || new Set).has(data.name)){
// check if being spammed
windw.specialData.blockService1Data.add(data);
setTimeout(()=>{
windw.specialData.blockService1Data.delete(data);
},5e3);
if(windw.specialData.blockService1Data.size >= 10){
// probably being spammed.
for(const bot of windw.specialData.blockService1Data){
if(bot.banned){
continue;
}
const p = createKickPacket(bot.cid);
socket.send(JSON.stringify(p));
killcount.innerHTML = +killcount.innerHTML + 1;
const banned = windw.cachedUsernames.find(o=>{
return o.id === data.cid;
});
if(banned){
banned.banned = true;
banned.time = 10;
}
delete windw.cachedData[bot.cid];
delete windw.specialData.kahootCore.game.core.controllers[bot.cid];
if(windw.specialData.blockService1Data.size >= 10){
windw.specialData.blockService1Data.delete(bot);
}else{
bot.banned = true;
}
}
throw "[ANTIBOT] - Bots banned. Likely from kahootflood.weebly.com. Don't add.";
}
}
}
if(!windw.isUsingNamerator){
if(isFakeValid(data.name)){
if(Date.now() - windw.specialData.lastFakeLogin < 5000){
if(windw.cachedData[windw.specialData.lastFakeUserID]){ // to get the first guy
const packet = createKickPacket(windw.specialData.lastFakeUserID);
socket.send(JSON.stringify(packet));
delete windw.kahootCore.game.core.controllers[windw.specialData.lastFakeUserID];
delete windw.cachedData[windw.specialData.lastFakeUserID];
const banned = windw.cachedUsernames.find(o=>{
return o.id === windw.specialData.lastFakeUserID;
});
if(banned){
banned.banned = true;
banned.time = 10;
}
}
const packet = createKickPacket(data.cid);
socket.send(JSON.stringify(packet));
delete windw.cachedData[data.cid];
const banned = windw.cachedUsernames.find(o=>{
return o.id === data.cid;
});
if(banned){
banned.banned = true;
banned.time = 10;
}
killcount.innerHTML = +killcount.innerHTML + 1;
windw.specialData.lastFakeLogin = Date.now();
windw.specialData.lastFakeUserID = data.cid;
windw.specialData.lastFakeUserName = data.name;
throw `[ANTIBOT] - Banned bot ${data.name}; their name is suspicious, likely a bot.`;
}
windw.specialData.lastFakeLogin = Date.now();
windw.specialData.lastFakeUserID = data.cid;
windw.specialData.lastFakeUserName = data.name;
}
}
break;
}
}
/**
* teamBotDetector - Checks the team of a client
*
* @param {Array} team The team members
* @param {String} cid The cid of the controller
* @param {WebSocket} socket The websocket
*/
function teamBotDetector(team,cid,socket){
if(team.length == 0 || team.indexOf("") != -1 || team.indexOf("Player 1") != -1 || team.join("") === "Youjustgotbotted" /* kahootflood.weebly.com */){
const packet = createKickPacket(cid);
socket.send(JSON.stringify(packet));
killcount.innerHTML = +killcount.innerHTML + 1;
let name = "";
delete windw.cachedData[cid];
const banned = windw.cachedUsernames.find(o=>{
return o.id === cid;
});
if(banned){
banned.banned = true;
banned.time = 10;
name = banned.name;
}
throw `[ANTIBOT] - Bot ${name} banned; invalid team members.`;
}
}
// Cache Manager Timer
setInterval(function(){
for(const i in windw.cachedUsernames){
if(windw.cachedUsernames[i].time <= 0 && !windw.cachedUsernames[i].banned && !windw.confirmedPlayers.has(windw.cachedUsernames[i].name)){
windw.confirmedPlayers.add(windw.cachedUsernames[i].name);
continue;
}
if(windw.cachedUsernames[i].time <= -20){
windw.cachedUsernames.splice(i,1);
continue;
}
windw.cachedUsernames[i].time--;
}
},1e3);
// 2 Factor Auth Timer
setInterval(()=>{
for(const i in windw.cachedData){
windw.cachedData[i].tries = 0;
}
},10e3);
/**
* sendHandler - Checks the outgoing messages
*
* @param {String} data The message
* @param {Object} e The socket
*/
windw.sendHandler = function(data,e){
data = JSON.parse(data)[0];
if(data.data){
if(!data.data.id){
return;
}
switch (data.data.id) {
case 2:
// question start
windw.specialData.startTime = Date.now();
windw.specialData.CAPTCHA_IDS = new Set;
break;
case 5:
// restart
windw.specialData.inLobby = true;
windw.specialData.lobbyLoadTime = 0;
break;
case 10:
if(data.data.content === "{}"){
windw.specialData.inLobby = true;
windw.specialData.lobbyLoadTime = 0;
}
break;
case 9:
// start
windw.specialData.inLobby = false;
if(windw.specialData.StartLockElem){
clearInterval(windw.specialData.StartLockInterval);
windw.specialData.StartLockElem.outerHTML = "";
windw.specialData.StartLockElem = null;
}
break;
case 4:
// question end
if(windw.specialData.kahootCore.game.navigation.currentGameBlockIndex === 0
&& windw.specialData.globalQuizData.questions[0].isAntibotQuestion){
// boot all who did not answer
const controllers = windw.specialData.kahootCore.game.core.controllers,
answeredControllers = windw.specialData.CAPTCHA_IDS;
for(const id in controllers){
if(controllers[id].isGhost || contollers[id].hasLeft){
continue;
}
if(!answeredControllers.has(id)){
const pack = createKickPacket(id);
e.webSocket.send(JSON.stringify(pack));
killcount.innerHTML = +killcount.innerHTML + 1;
console.error("[ANTIBOT] - Bot banned. Did not answer the CAPTCHA.");
delete windw.cachedData[data.data.cid];
delete controllers[id];
}
}
// Prevent auto-lock from activating from this
oldamount = 0;
}
break;
}
}
};
let ExtraCheck2 = function(){};
try{
if(windw.localStorage.extraCheck2){
ExtraCheck2 = new Function("return " + windw.localStorage.extraCheck2)();
}
}catch(e){/* Likely doesn't exist */}
let oldamount = 0,
locked = false;
setInterval(()=>{
oldamount = +killcount.innerHTML;
},20e3);
/**
* globalMessageListener - Checks the incoming messages
*
* @param {Object} e The socket
* @param {Object} t The incoming message
*/
window.globalMessageListener = function(e,t){
try{ExtraCheck2(e,t);}catch(e){console.error("[ANTIBOT] - Execution of PIN-CHECKER Failed: " + e);}
windw.e = e;
if(!windw.e.webSocket.oldSend){
windw.e.webSocket.oldSend = windw.e.webSocket.send;
windw.e.webSocket.send = function(data){
windw.sendHandler(data,e);
windw.e.webSocket.oldSend(data);
};
}
try{pin = windw.specialData.kahootCore.game.core.pin;}catch(e){/* Pin doesn't exist yet */}
// check DDOS
if(!locked && pin){
if(!!(+windw.specialData.config.ddos) && (+killcount.innerHTML - oldamount) > (+windw.specialData.config.ddos/3)){
locked = true;
// LOCK THE GAME!
// 2.12.0 - Repeats every 0.25 seconds until the game is actually locked.
const lockPacket = [{
channel: "/service/player",
clientId,
data: {
gameid: pin,
type: "lock"
},
ext: {},
id: ++messageId
}];
e.webSocket.send(JSON.stringify(lockPacket));
windw.specialData.lockInterval = setInterval(()=>{
lockPacket.id = ++messageId;
e.webSocket.send(JSON.stringify(lockPacket));
},250);
console.log("[ANTIBOT] - Detected bot spam. Locking game for 1 minute.");
if(windw.specialData.config.counters){
const ddoscount = document.createElement("div");
let int = 60;
ddoscount.innerHTML = `<span class="antibot-count-num">60</span>
<span class="antibot-count-desc">Until Unlock</span>`;
counters.append(ddoscount);
const countTimer = setInterval(()=>{
ddoscount.querySelector(".antibot-count-num").innerHTML = --int;
if(int <= 0){
clearInterval(countTimer);
ddoscount.outerHTML = "";
}
},1e3);
}
setTimeout(()=>{
locked = false;
clearInterval(windw.specialData.lockInterval);
// UNLOCK GAME
console.log("[ANTIBOT] - Unlocking game.");
e.webSocket.send(JSON.stringify([{
channel: "/service/player",
clientId,
data: {
gameid: pin,
type: "unlock"
},
ext: {},
id: ++messageId
}]));
},60e3);
}
}
const data = JSON.parse(t.data)[0];
messageId = data.id ? data.id : messageId;
/*if the message is the first message, which contains important clientid data*/
if(data.id === "1"){
clientId = data.clientId;
}
/*if the message is a player join message*/
if(data.data && data.data.type === "joined"){
console.warn("[ANTIBOT] - determining evil...");
determineEvil(data.data,e.webSocket);
specialBotDetector(data.data.type,data.data,e.webSocket);
// Player was not banned.
windw.cachedData[data.data.cid] = {
tries: 0,
loginTime: Date.now()
};
if(windw.specialData.inLobby && windw.specialData.config.start_lock !== 0 && windw.specialData.globalFuncs && windw.specialData.globalFuncs.gameOptions.automaticallyProgressGame){
if(windw.specialData.lobbyLoadTime === 0){
windw.specialData.lobbyLoadTime = Date.now();
if(windw.specialData.config.counters){
const c = document.createElement("div");
c.innerHTML = `<span class="antibot-count-num">${Math.round((windw.specialData.config.start_lock - (Date.now() - windw.specialData.lobbyLoadTime)/1000))}</span>
<span class="antibot-count-desc">Until Auto-Start</span>`;
const i = setInterval(()=>{
let t = Math.round((windw.specialData.config.start_lock - (Date.now() - windw.specialData.lobbyLoadTime)/1000));
if(t < 0){
t = "Please Wait...";
}
c.querySelector(".antibot-count-num").innerHTML = t;
},1e3);
counters.append(c);
windw.specialData.StartLockElem = c;
windw.specialData.StartLockInterval = i;
}
}
if(Date.now() - windw.specialData.lobbyLoadTime > windw.specialData.config.start_lock * 1000){
// max time passed, just start the darn thing!
const {controllers} = windw.specialData.globalFuncs;
if(controllers.filter((controller)=>{
return !controller.isGhost && !controller.hasLeft;
}).length === 0){
// The only current player in the lobby.
windw.specialData.lobbyLoadTime = Date.now();
}else{
windw.specialData.globalFuncs.startQuiz();
if(windw.specialData.StartLockElem){
clearInterval(windw.specialData.StartLockInterval);
windw.specialData.StartLockElem.outerHTML = "";
windw.specialData.StartLockElem = null;
}
}
}
}
}else if(data.data && data.data.id === 45){
// if player answers
if(windw.specialData.kahootCore.game.navigation.currentGameBlockIndex === 0
&& windw.specialData.globalQuizData.questions[0].isAntibotQuestion){
windw.specialData.CAPTCHA_IDS.add(data.data.cid);
// if incorrect answer
let choice = -1;
try{
choice = JSON.parse(data.data.content).choice;
}catch(e){/* Likely invalid answer */}
if(choice !== windw.specialData.globalQuizData.questions[0].AntibotCaptchaCorrectIndex){
// BAN!
const packet = createKickPacket(data.data.cid);
e.webSocket.send(JSON.stringify(packet));
delete windw.cachedData[data.data.cid];
delete windw.specialData.kahootCore.game.core.controllers[data.data.cid];
killcount.innerHTML = +killcount.innerHTML + 1;
console.error("[ANTIBOT] - Bot banned. Failed the captcha.");
}
}
if(Date.now() - windw.specialData.startTime < 500 && windw.specialData.config.timeout){
throw "[ANTIBOT] - Answer was too quick!";
}
// if player just recently joined (within 1 second)
if(windw.cachedData[data.data.cid] && Date.now() - windw.cachedData[data.data.cid].loginTime < 1000){
const packet = createKickPacket(data.data.cid);
windw.e.webSocket.send(JSON.stringify(packet));
killcount.innerHTML = +killcount.innerHTML + 1;
delete windw.cachedData[data.data.cid];
delete windw.specialData.kahootCore.game.core.controllers[data.data.cid];
throw `[ANTIBOT] - Bot with id ${data.data.cid} banned. Answered too quickly after joining.`;
}
}else if(data.data && data.data.id === 50){
windw.cachedData[data.data.cid].tries++;
if(windw.cachedData[data.data.cid].tries > 3){
const kicker = createKickPacket(data.data.cid);
e.webSocket.send(JSON.stringify(kicker));
const name = windw.cachedUsernames.filter(o=>{return o.id == data.data.cid;}).length ? windw.cachedUsernames.filter(o=>{return o.id == data.data.cid;})[0].name : "bot";
console.warn(`[ANTIBOT] - Bot ${name} banished. Seen spamming 2FA`);
const banned = windw.cachedUsernames.find(o=>{
return o.id === windw.specialData.lastFakeUserID;
});
if(banned){
banned.banned = true;
banned.time = 10;
}
delete windw.specialData.kahootCore.game.core.controllers[data.data.cid];
delete windw.cachedData[data.data.cid];
killcount.innerHTML = +killcount.innerHTML + 1;
}
}else if(data.data && data.data.id === 18) {
teamBotDetector(JSON.parse(data.data.content),data.data.cid,e.webSocket);
}else if(data.data && data.data.status === "LOCKED"){
clearInterval(windw.specialData.lockInterval);
}
};
// remove loaded modules (allows turning off things to be a bit easier)
delete localStorage.kahootThemeScript;
delete localStorage.extraCheck;
delete localStorage.extraCheck2;
},
mainScript = new XMLHttpRequest();
mainScript.open("GET","https://play.kahoot.it/"+script2);
mainScript.send();
mainScript.onload = ()=>{
let sc = mainScript.response;
// Access the namerator option
const nr = /=[a-z]\.namerator/gm,
letter = sc.match(nr)[0].match(/[a-z](?=\.)/g)[0];
sc = sc.replace(sc.match(nr)[0],`=(()=>{console.log(${letter}.namerator);windw.isUsingNamerator = ${letter}.namerator;return ${letter}.namerator})()`);
// Access the currentQuestionTimer and change the question time
const cqtr = /currentQuestionTimer:[a-z]\.payload\.questionTime/gm,
letter2 = sc.match(cqtr)[0].match(/[a-z](?=\.payload)/g)[0];
sc = sc.replace(sc.match(cqtr)[0],`currentQuestionTimer:${letter2}.payload.questionTime + (()=>{return (windw.specialData.config.additionalQuestionTime * 1000) || 0})()`);
// Access the "NoStreakPoints", allowing it to be enabled
const nsr = /[a-zA-Z]{2}\.NoStreakPoints/gm;
sc = sc.replace(sc.match(nsr)[0],"windw.specialData.config.streakBonus || 2"); // yes = 1, no = 2
// Access the StartQuiz function. Also gains direct access to the controllers!
const sq = /=[a-zA-Z]\.startQuiz/gm,
letter4 = sc.match(sq)[0].match(/[a-zA-Z](?=\.)/g)[0];
sc = sc.replace(sc.match(sq)[0],`=(()=>{
windw.specialData.globalFuncs = ${letter4};
return ${letter4}.startQuiz})()`);
// Access the fetched quiz information. Allows the quiz to be modified when the quiz is fetched!
const fqr = /RETRIEVE_KAHOOT_ERROR",[\w\d]{2}=function\([a-z]\){return Object\([\w$\d]{2}\.[a-z]\)\([\w\d]{2},{response:[a-z]}\)}/gm,
letter5 = sc.match(fqr)[0].match(/response:[a-z]/g)[0].split(":")[1],
fqrt = sc.match(fqr)[0];
sc = sc.replace(fqrt,`RETRIEVE_KAHOOT_ERROR",${fqrt.split("RETRIEVE_KAHOOT_ERROR\",")[1].split("response:")[0]}response:(()=>{
windw.specialData.globalQuizData = ${letter5};
windw.specialData.extraQuestionSetup(${letter5});
return ${letter5};
})()})}`);
// Access the core data
const cr = /[a-z]\.game\.core/m,
letter6 = sc.match(cr)[0].match(/[a-z](?=\.game)/)[0];
sc = sc.replace(cr,`(()=>{
if(typeof windw !== "undefined"){
windw.specialData.kahootCore = ${letter6};
}
return ${letter6}.game.core;
})()`);
let changed = originalPage.split("</body>");
/**
* ExternalLibrary - 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
*/
const ExternalLibrary = new XMLHttpRequest;
ExternalLibrary.open("GET","https://raw.githubusercontent.com/theusaf/a-set-of-english-words/master/index.js");
ExternalLibrary.send();
ExternalLibrary.onload = ExternalLibrary.onerror = function(){
let ext = "";
if(ExternalLibrary.readyState === 4 && ExternalLibrary.status === 200){
ext = ExternalLibrary.responseText;
}
changed = `${changed[0]}<script>${patchedScript}</script><script>${sc}</script><script>try{(${window.localStorage.kahootThemeScript})();}catch(err){}try{(${window.localStorage.extraCheck})();}catch(err){}window.setupAntibot = ${code.toString()};window.parent.fireLoaded = window.fireLoaded = true;window.setupAntibot();try{${ext};window.parent.aSetOfEnglishWords = window.aSetOfEnglishWords;}catch(e){}</script></body>${changed[1]}`;
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();
const doc = document.querySelector("iframe");
doc.contentDocument.write(changed);
document.title = doc.contentDocument.title;
};
};
};
};