kahoot.js-latest
Version:
Answer, Join Kahoot Quizzes with nodejs
282 lines (256 loc) • 10.6 kB
HTML
<!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<Number>} pin The gameid of the challenge the client is trying to connect to
* @param {Client} client The client
* @returns {Promise<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 && typeof proxyOptions.destroy === "function"){
// assume proxyOptions is a request object
proxyOptions.on("request",handleRequest);
return;
}else if(proxyOptions && typeof proxyOptions.then === "function"){
// assume Promise<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<Number>} pin The gameid
* @param {Client} client The client
* @returns {Promise<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 && typeof proxyOptions.destroy === "function"){
// assume proxyOptions is a request object
proxyOptions.on("request",handleRequest);
return;
}else if(proxyOptions && typeof proxyOptions.then === "function"){
// assume Promise<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 < 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<Number>} pin The gameid
* @param {Client} client The client
* @returns {Promise<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>