Compare commits

...

16 Commits
master ... dev

Author SHA1 Message Date
Adrian Amaglio 965ab54f20 working nice 2020-04-19 21:07:48 +02:00
Adrian Amaglio 9939d027b9 trying to fix ws on internet 2020-04-06 17:17:12 +02:00
Adrian Amaglio c759779135 docker 2020-04-02 19:44:17 +02:00
Adrian Amaglio 094ace0d2f 🐛 name and msg save 2020-04-02 19:44:09 +02:00
Adrian Amaglio 1ccf4b3c5c lol 2020-04-02 18:33:21 +02:00
Adrian Amaglio a72a1dbaeb 💄 some improvements 2020-04-02 18:01:29 +02:00
Adrian Amaglio ec79a69eab Changed discord shortcuts bu actual emoji 2020-04-02 18:01:11 +02:00
Adrian Amaglio 8c6e538421 🐛💄 bugfix and avatar image 2020-04-02 17:30:53 +02:00
Adrian Amaglio f6c36b76ff update 2020-04-01 12:35:19 +02:00
Adrian Amaglio 9c8de9a8f2 websocket powered visualisation 2020-03-31 20:59:36 +02:00
Adrian Amaglio 25507a8e08 fix 2020-03-31 15:24:40 +02:00
Adrian Amaglio d5a4163e74 renamed to standard name 2020-03-31 15:18:02 +02:00
Adrian Amaglio 4687d817f0 doc utilisateur 2020-03-31 15:17:25 +02:00
Adrian Amaglio e9c60956b6 now channel independant 2020-03-31 15:02:48 +02:00
Adrian Amaglio af7e3d1557 fix and todos 2020-03-31 10:54:11 +02:00
Adrian Amaglio b500d7d485 refactored. A strange bug persists… 2020-03-30 20:55:12 +02:00
12 changed files with 486 additions and 69 deletions

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
Dockerfile
node_modules
npm-debug.log

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
package-lock.json
node_modules/
.env

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
from node:13
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY index.js defaultReactions.json avatar.png ./
EXPOSE 8080
CMD [ "node", "./index.js" ]

View File

@ -1,3 +1,75 @@
# Coucou
# EducBot
Un petit bot d'éduc pop pour Discord (WIP).
Il permet de voir qui secoue les mains dans une discussion vocale.
## Contribuer
Pour donner votre avis, proposer une modification ou amélioration, contactez nous via le [formulaire de Jean-Cloud](https://jean-cloud.net#contact)
Pour proposer une modification, nhésitez pas à faire une pull request !
## Utilisation
### Installer le bot dans le serveur Discord
Ce nest pour linstant pas forcement facile…
### Fonctionnement
EducBot écoute tous les messages dun serveur où il a été installé (comme tous les bots).
Il réagira a des commandes messages commençants par `!`, et a des mots clef.
EducBot a pour objectif de recenser les utilisateurs souhaitant réagir.
#### Les commandes
EducBot répondra en toutes circonstances (sauf serveur cassé) aux commandes suivantes :
- `!educpop-help` Listera ces commandes
- `!educpop-enable` Commence la comptabilisation
- `!educpop-disable` Stope la comptabilisation
- `!educpop-reset` Remet à zéro les compteurs
- `!educpop-pause` Pause la comptabilisation
- `!educpop-resume` Reprend la comptabilisation
- `!educpop-list` Liste les mots-clés qui sont comptabilisés
Par exemple, taper `!educpop-list` dans un canal texte **où EducBot est activé**, lui fait dire :
```
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 !
:thumbsup:oui : Je suis daccord
:thumbsdown:non : Je ne suis pas daccord
:raised_hand:parole : Je veux parler
:raised_hands:réponse : Je veux répondre rapidement
:octagonal_sign:suffit : On tourne en rond
:hear_no_evil:écoute : On ne sécoute pas
:heart_eyes:love : Jadore
:dab:dab : Dab
```
#### Les mots-clés
Écrire seulement `oui` dans un canal texte **où EducBot est activé**, lui fait ajouter votre nom dutilisateur dans la liste des gens daccord.
Écrire seulement `-oui` fait retirer ce nom.
#### Visualisation
Le bot fait un résumé de létat des compteurs à chaque changement.
Pour des raisons de visibilités, le message utilisateur et les anciens messages de résumé sont supprimés.
#### Interface web
Une interface web permet de suivre létat des compteurs sans être secoué par un chat en folie.
## Lancer sa propre instance
Vu que le bot peut lire tous vos messages, il est normal de vouloir héberger sa propre instance !
Il faut suivre la documentation discord pour la création dun bot, récupérer un token de développeur et le mettre dans le fichier `.env`
## Développement
### Fichiers
- `index.js` est un module nodejs qui est le cœur du bot !
- `index.html` est la page html qui affiche les réactions des gens en temps réel
- `main.js` est le script qui fait tourner la page précédente
- `defaultReactions.json` est le fichier qui contient les réactions possibles par défaut
### Améliorations futures
#### Interface web
- Pouvoir supprimer des réactions / les reset (on y est presque)
- Avoir les emojis. Voir [twemoji](https://github.com/twitter/twemoji/tree/master/assets/svg). Il faut avoir leur code utf-truc.
#### Discord
- Faire une image docker
- La déployer et mettre le bot sur le serveur discord
un petit bot d'educ pop pour Discord (WIP)

BIN
avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

4
build.sh Normal file
View File

@ -0,0 +1,4 @@
echo "Ce script ne sexécute pas, il est là à titre dexemple"
exit 0
docker build . -t jeancloud/educbot:latest -t jeancloud/educbot:1.0.0 -t jeancloud/educbot:1.0

38
defaultReactions.json Normal file
View File

@ -0,0 +1,38 @@
{
"oui": {
"prefix": "👍",
"description": "Je suis daccord"
},
"non": {
"prefix": "👎",
"description": "Je ne suis pas daccord"
},
"parole": {
"prefix": "✋",
"description": "Je veux parler"
},
"réponse": {
"prefix": "🙌",
"description": "Je veux répondre rapidement"
},
"suffit": {
"prefix": "🛑",
"description": "On tourne en rond"
},
"écoute": {
"prefix": "🙉",
"description": "On ne sécoute pas"
},
"love": {
"prefix": "😍",
"description": "Jadore"
},
"dab": {
"prefix": ":dab:",
"description": "Dab"
},
"fuck": {
"prefix": "🖕",
"description": "Je vous emmerde!"
}
}

67
educ.js
View File

@ -1,67 +0,0 @@
const Discord = require('discord.js');
const client = new Discord.Client();
client.on('ready', () => {
console.log('Logged in as ${client.user.tag}');
});
// l'ID du channel de test (test-bot) pour plus tard
// var testchan_id = '691952512332202064';
var leve_main=[];
var pas_dacc=[];
message = ""; // var string à compléter
client.on('message', msg => {
// Various features
// ignore own messages
if(msg.author.username === 'bod-educ-pop'){
return;
}
if(msg.content === 'ping'){
msg.reply('mais encore :thinking:');
return;
}
if(msg.author.username === 'remi.peltier'){
msg.reply("Va bosser Rémi");
return;
}
// TODO : changer le surnom de la personne en ce qu'elle mange
if(msg.content === 'je mange'){
console.log("");
}
// exit if not on test channel. Might cause problem if asynchronous behavior.
if(msg.channel.name != 'test-bot'){
return;
}
// Educ pop stuff
//
if(msg.content === 'leve'){
leve_main.push(msg.author.username);
}
if(msg.content === 'non'){
pas_dacc.push(msg.author.username);
}
/* TODO : Implémenter les messages suivants :
* > "laisse" (je laisse la parole > on vire le pseudo de la liste "Lèvent la main"
* > "réponse rapide" (on me met au début de la liste, et on place la personne qui est en train de parler juste derrière.
* > autres mouvements d'educ pop : "d'accord", "ça tourne en rond", "on s'écoute plus", etc
*/
message += "*Lèvent la main : " + String(leve_main);
message += "* Pas d'accord : " + String(pas_dacc);
msg.reply(message);
msg.delete(); // marche pas, TODO : supprimer le message pour n'obtenir que les résumés.
message = ""; // reset message.
});
client.login('NjkxOTUzMDQzMDcxMzAzNzIy.Xnnhng.pYBFO2ogooVs2AyYz8Pk6AKhMoo');

23
index.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="main.css" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="main.js"></script>
<title>Éduc-Bot, le bot déduc-pop</title>
</head>
<body>
<main id="educbotstatus">
<p v-if="connection">Connecté !</p>
<p v-else="">Non connecté !</p>
<p>{{message}}</p>
<ul>
<li v-for="(value, key) in reactions">
{{value.prefix}}
<span v-for="person in value.people">{{person}}</span>
</li>
</ul>
</main>
</body>
</html>

253
index.js Normal file
View File

@ -0,0 +1,253 @@
/* 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);

58
main.js Normal file
View File

@ -0,0 +1,58 @@
window.onload = function () {
var app = new Vue({
el: '#educbotstatus',
data: {
web_token: '',
channel_id: '',
ws: null,
ws_url: 'wss://educbot.jean-cloud.net',
reactions: {},
connection: false,
message: '',
},
created: function () {
var url = new URL(location.href)
this.web_token = url.searchParams.get('web_token')
this.channel_id = url.searchParams.get('channel_id')
this.ws_port = url.searchParams.get('ws_port') || '8080'
if (this.web_token == '' || this.channel_id == '' || this.ws_port == '') {
this.message = 'Erreur ! Ladresse nest pas valide :( Redemandez-la à Educ-Bot avec la commande « !educpop-web ».'
return
}
window.WebSocket = window.WebSocket || window.MozWebSocket
this.ws = new WebSocket(this.ws_url + ':' + this.ws_port)
this.ws.onmessage = (data) => {
var reactions = JSON.parse(data.data)
if ('error' in reactions) {
this.message = 'Erreur ! '
if (reactions.error === "bad channel") {
this.message += 'Les identifiants sont incorrect ! Redemandez les à Educ-Bot avec la commande « !educpop-web ».'
}
return
}
this.reactions = reactions
}
this.ws.onopen = () => {
this.sendWs('init')
this.connection = true
}
this.ws.onclose = () => {
this.connection = false
this.message = 'Erreur ! La connection avec le bot a été perdue :( Essayez dactualiser la page.'
}
this.ws.onerror = () => {
this.connection = false
this.message = 'Erreur ! La connection avec le bot a été perdue :( Essayez dactualiser la page.'
}
},
methods: {
sendWs: function (action) {
this.ws.send(JSON.stringify({
'action': action,
'web_token': this.web_token,
'channel': this.channel_id,
}))
}
}
})
}

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "educ-bot",
"version": "1.0.0",
"description": "Un petit bot d'éduc pop pour Discord (WIP). Il permet de voir qui secoue les mains dans une discussion vocale.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "ssh://git@git.jean-cloud.net:22529/theolem/educ-bot.git"
},
"author": "",
"license": "ISC",
"dependencies": {
"discord.js": "^12.1.1",
"dotenv": "^8.2.0",
"lodash.clonedeep": "^4.5.0",
"randomstring": "^1.1.5"
}
}