import fs from 'fs'; import Discord from 'discord.js'; import { botToken, discordToken } from './config.json'; const DISCORD_TOKEN = discordToken; const BOT_TOKEN = botToken; const BOT_DISCORD_CHANNEL = '463832055944445962'; const servers = [ { name: 'Trivia Community', id: '414461171974930434', channels: [ { name: 'public-hq', id: '449575067333033984', weightModifier: 0.3, }, ], }, { name: 'Trivia Party', id: '460145544225095680', channels: [ { name: 'hq', id: '460146376358101003', weightModifier: 0.2, }, ], }, { name: 'HUB', id: '452222497312604170', channels: [ { name: 'hq-trivia', id: '452223090508693515', weightModifier: 0.8, }, ], }, { name: 'Trivia Addicts', id: '447177515522588672', channels: [ { name: 'hq', id: '450174797780090901', weightModifier: 0.7, }, ], }, { name: 'Trivia Nation', id: '436303863885070357', channels: [ { name: 'quizzee-boi', id: '440663723770773506', weightModifier: 0.15, }, ], } ]; const weights = { 'standard': 10, 'apg': 5, 'not': -10, 'w': 1, '?': 3, 'apb': 1, 'its': 15, }; export default class Scraper { constructor() { this.answers = []; this.questionId = null; this.collect = false; this.weights = [0, 0, 0]; this.botClient = null; this.timer = null; this.players = {}; this.lastWeights = null; this.ticker = 3; } run() { const client = new Discord.Client(); client.on('ready', () => { console.log(`Discord Scraper... Logged in as ${client.user.tag}!`); }); client.on('message', msg => { if (this.collect) { this.scrapeContents(msg); } }); client.login(DISCORD_TOKEN); if (BOT_TOKEN) { this.botClient = new Discord.Client(); this.botClient.on('ready', () => { console.log(`Discord Scraper... Bot logged in!`); }); this.botClient.login(BOT_TOKEN); } } getWeights() { return this.weights; } endQuestion(questionSummary, answerIndex) { this.scoreUsers(answerIndex); this.saveContents(); this.collect = false; if (this.timer) clearInterval(this.timer); this.showSummary(questionSummary, answerIndex); } startQuestion(question) { const { questionId } = question; this.collect = true; this.questionId = questionId; this.answers = []; this.weights = [0, 0, 0]; this.lastWeights = null; this.ticker = 3; const showUpdate = () => this.updateAnswers(question); const delayUpdate = () => this.timer = setInterval(showUpdate.bind(this), 1000); setTimeout(delayUpdate.bind(this), 3000); this.showQuestion(question); } updateAnswers(question) { const { answers } = question; if (this.botClient) { const channel = this.botClient.channels.get(BOT_DISCORD_CHANNEL); const normalizedWeights = this.normalizeWeights(); if (channel) { channel.send(`**Normalized Weights**\`\`\`[${normalizedWeights[0]}%, ${normalizedWeights[1]}%, ${normalizedWeights[2]}%]\t\t${this.getSuggestion()}\`\`\``); // channel.send(`**Weight Deltas**\`\`\`[${differenceInWeights[0]}, ${differenceInWeights[1]}, ${differenceInWeights[2]}]\`\`\``); } } this.ticker++; } getSuggestion() { const normalizedWeights = this.normalizeWeights(); let suggestion; const majority = normalizedWeights.find(weight => weight > 66); if (majority) { switch (normalizedWeights.indexOf(majority)) { case 0: suggestion = 'Choose 1'; break; case 1: suggestion = 'Choose 2'; break; case 2: suggestion = 'Choose 3'; break; } return suggestion; } let splitChoices = normalizedWeights.filter(weight => weight >= 20); if (splitChoices.length === 3 || splitChoices.length === 0) return 'Split 1 2 3'; splitChoices = normalizedWeights.map((weight, index) => { if (weight >= 20) return index; return null; }); const nullChoices = splitChoices.filter(choice => choice === null); if (nullChoices.length < 2) { suggestion = 'Split'; } else { suggestion = 'Choose'; } splitChoices.forEach(choiceIndex => { if (choiceIndex !== null) { suggestion += ` ${choiceIndex + 1}`; } }); return suggestion; } normalizeWeights(numberOfPlayers = 100) { let nonZeroWeights = this.weights.filter(weight => weight >= 0); if (nonZeroWeights.length === 0) { nonZeroWeights = [0, 0, 0]; } let totalWeight = nonZeroWeights .reduce((accumulator, currentValue) => accumulator + currentValue); let normalized = []; normalized = this.weights.map(weight => { if (totalWeight === 0) return Math.round((1 / 3) * numberOfPlayers); const zeroNegatives = weight < 0 ? 0 : weight; return Math.round((zeroNegatives / totalWeight) * numberOfPlayers); }); return normalized; } showQuestion(question) { const { questionNumber, questionId, question: text, answers } = question; if (this.botClient) { const channel = this.botClient.channels.get(BOT_DISCORD_CHANNEL); if (channel) { channel.send({ embed: { color: 3447003, title: `Question #${questionNumber}`, description: text, fields: [ { name: 'Choice 1', value: answers[0].text, }, { name: 'Choice 2', value: answers[1].text, }, { name: 'Choice 3', value: answers[2].text, }, ], }, }); } } } showSummary(questionSummary, answerIndex) { const { answerCounts, advancingPlayersCount, eliminatedPlayersCount, question } = questionSummary; const { answerId } = answerCounts[answerIndex]; let choice1 = 'Choice 1'; let choice2 = 'Choice 2'; let choice3 = 'Choice 3'; if (answerCounts[0].answerId === answerId) choice1 = `${choice1} :ballot_box_with_check:`; if (answerCounts[1].answerId === answerId) choice2 = `${choice2} :ballot_box_with_check:`; if (answerCounts[2].answerId === answerId) choice3 = `${choice3} :ballot_box_with_check:`; if (this.botClient) { const channel = this.botClient.channels.get(BOT_DISCORD_CHANNEL); if (channel) { channel.send({ embed: { color: 3447003, title: `Summary`, description: question, fields: [ { name: choice1, value: answerCounts[0].text || answerCounts[0].answer || 'undefined', }, { name: choice2, value: answerCounts[1].text || answerCounts[1].answer || 'undefined', }, { name: choice3, value: answerCounts[2].text || answerCounts[2].answer || 'undefined', }, ], footer: { text: `${eliminatedPlayersCount} players eliminated / ${advancingPlayersCount} players advancing`, }, }, }); } } } scoreUsers(answerIndex) { const correctAnswer = answerIndex + 1; this.answers = this.answers.map(userAnswer => { const { answer, userId } = userAnswer; const { pre, number } = answer; const negated = pre ? pre === 'not' : false; let correct = false; if (negated) { correct = number !== correctAnswer.toString(); } else { correct = number === correctAnswer.toString(); } if (!this.players[userId]) this.players[userId] = 1; if (correct) { this.players[userId] += 1; } else { this.players[userId] -= 5; } return { ...userAnswer, correct, } }); } scrapeContents(msg) { const { channel, guild } = msg; if (!guild || !channel) return; const validServer = servers.find(({ id }) => id === guild.id); if (!validServer) return; const validChannel = validServer.channels.find(({ id }) => id === channel.id); if (!validChannel) return; const { author, content } = msg; const { id: userId, username } = author; const cleanedMessage = content.trim().replace(' ', '').replace('\'', '').toLowerCase(); const matchedMessage = /((w|not|its)?([123])(\?|apg|apb)?)/g.exec(cleanedMessage); if (!matchedMessage || matchedMessage.length === 0) return; const fullAnswer = matchedMessage[0]; const answer = { pre: matchedMessage[2], number: matchedMessage[3], post: matchedMessage[4], }; let weight = this.weightAnswer(userId, answer); weight = weight * validChannel.weightModifier; weight = weight * this.ticker; this.logAnswer(userId, username, answer, weight); /* This is for debugging. You don't want this during game. console.log(`\n${validServer.name} => #${validChannel.name}`); console.log(`${username}: ${fullAnswer}`); console.log(`This answer carries weight modifier: ${weight}`); */ } weightAnswer(userId, answer) { let weight = 0; const { pre, post } = answer; const player = this.players[userId]; if (player) { weight += parseInt(player); } if (pre) { weight += weights[pre] || 0; } if (post) { weight += weights[post] || 0; } if (!pre && !post) { weight += weights['standard']; } return weight; } logAnswer(userId, username, answer, weight) { const userAnswer = { userId, username, answer: { ...answer, }, }; const { number } = answer; if (['1','2','3'].includes(number)) { this.weights[number - 1] += weight; } this.answers.push(userAnswer); } saveContents() { fs.readFile('playerAnswers.json', 'utf8', (err, data) => { if (err) { console.log('Error reading playerAnswers for writing: ', err); } else { try{ const playerAnswers = JSON.parse(data); playerAnswers.playerScores = this.players; if (!playerAnswers.answers) playerAnswers.answers = {}; playerAnswers.answers[this.questionId] = this.answers; const playerAnswersJSON = JSON.stringify(playerAnswers, null, 2); fs.writeFile('playerAnswers.json', playerAnswersJSON, 'utf8', (err, data) => { if (err) { console.log('Error saving playerAnswers: ', err); } else { // console.log(`Saved results for question ${TEST_QUESTION_ID.toString()}`); } this.answers = []; }); } catch(error){ console.warn(error); } } }); } }