Kahoot AntiBot

Remove all bots from a kahoot game.

目前為 2019-04-09 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Kahoot AntiBot
// @namespace    http://tampermonkey.net/
// @version      2.2.0
// @description  Remove all bots from a kahoot game.
// @author       theusaf
// @match        *://play.kahoot.it/*
// @grant        none
// @run-at       document_start
// ==/UserScript==

if(window.fireLoaded){
  throw "page is loaded";
}
window.stop(); //stop loading and save data from the page.
window.url = window.location.href;
window.version = new XMLHttpRequest();
window.version.open("GET",`https://play.kahoot.it/shared/theme/config.js?${Date.now()}`);
window.version.send();
window.version.onload = ()=>{
  var kV = window.version.response.split("'")[1];
  var player = new XMLHttpRequest();
  player.open("GET","https://play.kahoot.it/js/player.min.js?v=" + kV);
  player.send();
  player.onload = ()=>{
    var playerScript = player.response.replace("i.onMessage=function(e,t){","i.onMessage=function(e,t){window.globalMessageListener(e,t);");
    var boots = new XMLHttpRequest();
    boots.open("GET","https://play.kahoot.it/js/bootstrap.js");
    boots.send();
    boots.onload = ()=>{
      var patch = boots.response.replace(",$script([\"js/player.min.js?v\"+kahoot.version],function(){angular.bootstrap(document,[\"app\"])});",";");
      var page = new XMLHttpRequest();
      page.open("GET",window.url);
      page.send();
      page.onload = ()=>{
        const code = `window.setupAntibot = ()=>{
            const percent = 0.6;
            var waterMark = document.createElement("p");
            waterMark.innerText = "v2.2.0 @theusaf";
            waterMark.style = "position: fixed; bottom: 100px; right: 100px; font-size: 1em";
            document.body.append(waterMark);
            window.isUsingNamerator = false;
            window.cachedUsernames = [];
            window.confirmedPlayers = [];
            var messageId = 0;
            var clientId = null;
            var pin = null;
            function similarity(s1, s2) {
                if(!s2){
                    return 0;
                }
                if(!isNaN(s2) && !isNaN(s1) && s1.length == s2.length){
                    return 1;
                }
                if(window.isUsingNamerator){
                    let caps = s2.length - s2.replace(/[A-Z]/g, '').length;
                    if(caps !== 2){ /*has less than 2 or more than 2 capitals*/
                        return 1;
                    }
                    if (s2.substr(0,1).replace(/[A-Z]/g,'').length == 1){ /*first char is not a capital*/
                        return 1;
                    }
                    if(s2.substr(s2.length - 1,1).replace(/[A-Z]/g,'').length == 0){ /*last char is a capital*/
                        return 1;
                    }
                    if(s2.replace(/[a-z]/ig,'').length > 0){ /*has non-letter chars*/
                        return 1;
                    }
                }
                if(!s1){
                    return;
                }
                s1 = s1.toLowerCase();
                s2 = s2.toLowerCase();
                var longer = s1;
                var shorter = s2;
                if (s1.length < s2.length) {
                    longer = s2;
                    shorter = s1;
                }
                var longerLength = longer.length;
                if (longerLength == 0) {
                    return 1.0;
                }
                return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength);
            }
            function editDistance(s1, s2) {
                s1 = s1.toLowerCase();
                s2 = s2.toLowerCase();

                var costs = new Array();
                for (var i = 0; i <= s1.length; i++) {
                    var lastValue = i;
                    for (var j = 0; j <= s2.length; j++) {
                        if (i == 0){
                            costs[j] = j;
                        }
                        else {
                            if (j > 0) {
                                var 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 checkSettings(){
                var namer = document.querySelectorAll("[data-functional-selector=\\\"game-settings-namerator-toggle\\\"]")[0];
                window.isUsingNamerator = Array.from(namer.classList).includes("on");
                let observer = new MutationObserver(function(button){
                    window.isUsingNamerator = Array.from(button[0].target.classList).includes("on");
                });
                let conf = {
                    attributes: true
                };
                observer.observe(namer,conf);
            }
            function setup(){
                var launch_button = document.querySelectorAll("[data-functional-selector=\\\"launch-button\\\"]")[0];
                if(launch_button){
                    console.warn("launch button found!");
                    checkSettings();
                }else{
                    setTimeout(setup,1000);
                }
            }
            setup();
            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"
                    }
                }];
            }
            function determineEvil(player,socket){
                if(window.cachedUsernames.length == 0){
                    if(similarity(null,player.name) >= percent){
                        var packet = createKickPacket(player.cid);
                        socket.send(JSON.stringify(packet));
                        console.warn(\`Bot \${player.name} has been banished\`);
                        throw "Bot banned. Dont add";
                    }
                    window.cachedUsernames.push({name: player.name, id: player.cid, time: 10, banned: false});
                }else{
                    var removed = false;
                    for(var i in window.cachedUsernames){
                        if(window.confirmedPlayers.includes(window.cachedUsernames[i].name)){
                            continue;
                        }
                        if(similarity(window.cachedUsernames[i].name,player.name) >= percent){
                            removed = true;
                            var packet1 = createKickPacket(player.cid);
                            socket.send(JSON.stringify(packet1));
                            if(!window.cachedUsernames[i].banned){
                                var packet2 = createKickPacket(window.cachedUsernames[i].id);
                                window.cachedUsernames[i].banned = true;
                                window.cachedUsernames[i].time = 10;
                                socket.send(JSON.stringify(packet2));
                            }
                            console.warn(\`Bots \${player.name} and \${window.cachedUsernames[i].name} have been banished\`);
                            throw "Bot banned. Dont add";
                        }
                    }
                    if(!removed){
                        window.cachedUsernames.push({name: player.name, id: player.cid, time: 10, banned: false});
                    }
                }
            }
            var timer = setInterval(function(){
                for(let i in window.cachedUsernames){
                    if(window.cachedUsernames[i].time <= 0 && !window.cachedUsernames[i].banned && !window.confirmedPlayers.includes(window.cachedUsernames[i].name)){
                        window.confirmedPlayers.push(window.cachedUsernames[i].name);
                        continue;
                    }
                    if(window.cachedUsernames[i].time <= -20){
                        window.cachedUsernames.splice(i,1);
                        continue;
                    }
                    window.cachedUsernames[i].time--;
                }
            },1000);
            window.globalMessageListener = function(e,t){
                /*console.log(e); from testing: e is the websocket*/
                var data = JSON.parse(t.data)[0];
                /*console.log(data);*/
                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 contains the pin*/
                if(data.id == 3){
                    pin = Number(data.subscription.split("r/")[1]);
                }
                /*if the message is a player join message*/
                if(data.data ? data.data.type == "joined" : false){
                    console.warn("determining evil...");
                    determineEvil(data.data,e);
                }
            }
        }`;
        var changed = page.response.replace("js/bootstrap.js","data:text/plain,null");
        changed = changed.split("</body>");
        changed = `${changed[0]}<script type="text/javascript">${patch+playerScript+code};angular.bootstrap(document,["app"]);window.setupAntibot();window.fireLoaded = true;</script></body>${changed[1]}`;
        document.open();
        document.write(changed);
        document.close();
      };
    };
  };
};