Kahoot AntiBot

Remove all bots from a kahoot game.

目前为 2019-04-09 提交的版本。查看 最新版本

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

window.stop(); //stop loading and save data from the page.
window.url = window.location.href;
window.kahootScript = null;
window.kahootScriptSrc = "https://play.kahoot.it/js/player.min.js?v=" + window.kahoot.version;
document.documentElement.innerHTML = "";
window.x = new XMLHttpRequest();
window.x.open("GET",kahootScriptSrc);
window.x.send();
window.x.onload = ()=>{ //load kahoot resources and edit code to allow access to the websockets
    var fix = window.x.response.replace("i.onMessage=function(e,t){","i.onMessage=function(e,t){window.globalMessageListener(e,t);"); //edit code to be able to read all messages and access the websocket
    //console.log(fix);
    window.kahootScript = document.createElement("script");
    window.kahootScript.innerHTML = fix;
    var x = new XMLHttpRequest();
    x.open("GET","https://play.kahoot.it/js/bootstrap.js");
    x.send();
    x.onload = ()=>{
        var patch = x.response.replace(",$script([\"js/player.min.js?v\"+kahoot.version],function(){angular.bootstrap(document,[\"app\"])});","");
        var pS = document.createElement("script");
        pS.innerHTML = patch;
        var x2 = new XMLHttpRequest();
        x2.open("GET",url);
        x2.send();
        x2.onload = ()=>{
            var fin = x2.response.replace("js/bootstrap.js","data:text/plain,null");
            document.open();
            document.write(fin);
            document.close();
            window.onload = ()=>{
                document.body.append(pS);
                document.body.append(window.kahootScript);
                angular.bootstrap(document,["app"]); /*global angular*/
                setupAntibot(); //win!
            };
        };
    };
};

function setupAntibot(){
    const percent = 0.6;
    var waterMark = document.createElement("p");
    waterMark.innerText = "v2.1.2 @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);
        }
    }
}