Files
tinagtg/quiz.js
2018-10-27 07:21:42 -05:00

343 lines
17 KiB
JavaScript

import ws from 'ws';
import fs from 'fs';
import Discord from 'discord.js';
import HttpsProxyAgent from 'https-proxy-agent';
import Logger from 'simple-node-logger';
const config = JSON.parse(fs.readFileSync('config.json'));
const log = Logger.createSimpleLogger('game.log');
log.setLevel('debug');
const client = new Discord.Client();
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
class Player {
constructor({ objectId, username, proxy }) {
this.objectId = objectId;
this.username = username;
/* Post Initialization Variables */
this.isMaster = false;
this.socket = null;
this.pingTimeout = null;
this.pingInterval = null;
this.sid = null;
this.game = null;
this.controller = null;
this.actionIndex = 0;
this.lastAnswerIndex = -1;
this.agent = proxy ? new HttpsProxyAgent(proxy, { secureProxy: true }) : null;
}
cleanPrint(message) {
if (this.isMaster) {
log.info(message);
}
}
connect() {
const options = {
agent: this.agent,
};
this.socket = new ws(`${config.PROD_SOCKET_URL}/socket.io/?EIO=3&transport=websocket`, options);
this.socket.on('open', this.handleOpen.bind(this));
this.socket.on('close', this.handleClose.bind(this));
this.socket.on('message', this.handleMessage.bind(this));
}
ping() {
try {
//this.socket.ping();
this.socket.send('2');
this.pingTimeout = setTimeout(() => {
this.socket.terminate();
log.error(`Player ${this.username} connection closed. Attempting to reconnect...`);
this.connect();
}, 8000);
} catch (error) {
log.error(`Player ${this.username} connection closed. Attempting to reconnect...`);
this.connect();
}
}
pong() {
clearTimeout(this.pingTimeout);
}
handleOpen() {
this.pingInterval = setInterval(this.ping.bind(this), 8000);
}
handleClose() {
clearInterval(this.pingInterval);
clearTimeout(this.pingTimeout);
}
async handleMessage(message) {
if (this.isMaster) {
log.debug(message);
}
const parseMessage = () => {
const extractedData = /\[(.*?)\]$/.exec(message);
if (!extractedData) return null;
let messageJSON = null;
try {
messageJSON = JSON.parse(extractedData[0]);
} catch (error) {
messageJSON = JSON.parse(extractedData[1]);
}
return messageJSON;
}
// maybe a bit hackish? but works for every example I see so far and cleaner.
const msgCode = parseInt(message);
const data = parseMessage();
switch (msgCode) {
case 0:
const parsedJSON = JSON.parse(message.slice(1));
this.sid = parsedJSON.sid;
break;
case 40:
this.sendAction('GAME_JOIN', { userId: this.objectId });
break;
case 430:
if (data) {
log.info(`Player ${this.username} has connected!`);
this.sendAction('GAME_GET_STATUS', { userId: this.objectId });
this.sendAction('TIME_SYNC', null);
} else {
log.error(`Player ${this.username} failed to connect!`);
}
break;
case 431:
if (data && data[0] && data[0].game) {
this.game = data[0].game;
if (this.game.currentState === 'GAME_LOBBY' && this.isMaster && config.PUSH_DISCORD) {
this.showQuestions();
}
}
break;
case 42:
const type = data[0];
switch (type) {
case 'GAME_CLIENT_COUNT_UPDATE':
// Text Sat Oct 20 13:10:22 CDT 2018 Sat Oct 20 13:10:22 CDT 2018 42["GAME_CLIENT_COUNT_UPDATE",15]
let online = data[1];
if (this.isMaster) {
const totalAccounts = this.controller.players.length;
const percent = Math.round(totalAccounts / parseInt(online) * 100);
this.cleanPrint(`There are currently ${online} players online! You make up ${percent}% of them.`);
}
break;
case 'GAME_TABULATION_RESULTS':
const results = data[1];
const { answerCorrectIndex } = results;
if (this.lastAnswerIndex !== answerCorrectIndex) {
log.info(`${this.username} has been eliminated!`);
this.remove();
}
case 'GAME_UPDATE':
const game = data[1];
if (!game) return;
switch (game.currentState) {
case 'GAME_QUESTION':
if (this.isMaster) {
this.cleanPrint(`Now on Question ${game.currentQuestionIndex + 1}!`);
await delay(Math.random() * 3000 + 1000);
this.controller.sendAnswers(game.currentQuestionIndex);
}
break;
}
// let showQuestions = false;
// if (game && game.questions) {
// if (this.game && this.game.questions) {
// if (game.questions.length !== this.game.questions.length) {
// showQuestions = true;
// }
// } else {
// showQuestions = true;
// }
// }
// this.game = game;
// if (this.isMaster && showQuestions) {
// this.showQuestions();
// }
break;
}
break;
case 3:
this.pong();
}
}
showQuestions() {
const { questions } = this.game;
try {
questions.forEach((question, index) => {
const embed = new Discord.RichEmbed();
const type = question.type.toLowerCase();
embed.setTitle(`Question ${index + 1}: ${question.text}`);
embed.setDescription(`WRONG: ${question.answerWrongText}\nRIGHT: ${question.answerRightText}`);
question.answers.forEach((answer, index) => {
const answerText = (type === 'text') ? answer.text : answer.imageSrc;
embed.addField(`Choice ${index + 1}`, answerText, false);
});
const channel = client.channels.get(config.discord_channel);
channel.send({ embed }).then(async (message) => {
await message.react('504045972217659432');
await message.react('504045973241331712');
await message.react('504045972045955073');
await message.react('504045971878051851');
await message.react('504045972419117057');
this.controller.questions.push(message.id);
});
});
} catch (error) {
log.error('Question format was invalid.');
log.debug(JSON.stringify(questions));
}
}
sendAnswer(answer) {
this.sendAction('GAME_USER_SELECT_ANSWER', { index: answer - 1});
log.info(`${this.username} sent answer ${answer}.`);
this.lastAnswerIndex = answer - 1;
}
sendAction(type, data) {
this.socket.send(`42${this.actionIndex++}["${type}",${JSON.stringify(data)}]`);
}
remove() {
if (this.socket) {
this.socket.terminate();
this.socket.close();
}
this.handleClose();
this.controller.removePlayer(this.objectId);
}
}
class Game {
constructor() {
this.players = [];
this.questions = [];
this.answers = [];
}
run() {
fs.readFile('accounts.json', (err, data) => {
if (err) throw err;
const { accounts } = JSON.parse(data);
this.players = accounts.map(this.createPlayer.bind(this));
this.players[0].isMaster = true;
this.players.forEach(player => player.connect());
});
}
createPlayer(account) {
const player = new Player(account);
player.controller = this;
return player;
}
sendAnswers(questionIndex) {
const answer = this.answers[questionIndex] || 'split';
if ([1, 2, 3, 4].includes(answer)) {
log.info(`Sending answer ${answer} for Question ${questionIndex + 1}.`);
this.players.forEach(player => player.sendAnswer(parseInt(answer)));
} else {
log.info(`Splitting answers for Question ${questionIndex + 1}.`);
this.players.forEach((player, index) => {
const choice = (index % 4) + 1;
player.sendAnswer(choice);
});
}
// const channel = client.channels.get(config.discord_channel);
// channel.fetchMessage(this.questions[questionIndex]).then(message => {
// if (!message) {
// log.error(`Couldn't grab message from Discord for Question ${questionIndex + 1}.`);
// } else {
// const { reactions } = message;
// const { emoji } = reactions.reduce((a, b) => (a.count > b.count) ? a : b);
// const { name } = emoji;
// const answer = name.replace(/_|:/g, '');
// if (['1', '2', '3'].includes(answer)) {
// log.info(`Sending answer ${answer} for Question ${questionIndex + 1}.`);
// this.players.forEach(player => player.sendAnswer(parseInt(answer)));
// } else {
// log.info(`Splitting answers for Question ${questionIndex + 1}.`);
// this.players.forEach((player, index) => {
// const choice = (index % 4) + 1;
// player.sendAnswer(choice);
// });
// }
// }
// });
}
removePlayer(objectId) {
this.players = this.players.filter(player => player.objectId !== objectId);
}
checkWinners(winnersList) {
fs.readFile('accounts.json', (err, data) => {
if (err) throw err;
const { accounts } = JSON.parse(data);
const myPlayers = accounts.map(account => account.objectId);
const myWinners = winnersList.reduce((all, winner) => {
if (myPlayers.includes(winner)) all.push(winner);
return all;
}, []);
console.log(`${myWinners.length} of your players won this game!`);
});
}
}
// const game = new Game();
// game.checkWinners(["00qCow1LFJ","041C0EHK7x","06R7kKoXiD","09gdPQJA80","0EISev10kk","0EQG4clVGl","0IN3yM8UG4","0PP4bBGvAL","0QRJPj8PJo","0TRuUmNdeK","0VDgNqCodo","0aGkAIwG8l","0dojS78GUR","0iM9BjMXxx","0lcrMt5c6q","137MBl356s","13kz92ff10","18j3dIPgGX","1E8MYi50MK","1NXBVcsODD","1lW1zUKjfC","1tHNyAHeQ6","1u7C3vyJkl","1xbw4CTpWm","27u0huHf26","28VywysmOw","2DXq94ZFHT","2KyTSFUtUH","2PuhjnqeqX","2dVNrw6jZq","2lFaIdIUiO","2qa8Jk5CKo","2sEGb3Ts3T","3HJCEPPiCd","3TvxiQQuYM","3nUqcJ7PWC","3veZSxERgh","46rCI9rC8R","4Ad7gzNJCT","4TsDh6BPQJ","4YmMj38hBw","4g1XUPh1PO","4llhmzpy7C","4mfxtiP8mN","58dXHhhNS8","5ALmhp0gdw","5DEZtGEjEY","5hx1gSPwi3","5pk2E5MRl2","5vwKYdS1fU","5xBB8cFHOd","6A6cc8MHIm","6MfR3YFN7e","6VTAPPvT0L","6b1R1u2AN0","6lvEM0nLdn","6tBTvr0B2T","7Fo26vgQgP","7LIANQKAcD","7jXd0vtmF4","7jofCPj7rW","7qHFzHbuEm","7y5bpUCR4W","7yFs2XXqRo","88H1tptJ2b","8DPGXVFMqq","8FbjqOfnzx","8Il2AUAjad","8SHLaBlDq1","8SpNS3GLUl","8VQGqAPgmJ","8eJNrS37gE","8o4vyuakmt","92LlP0DkBG","93T7bK1COD","9EXGO0Ij33","9J5QCNf4HH","9Rqkgs0Ap8","9TdL2yF1I8","9Y0MAGrZgp","9YgD8AvhUO","9dAyyvPg3f","9hqJpfeBDO","9pnHq7bZrk","9rlv4UPzTh","A6iwkZgRZa","A7HZsMayzA","ADfMSSWFHv","AF0J5QYdUJ","AJeQMC2nFb","AhN90xC4CO","ApfmfKJyiE","AputfnSphm","B1qHnUy3TD","B5dfkDTowV","B6uBLT7Usp","BDN5Hxxovg","BKrgKZfKh7","BUgbm5bfNI","BaWOvguGth","BgHQGarYjZ","BiaLKkDR1x","BlUv7dufpU","BlcJiyT7ZB","C1rPwqXRXX","C1uLGu6PhK","C4LmZaslJf","C5H2FMmk8G","CE0jWkUNFB","CGKdLJE1RY","CMA4bogSp5","CNtF4oKVvA","CXK4OmqLHW","CYsfEgFenR","CePbsFBnBK","CggdZvryeA","CoHbGUeWfe","CuTdmXoFBb","CwMF4TT8xB","D5KMInPRBf","DC7viOCzqC","DXQ13P7inM","DXU54Q5FN0","DmtUGaVbg0","DtVJZ4pZsn","Dv2kVfMO6F","E3xjLCBdDI","E4yqftD3P8","EAmO5hVJk8","EfOaTFwfW9","EhTwhUQnyd","EoA0RaH8rT","EpXmgVVJ2A","EqEaSC3PqR","Eu8yim7dBv","EzzHlhPwNa","F4EELW3gRe","FLSBC8u1wW","FQBYN9AvwP","FQDQibu1Zt","FShrCDlUY0","FUhcQwI0wg","FbD275APM7","FiyHPxLaDa","FjiDmdcBfS","GGHxaHOv9p","GOyVv5Qc1x","Gm6tIubbT2","GyMlfSMff0","GzAvu1nQBG","H0RC3keQOh","H9ud3D7yeN","HBvAwGrsKp","HhqRcfqmJA","Hi65QtsIYJ","HjKd81IMWQ","HzgmUdX5P5","IAjrUj2Ho7","IDf8LggAGT","IQ1ly4Dw3n","ISOAh8U4WN","IYpMAC9Vz0","IjgWsMSvOV","IvBlNfNk2G","J7xlwCiGXG","JFNNbSlU7I","JISbSG47Mc","JLjShPl0aQ","JOadAqPdoc","JRjGXIJTOs","JRjxyWfdtj","JXe0Rv4z2e","JaapBB0b2i","JeEzC0Yc3d","JjHs7BKwHV","K61cKs8aYL","KBpLreA8jo","KKH5GUgiwy","KWlVoWH7e3","KfoaZ2Xg8R","KlWYqABeIy","KpAG52fXYW","KtHEDi5En6","KweG76Bamf","L1PAUC1aBn","LAWxgFjlWn","LB1FeMtmRA","LJBKOViLXr","LLsdmtK6iU","LiyJrFkzrz","LlnehWchms","LuWnsq80bK","Lxql9Ntm8n","M8pEtnKNib","MQ78XBI5Zm","MZlJ9vvyab","MfOXhCA8jR","MlmxnRFxMA","Mp9ND2mShb","MqYcRNTvek","MsD5OpBmll","MzMUhisXiu","N7vvDY1tRY","N8pFk8aOH7","N9J5CTG7Ep","N9ZHeP1K9h","NXpHXHh5Jx","NpLaFVHlb4","NwoCkXOEqa","O19jCXWtF9","O1fGwa76Vl","O53mHosQux","O8oI3vG3JK","OJmQyPhjtI","ORZiTBEsL6","OeMvrvPWgp","Oo4u8QpXU5","OwC7DjVCip","PEgvtPqeAm","PF0o5v1HR5","PGdUWVFy5B","PI549iCXcY","PUm0MLuhW3","PXlKlCYbwl","PeIHKA1D9D","PlcSvDPflc","PrqQV3Yr0F","PyNIZkaSEj","Q28t6hFdwD","Q3rARbx4Qb","Q6Oiw5lYhm","QAB4YWl8P0","QATnsafZv5","QEFvVpbbt0","QMAeWtFBrR","Qo38pSDlRZ","QoApbx2i93","Qq3a1Y5zKN","QyG3VBTO2A","Qz5DB1CH18","R07O9vBNca","RHJbHC3yW3","RQz2tp3MeF","RRTDBHosXy","Rp4SPUHbyk","RvEzdR9oNg","Rvby8s8nwS","S9lyMZq1JB","SSWDYOdqZq","SVMbaiWh1f","SWhvOOaiYJ","SXaTLOH8kB","SYxFkpyUAj","Skrp6TapJh","SpijHA1uQe","St2m67s9AS","T2zYteadvT","TLQWoGfc1h","TNtAzPg9f7","TOE4erhGUG","TPp5FSsi98","Tb3oUTK7Fg","TcOXHzVh1v","Tf4e14dV94","ThnuXwSsSr","TiCEIosOJF","TtIiq9H0hZ","TyYRPJaByH","U3KYi7MxqI","U3fecPaTDV","Ugb3UOLGBL","UlGOGtzOqH","V6lKGlA1Lx","VDXPtmhwqI","VQ0H4JlWoW","Vd4frLHsw6","VePnu2iCjG","Vf3lHeXawd","VjVtTAqRNg","VnUJtdoEHP","W1DGbDFSxP","W2yJ2g3Odj","WFPSmEPnj5","WQyATVLiCs","WettdQAXyn","WjF1ue19gC","Wvgghvu77e","XH07d9c7ra","XJ3pesidkg","XJwUGVuvt0","XeljwvGfA9","XkwvuBdddJ","XqTlSPJLaR","YHestlYmK0","YNsQecRKON","YPsymHBp4X","YWnIQcQZLk","YWrRgYmCBa","YeD7088rds","Yf3q2rIFz8","Yz2v1BMkfp","ZEuQn0nMCP","ZIMYD4lhVk","ZMar4bAByN","ZMgsZsbpjs","ZZH9oN6aRc","ZfkT7C1dOd","aAqUs6qgfU","aBPoMI6ktc","aCY4QtGfr0","aLdms8bY3F","aPlrnSggBM","aRo548nHcD","aS7Ddkgh0W","aV6atP7Lb3","aWCrOtIckk","aZjbUQCvRw","atFbfeYaPI","auYSsXAdLJ","awRA29fASB","azKpkjPfgf","b6NBz4QDWc","bFhZgVGOQF","bMbrJQuQwU","bQLLsIUdId","bgJpOqWNEm","bhB9NfEn4Q","btaSjKdIti","bwG5JFGgw0","c43d8B4DWx","c4QMhwi7hl","cMiSQvOee8","cOZMSy6pg8","cjIasBq1Ru","cm99Zunepw","cmnwLPFQGM","cqBr9k2fDC","cz3cGZh2v5","d08Mhx7kFw","d0wd5kWDlB","dOS9WukBHz","dVroVyI0Mx","do14H6XxOQ","eCpszvJ7Ml","eYmBn7FIWh","erNQam7CSr","f0yZ9CGUna","f4f66pPykG","fBpk7YuuGo","fMwPeZKhMG","fV5vFv9npZ","fzqNumNP8n","g3WHbGXE1X","g5d0iDtF1r","g6DxzsxPPm","g6PpMUASlU","g7pGJveQQD","g9uQdkvSWw","gJVAfP98Xa","gTHgMU0nP7","gf0sC1qcwu","gnYFmdh2WB","gsGowAL5BS","gsN7rit6OF","gwxHYHNGGF","h149EkeoK2","h1yi0BI81s","hHKLWhbzOp","hMVtlk6lJt","hOkPMxhk4y","hZbD0hTpAo","hdxj1U8V1F","hrkSCtl0Am","hunpcKiXCk","hvzxCkFKYt","i4CwsgQHKp","i8bnoEqFJx","iG7B3gPsbc","iHWy3HvsAM","iHp1n781aM","iOxzA775E0","iZ12aSVHaa","ieW7Dl6GgP","ikBwArxR3Q","isDVUTHEdc","itZ89qP7Ss","jDaK2DOnLA","jLuLYttKCP","jXKlxgQazX","jZAy8PMBah","jdGzhXXSvH","kDZDekuE0a","kDqi87cCvk","kKYlCn2LgA","kLfOR2hgox","kSIH7Rxufo","kjJ1gOOc4r","knV0Ak3CkA","kr4grJG47h","ksBQCIBpgU","lofydwZaeG","lwZ7zTKUwz","lxX1EcRdc7","mAXQZNEVDZ","mArVLzYxC0","mEVQU5jHYW","mYAW25fNdg","mgtslRVbWG","mjNBn8sHKN","moy5CF0ZEE","mslev375DK","nUwj4nrCEv","nYZs6MYT1c","nbr1ts3T8Z","nl7dlBbOs7","ntD42X2jtK","o19Q4iJYXr","oAYs0OWRnK","oQzYxnGbiN","oTQhAl44Jq","ob4rUlaFjm","oe2i3SNvhV","ok4nBumsZL","owiAwMPfhB","p3iYOXtuzn","p4c5cgCWX8","pF0HyoYVnD","pJxftb0Ru8","pTQJT1LniA","peVci8Am8g","psFsnVZ5sT","pw8FVMihTL","q2ZST3DuU4","q8jprsuBFf","q94x4364zL","qAyhoaFTec","qH093rcs4T","qII47L8Bgy","qKGAQA5Vhs","qOZW5ZaOQs","qOuKys5Q7B","qT8PHJ4kji","qTnaKJbtSn","qVzHZ8gcDl","qffAEa3ocx","qkVtK21SER","rR2MDCEQLR","rSUr9uhbG2","rTr5um1axq","rZRUmFK5ad","rbqRmZcURu","rok2uhdjzN","rpEdcNzvXw","s4c01uE7Ni","s9jkRcPTE3","sB0BLdy1AU","sRnjoacivu","sU7As6cC74","sW5BAVUL7C","sWdO1EKE2R","sXufm4Ae9B","sr2NJ0Htc7","suYYkSIXTj","tGQnUTczZm","tHVWDyrGc3","tNdYzzk4tB","tSxeHCFtUM","tiFRYbUtHX","u4L5leCSFn","uABdEVkPZu","uFtLFtYB2K","uGalnam0dC","uHDjDWluky","uP1t1qg6iw","uPjqWrxj6l","uPs3gwB1hh","udxZoQXnOT","ukFJ5uTCcc","umkaGr0PWP","urAYXgfMH3","uyZvtc5xaC","v4gejgXCab","v6O0p11W8C","v7wR0Od81u","v95RS8e99w","vATJeY7h7q","vK3WvoXB6d","vONqNCHM9z","vPGPmD7LgO","vVeH0AZVbC","vXUOf8fId3","vXaAkGLQXr","vlfcc2yJ36","vqGkPg78y5","vsLEqwYFpL","vwkRohm96G","vzhkphupuv","wqo7Ahjp7U","wxmAkF089e","x418FNA7LN","xCzHr4x8fa","xEt66QqB4K","xHzwYjqXTD","xILOZa12CP","xSo5Ugx3xl","xSzhBKe61N","xXgLZtPO8E","xiuReusU2t","xmUb28Quwz","xwgfSIOdgx","yNXA0PDpEy","yW0S0fPd0M","yfyZErONIc","yn1BTGwOg2","yql6e8AB5M","ytyYKXduYK","yxu70vFHTu","yyOG9b8dJF","zOzy3VuaTa","zbekfH5wk3","zfChJGkf4W","zmaMNimTSz","zmpcjhhtHc","zqxehrAJgk","zrRcQ6RYxd","zxrl3WHA14"]);
const game = new Game(log);
client.on('ready', () => {
// This event will run if the bot starts, and logs in, successfully.
console.log(`Discord module started with ${client.users.size} users.`);
client.user.setActivity('Samantha Bee');
});
client.on('message', (msg) => {
const { channel, guild } = msg;
if (!guild || !channel) return;
const { author, content } = msg;
const { id } = author;
if (id !== config.accept_userid) return;
if (content.startsWith('!answers')) {
const answers = content.replace('!answers ', '');
if (answers.length > 0) {
try {
const parsedAnswers = JSON.parse(answers);
if (parsedAnswers.length !== 10) {
msg.channel.send(`You only sent ${parsedAnswers.length} answers. Need exactly 10!`);
} else {
game.answers = parsedAnswers;
msg.channel.send('Answers have been set! Time to have a smarty party over here!');
}
} catch (error) {
msg.channel.send(`Answers weren't written in a proper array structure!`);
}
}
}
});
client.login(config.discord_token).then(() => {
game.run();
});