educ-bot/index.js

254 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 || '8080' })
wss.on('connection', ws => {
ws.isAlive = true;
ws.on('pong', heartbeat);
ws.on('message', message => {
var data;
try {
data = JSON.parse(message)
} catch (e) {
console.log(e)
return
}
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 lon 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 mutiliser. 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 + '&ws_port=' + (process.env.EXT_WS_PORT || process.env.WS_PORT || '8080'))
}
/* 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) {
//TODO est-ce quun nickname vide aura la valeur username ?
educpopDelPerson(channel, msg.member.nickname || msg.member.username, content.toLowerCase())
msg.delete()
reply(channel)
}
}
else if (msg.content.toLowerCase() in reactions) {
var reaction = msg.content.toLowerCase()
educpopAddPerson(channel, msg.author.nickname || msg.author.username, 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 sest 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);