您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Check the pin of a kahoot game.
当前为
// ==UserScript== // @name Kahoot PIN Checker // @namespace http://tampermonkey.net/ // @version 1.0.5 // @description Check the pin of a kahoot game. // @author theusaf // @match *://play.kahoot.it/* // @exclude *://play.kahoot.it/v2/assets/* // @grant none // @run-at document-start // ==/UserScript== if(window.fireLoaded || (window.parent && window.parent.PinCheckerMain)){ throw "[PIN-CHECKER] - Already loaded."; } console.log("[PIN-CHECKER] - Looking for AntiBot"); /** * PinCheckerMain - The main pin checking function */ window.PinCheckerMain = function(){ const windw = window.parent; const loader = setInterval(()=>{ if(!document.querySelector("[data-functional-selector=launch-team-mode-button]")){ return; } console.log("[PIN-CHECKER] - Ready!"); clearInterval(loader); document.querySelector("[data-functional-selector=launch-team-mode-button]").addEventListener("click",()=>{ console.log("[PIN-CHECKER] - Using Team Mode.") windw.localStorage.PinCheckerMode = "team"; }); if(windw.localStorage.PinCheckerAutoRelogin == "true"){ const waiter = setInterval(()=>{ let a = document.querySelector("[data-functional-selector=launch-button]"); if(windw.localStorage.PinCheckerMode == "team"){ a = document.querySelector("[data-functional-selector=launch-team-mode-button]"); } delete windw.localStorage.PinCheckerMode; if(a && !a.disabled){ a.click(); windw.localStorage.PinCheckerAutoRelogin = false; clearInterval(waiter); } },500); }else{ delete windw.localStorage.PinCheckerMode; } },500); windw.PinCheckerNameList = []; windw.PinCheckerPin = null; /** * ResetGame - Reloads the page */ function ResetGame(){ console.error("[PIN-CHECKER] - Pin Broken. Attempting restart."); windw.localStorage.PinCheckerAutoRelogin = true; windw.document.write("<scr" + "ipt>" + `window.location = "https://play.kahoot.it/v2/${windw.location.search}";` + "</scr" + "ipt>"); } /** * concatTokens - From kahoot.js.org. Combines the tokens. * * @param {String} headerToken decoded token * @param {String} challengeToken decoded token 2 * @returns {String} The final token */ function concatTokens(headerToken, challengeToken) { // Combine the session token and the challenge token together to get the string needed to connect to the websocket endpoint for (var token = "", i = 0; i < headerToken.length; i++) { var char = headerToken.charCodeAt(i); var mod = challengeToken.charCodeAt(i % challengeToken.length); var decodedChar = char ^ mod; token += String.fromCharCode(decodedChar); } return token; } /** * CreateClient - Creates a Kahoot! client to join a game * This really only works because kahoot treats kahoot.it, play.kahoot.it, etc as the same thing. * * @param {Number} pin The gameid */ function CreateClient(pin){ console.log("[PIN-CHECKER] - Creating client"); pin = pin + ""; const SessionGetter = new XMLHttpRequest(); SessionGetter.open("GET","/reserve/session/" + pin); SessionGetter.send(); SessionGetter.onload = function(){ let SessionData; try{ SessionData = JSON.parse(SessionGetter.responseText); }catch(e){ // probably not found return ResetGame(); } const TokenHeader = atob(SessionGetter.getResponseHeader("x-kahoot-session-token")); let {challenge} = SessionData; challenge = challenge.replace(/(\u0009|\u2003)/mg, ""); challenge = challenge.replace(/this /mg, "this"); challenge = challenge.replace(/ *\. */mg, "."); challenge = challenge.replace(/ *\( */mg, "("); challenge = challenge.replace(/ *\) */mg, ")"); challenge = challenge.replace("console.", ""); challenge = challenge.replace("this.angular.isObject(offset)", "true"); challenge = challenge.replace("this.angular.isString(offset)", "true"); challenge = challenge.replace("this.angular.isDate(offset)", "true"); challenge = challenge.replace("this.angular.isArray(offset)", "true"); const merger = "var _ = {" + " replace: function() {" + " var args = arguments;" + " var str = arguments[0];" + " return str.replace(args[1], args[2]);" + " }" + "}; " + "var log = function(){};" + "return "; const solver = Function(merger + challenge); const ChallengeHeader = solver(); const FinalToken = concatTokens(TokenHeader,ChallengeHeader); const connection = new WebSocket("wss://play.kahoot.it/cometd/" + pin + "/" + FinalToken); connection.addEventListener("open",()=>{ connection.send(JSON.stringify([ { advice: { interval: 0, timeout: 60000 }, minimumVersion: "1.0", version: "1.0", supportedConnectionTypes: ["websocket","long-polling"], channel: "/meta/handshake", ext: { ack: true, timesync: { l: 0, o: 0, tc: Date.now() } }, id: 1 } ])); }); let shoken = false, timesync = {}, clientId = "", mid = 2, closed = false, cid = "", name = ""; connection.addEventListener("message",(m)=>{ const {data} = m; const [message] = JSON.parse(data); if(message.channel === "/meta/handshake" && !shoken){ if(message.ext && message.ext.timesync){ shoken = true; clientId = message.clientId; const {tc,ts,p} = message.ext.timesync, l = Math.round((Date.now() - tc - p) / 2), o = ts - tc - l; Object.assign(timesync,{ l, o, get tc(){ return Date.now(); } }); connection.send(JSON.stringify([{ advice: {timeout:0}, channel: "/meta/connect", id: 2, ext: { ack: 0, timesync }, clientId }])); // start joining setTimeout(()=>{ name = "KCP_" + (Date.now() + "").substr(2); connection.send(JSON.stringify([{ clientId, channel: "/service/controller", id: ++mid, ext: {}, data: { gameid: pin, host: "play.kahoot.it", content: JSON.stringify({ device: { userAgent: windw.navigator.userAgent, screen: { width: windw.screen.width, height: windw.screen.height } } }), name, type: "login" } }])); },1000); } }else if(message.channel === "/meta/connect" && shoken && !closed){ connection.send(JSON.stringify([{ channel: "/meta/connect", id: ++mid, ext: { ack: message.ext.ack, timesync }, clientId }])); }else if(message.channel === "/service/controller"){ if(message.data && message.data.type === "loginResponse"){ if(message.data.error === "NONEXISTING_SESSION"){ // session doesn't exist connection.send(JSON.stringify([{ channel: "/meta/disconnect", clientId, id: ++mid, ext: { timesync } }])); connection.close(); ResetGame(); }else{ // Check if the client is in the game after 10 seconds setTimeout(()=>{ if(!windw.PinCheckerNameList.includes(name)){ // Uh oh! the client didn't join! ResetGame(); } },10e3) // good. leave the game. connection.send(JSON.stringify([{ channel: "/meta/disconnect", clientId, id: ++mid, ext: { timesync } }])); closed = true; setTimeout(()=>{ connection.close(); },500); } } }else if(message.channel === "/service/status"){ if(message.data.status === "LOCKED"){ // locked, cannot test console.log("[PIN-CHECKER] - Game is locked. Unable to test."); closed = true; connection.send(JSON.stringify([{ channel: "/meta/disconnect", clientId, id: ++mid, ext: { timesync } }])); setTimeout(()=>{ connection.close(); },500); } } }); }; } windw.PinCheckerInterval = setInterval(()=>{ if(windw.PinCheckerPin){ CreateClient(windw.PinCheckerPin); } },60*1000); }; /** * PinCheckerInjector - Checks messages and stores the names of players who joined within the last few seconds * * @param {String} message The websocket message */ window.PinCheckerInjector = function(socket,message){ const windw = window.parent; const data = JSON.parse(message.data)[0]; try{ const part = document.querySelector("[data-functional-selector=\"game-pin\"]") || document.querySelector("[data-functional-selector=\"bottom-bar-game-pin\"]"); if(Number(part.innerText) != windw.PinCheckerPin && (Number(part.innerText) != 0) && !isNaN(Number(part.innerText))){ windw.PinCheckerPin = Number(part.innerText); console.log("[PIN-CHECKER] - Discovered new PIN: " + windw.PinCheckerPin); }else if(Number(part.innerText) == 0 || isNaN(Number(part.innerText))){ windw.PinCheckerPin = null; console.log("[PIN-CHECKER] - PIN is hidden or game is locked. Unable to test."); } }catch(err){ console.warn("[PIN-CHECKER] - Failed to get pin. Full error log below:\n" + err); } if(data.data && data.data.type === "joined"){ windw.PinCheckerNameList.push(data.data.name); setTimeout(()=>{ windw.PinCheckerNameList.splice(0,1); },20e3); } }; if(!window.page){ document.write("[PIN-CHECKER] - Patching Kahoot. Please wait. If this screen stays blank for long periods of time, please force reload or clear your cache."); let page = new XMLHttpRequest(); page.open("GET",location.href); page.send(); page.onload = function(){ let scriptURL = page.response.match(/><\/script><script\ .*?vendors.*?><\/script>/mg)[0].substr(9).split("src=\"")[1].split("\"")[0]; let script2 = page.response.match(/<\/script><script src=\"\/v2\/assets\/js\/main.*?(?=\")/mg)[0].substr(22); let originalPage = page.response.replace(/><\/script><script\ .*?vendors.*?><\/script>/mg,"></script>"); originalPage = originalPage.replace(/<\/script><script src=\"\/v2\/assets\/js\/main.*?(?=\")/mg,"</script><script src=\"data:text/javascript,"); let script = new XMLHttpRequest(); script.open("GET","https://play.kahoot.it/"+scriptURL); script.send(); script.onload = ()=>{ const patchedScriptRegex = /\.onMessage=function\([a-z],[a-z]\)\{/mg; const letter1 = script.response.match(patchedScriptRegex)[0].match(/[a-z](?=,)/g)[0]; const letter2 = script.response.match(patchedScriptRegex)[0].match(/[a-z](?=\))/g)[0]; let patchedScript = script.response.replace(script.response.match(patchedScriptRegex)[0],`.onMessage=function(${letter1},${letter2}){window.globalMessageListener(${letter1},${letter2});`); let mainScript = new XMLHttpRequest(); mainScript.open("GET","https://play.kahoot.it/"+script2); mainScript.send(); mainScript.onload = ()=>{ let sc = mainScript.response; let changed = originalPage.split("</body>"); changed = `${changed[0]}<script>${patchedScript}</script><script>${sc}</script><script>window.globalMessageListener=${window.PinCheckerInjector.toString()};(${window.PinCheckerMain.toString()})();try{(${window.localStorage.kahootThemeScript})();}catch(err){}window.fireLoaded = window.parent.fireLoaded = true;</script></body>${changed[1]}`; console.log("[PIN-CHECKER] - 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; }; }; }; }else{ console.warn("[PIN-CHECKER] - found AntiBot, waiting for injection"); window.localStorage.extraCheck = window.PinCheckerMain.toString(); window.localStorage.extraCheck2 = window.PinCheckerInjector.toString(); }