Kahoot AntiBot

Remove all bots from a kahoot game.

当前为 2019-04-09 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
        }
    }
}