UNPKG

kahoot.js-latest

Version:
282 lines (256 loc) 10.6 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: util/token.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: util/token.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>const https = require("https"); const http = require("http"); const ua = require("user-agents"); class Decoder{ /** * @static requestChallenge - Requests information about Kahoot! challenges. * * @param {String&lt;Number>} pin The gameid of the challenge the client is trying to connect to * @param {Client} client The client * @returns {Promise&lt;Object>} The challenge data */ static requestChallenge(pin,client){ return new Promise((resolve, reject)=>{ function handleRequest(res){ const chunks = []; res.on("data",(chunk)=>{ chunks.push(chunk); }); res.on("end",()=>{ const body = Buffer.concat(chunks).toString("utf8"); let bodyObject; try{ bodyObject = JSON.parse(body); resolve({ data: Object.assign({ isChallenge: true, twoFactorAuth: false, kahootData: bodyObject.kahoot, rawChallengeData: bodyObject.challenge },bodyObject.challenge.game_options) }); }catch(e){ return reject(body || e); } }); } let options = { headers: { "User-Agent": (new ua).toString(), "Origin": "kahoot.it", "Referer": "https://kahoot.it/", "Accept-Language": "en-US,en;q=0.8", "Accept": "*/*" }, host: "kahoot.it", protocol: "https:", path: `/rest/challenges/pin/${pin}` }; const proxyOptions = client.defaults.proxy(options); if(proxyOptions &amp;&amp; typeof proxyOptions.destroy === "function"){ // assume proxyOptions is a request object proxyOptions.on("request",handleRequest); return; }else if(proxyOptions &amp;&amp; typeof proxyOptions.then === "function"){ // assume Promise&lt;IncomingMessage> proxyOptions.then((req)=>{ req.on("request",handleRequest); }); return; } options = proxyOptions || options; let req; if(options.protocol === "https:"){ req = https.request(options,handleRequest); }else{ req = http.request(options,handleRequest); } req.on("error",(e)=>{ reject(e); }); req.end(); }); } /** * @static requestToken - Requests the token for live games. * * @param {String&lt;Number>} pin The gameid * @param {Client} client The client * @returns {Promise&lt;object>} The game options */ static requestToken(pin,client){ return new Promise((resolve,rej)=>{ function handleRequest(res){ const chunks = []; res.on("data",(chunk)=>{ chunks.push(chunk); }); res.on("end",()=>{ let token = res.headers["x-kahoot-session-token"]; if(!token){ rej("header_token"); } try{ token = Buffer.from(token,"base64").toString("ascii"); const data = JSON.parse(Buffer.concat(chunks).toString("utf8")); resolve({ token, data }); }catch(e){ rej(e); } }); } let options = { headers: { "User-Agent": (new ua).toString(), "Origin": "kahoot.it", "Referer": "https://kahoot.it/", "Accept-Language": "en-US,en;q=0.8", "Accept": "*/*" }, host: "kahoot.it", path: `/reserve/session/${pin}/?${Date.now()}`, protocol: "https:" }; const proxyOptions = client.defaults.proxy(options); if(proxyOptions &amp;&amp; typeof proxyOptions.destroy === "function"){ // assume proxyOptions is a request object proxyOptions.on("request",handleRequest); return; }else if(proxyOptions &amp;&amp; typeof proxyOptions.then === "function"){ // assume Promise&lt;IncomingMessage> proxyOptions.then((req)=>{ req.on("request",handleRequest); }); return; } options = proxyOptions || options; let req; if(options.protocol === "https:"){ req = https.request(options,handleRequest); }else{ req = http.request(options,handleRequest); } req.on("error",(e)=>{ rej(e); }); req.end(); }); } /** * @static solveChallenge - Solves the challenge for the various Kahoot! tokens. * * @param {String} challenge The JS function to execute to get the challenge token. * @returns {String} The decoded token */ static solveChallenge(challenge){ let solved = ""; challenge = challenge.replace(/(\u0009|\u2003)/mg, ""); challenge = challenge.replace(/this /mg, "this"); challenge = challenge.replace(/ *\. */mg, "."); challenge = challenge.replace(/ *\( */mg, "("); challenge = challenge.replace(/ *\) */mg, ")"); // Prevent any logging from the challenge, by default it logs some debug info challenge = challenge.replace("console.", ""); // Make a few if-statements always return true as the functions are currently missing 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 EVAL_ = `var _ = { replace: function() { var args = arguments; var str = arguments[0]; return str.replace(args[1], args[2]); } }; var log = function(){};return `; // Concat the method needed in order to solve the challenge, then eval the string const solver = Function(EVAL_ + challenge); // Execute the string, and get back the solved token solved = solver().toString(); })(); return solved; } /** * @static concatTokens - Combines the two tokens to get the final token * * @param {String} headerToken The base64 decoded header token * @param {String} challengeToken The decoded challenge token * @returns {String} The final token */ static 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 &lt; headerToken.length; i++) { let char = headerToken.charCodeAt(i); let mod = challengeToken.charCodeAt(i % challengeToken.length); let decodedChar = char ^ mod; token += String.fromCharCode(decodedChar); } return token; } /** * @static resolve - The main function for getting token info * * @param {String&lt;Number>} pin The gameid * @param {Client} client The client * @returns {Promise&lt;Object>} The game information. */ static resolve(pin,client){ if(isNaN(pin)){ return new Promise((res,reject)=>{reject("Invalid/Missing PIN");}); } if(pin[0] === "0"){ // challenges return this.requestChallenge(pin,client); } return this.requestToken(pin,client).then(data=>{ const token2 = this.solveChallenge(data.data.challenge); const token = this.concatTokens(data.token,token2); return { token, data: data.data }; }); } } /** * @fileinfo This module contains functions for fetching important tokens used for connection */ module.exports = Decoder; </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Namespaces</h3><ul><li><a href="Client_data.html">data</a></li><li><a href="Client_defaults.html">defaults</a></li><li><a href="Client_defaults.modules.html">modules</a></li><li><a href="Client_defaults.options.html">options</a></li><li><a href="Client_defaults.wsproxy.WsProxyReturn.html">WsProxyReturn</a></li><li><a href="Client_quiz.html">quiz</a></li><li><a href="Client_quiz.currentQuestion.html">currentQuestion</a></li><li><a href="LiveEventTimetrack.html">LiveEventTimetrack</a></li><li><a href="Nemesis.html">Nemesis</a></li><li><a href="PointsData.html">PointsData</a></li><li><a href="StreakPoints.html">StreakPoints</a></li></ul><h3>Classes</h3><ul><li><a href="Client.html">Client</a></li></ul><h3>Events</h3><ul><li><a href="Client.html#event:Disconnect">Disconnect</a></li><li><a href="Client.html#event:Feedback">Feedback</a></li><li><a href="Client.html#event:GameReset">GameReset</a></li><li><a href="Client.html#event:Joined">Joined</a></li><li><a href="Client.html#event:NameAccept">NameAccept</a></li><li><a href="Client.html#event:Podium">Podium</a></li><li><a href="Client.html#event:QuestionEnd">QuestionEnd</a></li><li><a href="Client.html#event:QuestionReady">QuestionReady</a></li><li><a href="Client.html#event:QuestionStart">QuestionStart</a></li><li><a href="Client.html#event:QuizEnd">QuizEnd</a></li><li><a href="Client.html#event:QuizStart">QuizStart</a></li><li><a href="Client.html#event:RecoveryData">RecoveryData</a></li><li><a href="Client.html#event:TeamAccept">TeamAccept</a></li><li><a href="Client.html#event:TeamTalk">TeamTalk</a></li><li><a href="Client.html#event:TimeOver">TimeOver</a></li><li><a href="Client.html#event:TwoFactorCorrect">TwoFactorCorrect</a></li><li><a href="Client.html#event:TwoFactorReset">TwoFactorReset</a></li><li><a href="Client.html#event:TwoFactorWrong">TwoFactorWrong</a></li></ul><h3>Global</h3><ul><li><a href="global.html#EventEmitter">EventEmitter</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.5</a> on Sun Oct 11 2020 19:45:43 GMT-0700 (Pacific Daylight Time) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>