2020-03-31 13:02:48 +00:00
/* Run dotenv */
require ( 'dotenv' ) . config ( ) ;
const fs = require ( 'fs' ) ;
2020-03-24 15:01:15 +00:00
2020-04-02 16:01:29 +00:00
/* Catch evry unhandled promise rejection */
process . on ( 'unhandledRejection' , error => console . error ( 'Uncaught Promise Rejection' , error ) ) ;
2020-03-31 13:02:48 +00:00
/* Deep clone of objects */
const clonedeep = require ( 'lodash.clonedeep' )
2020-03-24 15:01:15 +00:00
2020-03-31 13:02:48 +00:00
/* 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 )
2020-03-31 18:59:36 +00:00
wsSendState ( channel , ws )
}
function wsSendState ( channel , ws ) {
2020-03-31 13:02:48 +00:00
ws . send ( JSON . stringify ( channel . reactions ) )
2020-03-30 18:55:12 +00:00
}
2020-03-31 18:59:36 +00:00
function wsSendStateAll ( channel ) {
for ( var index in channel . ws _clients ) {
wsSendState ( channel , channel . ws _clients [ index ] )
}
}
2020-03-31 13:02:48 +00:00
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 ) {
}
2020-03-30 18:55:12 +00:00
/* Liste des channels où on peut lire avec le dernier message que l’ on y a envoyé */
var channels = { }
2020-03-31 13:02:48 +00:00
function educpopReset ( channel ) {
for ( var index in channel . reactions ) {
channel . reactions [ index ] . people = [ ]
}
}
2020-03-30 18:55:12 +00:00
2020-03-31 13:02:48 +00:00
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 ] )
}
2020-03-24 15:01:15 +00:00
2020-03-31 13:02:48 +00:00
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 )
2020-03-24 15:01:15 +00:00
}
2020-03-31 13:02:48 +00:00
}
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 ) ;
2020-04-02 15:30:53 +00:00
client . user . setAvatar ( 'avatar.png' )
2020-04-02 17:44:09 +00:00
client . user . setUsername ( process . env . BOT _USERNAME )
2020-03-31 13:02:48 +00:00
} ) ;
/* Discord message center */
client . on ( 'message' , msg => {
2020-04-02 16:01:29 +00:00
//TODO
//const prefixRegex = new RegExp(`^(<@!?${client.user.id}>|${escapeRegex(prefix)})\\s*`);
//if (!prefixRegex.test(message.content)) return;
2020-03-31 13:02:48 +00:00
if ( msg . content === '!educpop-enable' ) {
if ( msg . channel . id in channels ) {
2020-03-30 18:55:12 +00:00
msg . reply ( 'J’ écoute déjà ce canal texte.' )
} else {
2020-03-31 13:02:48 +00:00
educpopAddChannel ( msg . channel )
2020-03-30 18:55:12 +00:00
msg . reply ( 'J’ écoute maintenant ce canal texte.' )
}
2020-03-31 13:02:48 +00:00
return
2020-03-24 15:01:15 +00:00
}
2020-03-31 13:02:48 +00:00
if ( msg . content === '!educpop-disable' ) {
2020-03-30 18:55:12 +00:00
var id = msg . channel . id
if ( id in channels ) {
2020-03-31 13:02:48 +00:00
educpopDelChannel ( id )
2020-03-30 18:55:12 +00:00
msg . reply ( 'Je n’ écoute plus ce canal texte.' )
} else {
msg . reply ( 'Je n’ écoutais pas ce canal texte :o' )
}
2020-03-31 13:02:48 +00:00
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 )
2020-03-31 18:59:36 +00:00
}
else if ( msg . content === '!educpop-list' ) {
2020-03-31 13:02:48 +00:00
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 !'
2020-03-30 18:55:12 +00:00
for ( var index in reactions ) {
text += '\n' + reactions [ index ] . prefix + index + ' : ' + reactions [ index ] . description
}
2020-03-31 13:02:48 +00:00
msg . reply ( text )
2020-03-24 15:01:15 +00:00
}
2020-03-31 18:59:36 +00:00
else if ( msg . content === '!educpop-web' ) {
2020-04-02 16:33:21 +00:00
msg . reply ( 'https://educbot.jean-cloud.net?channel_id=' + msg . channel . id + '&web_token=' + channel . web _token )
2020-03-31 18:59:36 +00:00
}
2020-03-30 18:55:12 +00:00
/* save and ignore own messages */
2020-03-31 13:02:48 +00:00
else if ( msg . author . username === process . env . BOT _USERNAME ) {
2020-04-02 17:44:09 +00:00
if ( ! msg . content . startsWith ( '<' ) ) /* Save if its educpop summary */
2020-03-31 13:02:48 +00:00
channel . last _msg = msg ;
2020-03-24 15:01:15 +00:00
}
2020-03-30 18:55:12 +00:00
/* Educ pop stuff */
else if ( msg . content . startsWith ( '-' ) ) {
2020-03-31 13:02:48 +00:00
const content = msg . content . slice ( 1 )
2020-03-31 08:54:11 +00:00
if ( content . toLowerCase ( ) in reactions ) {
2020-04-02 17:44:09 +00:00
educpopDelPerson ( channel , msg . member . nickname , content . toLowerCase ( ) )
2020-03-31 13:02:48 +00:00
msg . delete ( )
reply ( channel )
2020-03-30 18:55:12 +00:00
}
2020-03-24 15:01:15 +00:00
}
2020-03-31 08:54:11 +00:00
else if ( msg . content . toLowerCase ( ) in reactions ) {
2020-03-31 13:02:48 +00:00
var reaction = msg . content . toLowerCase ( )
2020-04-02 17:44:09 +00:00
educpopAddPerson ( channel , msg . member . nickname , msg . content . toLowerCase ( ) )
2020-03-31 13:02:48 +00:00
msg . delete ( )
reply ( channel )
2020-03-30 18:55:12 +00:00
}
} ) ;
2020-03-24 15:01:15 +00:00
2020-03-31 13:02:48 +00:00
/* Recap educ-pop state on discord. Delete action message and last educ-pop recap */
2020-03-31 18:59:36 +00:00
/* This function refresh the display */
2020-03-31 13:02:48 +00:00
function reply ( channel ) {
2020-03-30 18:55:12 +00:00
var text = ''
2020-03-31 13:02:48 +00:00
for ( var index in channel . reactions ) {
if ( channel . reactions [ index ] . people . length > 0 )
text += '\n' + channel . reactions [ index ] . prefix + String ( channel . reactions [ index ] . people )
2020-03-30 18:55:12 +00:00
}
2020-03-31 08:54:11 +00:00
if ( text === '' ) {
text = 'Personne ne s’ est manifesté :/'
}
2020-03-31 13:02:48 +00:00
if ( channel . last _msg ) channel . last _msg . delete ( )
channel . discord _channel . send ( text )
2020-03-31 18:59:36 +00:00
wsSendStateAll ( channel )
2020-03-30 18:55:12 +00:00
}
2020-03-24 15:01:15 +00:00
2020-03-31 13:02:48 +00:00
/* Actual discord login */
client . login ( process . env . DISCORD _TOKEN ) ;