/* Run dotenv */ require('dotenv').config(); const fs = require('fs'); /* Catch evry unhandled promise rejection */ process.on('unhandledRejection', error => console.error('Uncaught Promise Rejection', error)); /* Deep clone of objects */ const clonedeep = require('lodash.clonedeep') /* Generate random string for token */ const randomstring = require("randomstring"); /* Liste des réactions possibles par défaut et de leurs représentation actuelle */ const defaultReactions = JSON.parse(fs.readFileSync('defaultReactions.json', 'utf8')); /* The html page will render data passed in WS */ const WebSocket = require('ws') const wss = new WebSocket.Server({ port: process.env.WS_PORT }) wss.on('connection', ws => { ws.isAlive = true; ws.on('pong', heartbeat); ws.on('message', message => { const data = JSON.parse(message) if (!('channel' in data && 'action' in data && 'web_token' in data)) { ws.send('{"error":"invalid request"}') return } else if (!(data['action'] in wsActions)) { ws.send('{"error":"bad action"}') return } else if (!(data['channel'] in channels && channels[data['channel']].web_token === data['web_token'])) { ws.send('{"error": "bad channel"}') return } else { wsActions[data['action']](data, channels[data['channel']], ws) } }) }) function heartbeat() { this.isAlive = true; } const interval = setInterval(function ping() { wss.clients.forEach(function (ws) { if (ws.isAlive === false) return ws.terminate(); ws.isAlive = false; ws.ping(()=>{}); }); }, 30000); wss.on('close', function close() { clearInterval(interval); }); // TODO penser à prévenir le web quand un type de réaction est ajoutée/supprimée const wsActions = { 'init': wsInit, 'add_reaction': wsAddReaction, 'remove': wsRemoveReaction, 'reset': wsReset, } function wsInit (data, channel, ws) { channel.ws_clients.push(ws) wsSendState(channel, ws) } function wsSendState(channel, ws) { ws.send(JSON.stringify(channel.reactions)) } function wsSendStateAll(channel) { for (var index in channel.ws_clients) { wsSendState(channel, channel.ws_clients[index]) } } function wsAddReaction (data, channel, ws) { if (!('reaction' in data)) { ws.send('{"error":"No reaction supplied", "action":"add"}') } } function wsRemoveReaction (data, channel, ws) { if (!('reaction' in data)) { ws.send('{"error":"No reaction supplied", "action":"add"}') } } function wsReset (data, channel, ws) { } /* Liste des channels où on peut lire avec le dernier message que l’on y a envoyé */ var channels = {} function educpopReset (channel) { for (var index in channel.reactions) { channel.reactions[index].people = [] } } function educpopAddChannel (discordChannel) { channels [discordChannel.id] = { 'last_msg': null, /* On se souvient du dernier post que ce bot a envoyé pour le virer dès que possible */ 'discord_channel': discordChannel, 'ws_clients': [], 'web_token': randomstring.generate(), /* web auth */ 'reactions': Object.assign({}, clonedeep(defaultReactions)), 'pause': false, } educpopReset(channels[discordChannel.id]) } function educpopDelChannel (channel) { delete channels[channel.discord_channel.id] } function educpopPause (channel) { channel.pause = true } // TODO wait for database before using this function educpopAddReaction (channel, word, prefix, description) { channels[channelId].reactions[word] = {'prefix': prefix, 'description': description, 'people':[]} } function educpopDelReaction (channelId, word) { delete channels[channelId].reactions[word] } function educpopAddPerson (channel, username, reaction) { var liste = channel.reactions[reaction].people if (liste.indexOf(username) < 0) { liste.push(username) } } function educpopDelPerson (channel, username, reaction) { var liste = channel.reactions[reaction].people var index = liste.indexOf(username) if (index >= 0) { liste.splice(index, 1) } } /* Connexion à discord */ const Discord = require('discord.js'); const client = new Discord.Client(); client.on('ready', () => { console.log('Connected to Discord as ' + client.user.tag); client.user.setAvatar('avatar.png') client.user.setUsername(process.env.BOT_USERNAME) }); /* Discord message center */ client.on('message', msg => { //TODO //const prefixRegex = new RegExp(`^(<@!?${client.user.id}>|${escapeRegex(prefix)})\\s*`); //if (!prefixRegex.test(message.content)) return; if (msg.content === '!educpop-enable') { if (msg.channel.id in channels) { msg.reply('J’écoute déjà ce canal texte.') } else { educpopAddChannel(msg.channel) msg.reply('J’écoute maintenant ce canal texte.') } return } if (msg.content === '!educpop-disable') { var id = msg.channel.id if (id in channels) { educpopDelChannel(id) msg.reply('Je n’écoute plus ce canal texte.') } else { msg.reply('Je n’écoutais pas ce canal texte :o') } return } if (msg.content === '!educpop-help') { msg.reply('Voilà comment m’utiliser. Taper :\n!educpop-enable pour activer le bot sur ce salon ;\n!educpop-disable pour le désactiver ;\n!educpop-list pour voir la liste des réactions possibles ;\n!educpop-reset pour remettre les compteurs à zéro.') return } /* Listen only to enabled channels */ if(!(msg.channel.id in channels)) { return } var channel = channels[msg.channel.id] var reactions = channel.reactions if(msg.content === '!educpop-reset') { educpopReset reply(channel) } else if (msg.content === '!educpop-list') { var text = 'Tapez simplement le mot-clé ci-dessous pour être comptabilisé. Tapez un - immédiatement suivi du mot-clé pour être retiré du compte : -oui par exemple !' for (var index in reactions) { text += '\n' + reactions[index].prefix + index + ' : ' + reactions[index].description } msg.reply(text) } else if (msg.content === '!educpop-web') { msg.reply('https://educbot.jean-cloud.net?channel_id=' + msg.channel.id + '&web_token=' + channel.web_token) } /* save and ignore own messages */ else if(msg.author.username === process.env.BOT_USERNAME){ if (!msg.content.startsWith('<')) /* Save if its educpop summary */ channel.last_msg = msg; } /* Educ pop stuff */ else if(msg.content.startsWith('-')) { const content = msg.content.slice(1) if (content.toLowerCase() in reactions) { educpopDelPerson(channel, msg.member.nickname, content.toLowerCase()) msg.delete() reply(channel) } } else if (msg.content.toLowerCase() in reactions) { var reaction = msg.content.toLowerCase() educpopAddPerson(channel, msg.member.nickname, msg.content.toLowerCase()) msg.delete() reply(channel) } }); /* Recap educ-pop state on discord. Delete action message and last educ-pop recap */ /* This function refresh the display */ function reply (channel) { var text = '' for (var index in channel.reactions) { if (channel.reactions[index].people.length > 0) text += '\n' + channel.reactions[index].prefix + String(channel.reactions[index].people) } if (text === '') { text = 'Personne ne s’est manifesté :/' } if(channel.last_msg) channel.last_msg.delete() channel.discord_channel.send(text) wsSendStateAll(channel) } /* Actual discord login */ client.login(process.env.DISCORD_TOKEN);