Commit c32a7d23 authored by Guillaume Vagner's avatar Guillaume Vagner
Browse files

Merge branch 'simple-birthdays'

parents d3c0361d 28ffbbba
const bot = require('./telegram');
const app = require('./website');
const mongoose = require('./mongoose');
const Token = require('./models/Token');
const rp = require('request-promise');
function sendRequest(req, token, callback) {
const options = {
headers: { 'Authorization': `Bearer ${token}` },
json: true
}
const url = 'https://gateway.linkcs.fr/v1/graphql';
return rp(`${url}?query=${req}`, options)
}
function getBirthdays(token) {
req = 'query getUsersBirthday {users: usersBirthday { ...userData}}fragment userData on User {id firstName lastName roles {sector {composition {association {id}}}}}'
return sendRequest(req, token).then(body => {
const users = [];
body.data.users.forEach(user => {
use = {};
use.name = `${user.firstName} ${user.lastName}`;
use.asso = [];
user.roles.forEach(role => {
use.asso.push(role.sector.composition.association.id);
})
users.push(use);
});
return users;
})
}
// Token.findOne({ expiration: { $gt: Date.now() } }).then(token => {
// getBirthdays(token.token).then(a => console.log(a));
// })
\ No newline at end of file
require('./telegram');
require('./website');
const mongoose = require('../mongoose');
const tokenSchema = new mongoose.Schema({
const channelSchema = new mongoose.Schema({
token: String,
refresh: String,
expiration: Date,
username: String,
chatId: Number,
state: String
state: String,
groups: [Number],
scheduleTime: { type: String, validate: /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/ }
})
module.exports = mongoose.model('Token', tokenSchema);
\ No newline at end of file
module.exports = mongoose.model('Channel', channelSchema);
\ No newline at end of file
......@@ -176,6 +176,15 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cron-parser": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.7.3.tgz",
"integrity": "sha512-t9Kc7HWBWPndBzvbdQ1YG9rpPRB37Tb/tTviziUOh1qs3TARGh3b1p+tnkOHNe1K5iI3oheBPgLqwotMM7+lpg==",
"requires": {
"is-nan": "^1.2.1",
"moment-timezone": "^0.5.23"
}
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
......@@ -500,6 +509,14 @@
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
},
"is-nan": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz",
"integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=",
"requires": {
"define-properties": "^1.1.1"
}
},
"is-regex": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
......@@ -572,6 +589,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"long-timeout": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
"integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
......@@ -611,6 +633,19 @@
"mime-db": "~1.37.0"
}
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"moment-timezone": {
"version": "0.5.23",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz",
"integrity": "sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w==",
"requires": {
"moment": ">= 2.9.0"
}
},
"mongodb": {
"version": "3.1.13",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.1.13.tgz",
......@@ -702,6 +737,16 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
},
"node-schedule": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.2.tgz",
"integrity": "sha512-GIND2pHMHiReSZSvS6dpZcDH7pGPGFfWBIEud6S00Q8zEIzAs9ommdyRK1ZbQt8y1LyZsJYZgPnyi7gpU2lcdw==",
"requires": {
"cron-parser": "^2.7.3",
"long-timeout": "0.1.1",
"sorted-array-functions": "^1.0.0"
}
},
"node-telegram-bot-api": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.30.0.tgz",
......@@ -979,6 +1024,11 @@
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
},
"sorted-array-functions": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.2.0.tgz",
"integrity": "sha512-sWpjPhIZJtqO77GN+LD8dDsDKcWZ9GCOJNqKzi1tvtjGIzwfoyuRH8S0psunmc6Z5P+qfDqztSbwYR5X/e1UTg=="
},
"sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
......
const rp = require('request-promise');
const config = require('./config');
// Fonction d'envoi d'une requête au GraphQL de LinkCS
async function sendRequest(req, token) {
const options = {
headers: { 'Authorization': `Bearer ${token}` },
json: true
}
const url = 'https://gateway.linkcs.fr/v1/graphql';
return rp(`${url}?query=${req}`, options)
}
// Récupération de tous les personnes et leurs assos ayant leur anniversaire
function getBirthdays(token) {
const req = 'query getUsersBirthday {users: usersBirthday { ...userData}}fragment userData on User {firstName lastName roles {sector {composition {association {id}}}}}'
return sendRequest(req, token).then(body => {
const users = [];
body.data.users.forEach(user => {
use = {};
use.name = `${user.firstName} ${user.lastName}`;
use.asso = [];
user.roles.forEach(role => {
use.asso.push(role.sector.composition.association.id);
})
users.push(use);
});
return users;
})
}
// Récupération de la recherche de groupe
function searchGroups(token, term) {
const req = `query {searchAssociations(term: "${term}") {id name code}}`
return sendRequest(req, token).then(body => {
if (!body.data) return;
return body.data.searchAssociations;
}).catch(err => { console.error(err) })
}
// Récupération du nom de l'asso ayant l'ID donnée
function getGroupById(token, id) {
const req = `query {association(id: ${id}) {name}}`
return sendRequest(req, token).then(body => {
if (!body.data) return;
return body.data.association.name;
}).catch(err => { console.error(err) })
}
// Récupération d'un nouveau token
function getNewToken(chan) {
const options = {
url: 'https://auth.viarezo.fr/oauth/token',
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
form: {
grant_type: 'refresh_token',
refresh_token: chan.refresh,
client_id: config.oauth2.clientid,
client_secret: config.oauth2.secretid
}
}
return rp(options).then(body => {
if (!chan) { return req.query.state }
rep = JSON.parse(body);
console.log(rep);
chan.token = rep.access_token;
chan.refresh = rep.refresh_token;
chan.expiration = rep.expires_at * 1000;
return chan.save()
})
};
module.exports = { getBirthdays, sendRequest, searchGroups, getGroupById, getNewToken };
\ No newline at end of file
process.env["NTBA_FIX_319"] = 1;
const TelegramBot = require('node-telegram-bot-api');
const Token = require('./models/Token');
// Modules extérieurs
var TelegramBot = require('node-telegram-bot-api');
var schedule = require('node-schedule');
// Modules propres
var Channel = require('./models/Channel');
var { getBirthdays, searchGroups, getGroupById, getNewToken } = require('./requests');
// Configurations
const config = require('./config');
// Création de variables
var bot = new TelegramBot(config.telegram.token, { polling: true });
var schedules = {};
const bot = new TelegramBot(config.telegram.token, { polling: true });
bot.onText(/\/start/, (msg, _) => {
console.log(msg);
// A la connexion, création d'un document Channel dans MongoDB
bot.onText(/\/start/, msg => {
const chatId = msg.chat.id;
const resp = 'Holà, je suis le Happy Botday, je suis là pour vous souhaiter vous rapeller les anniversaires de vos potes !\nPour commencer, il faut que quelqu\'un s\'identifie : /connect';
bot.sendMessage(chatId, resp);
return Channel.findOne({
chatId: chatId
}).then(chan => {
// /start déjà fait...
if (chan) return bot.sendMessage(chatId, 'Vous avez déjà fait lancé le bot sur cette conversation. Pour tout réinitialiser, faites /reset.')
return Channel.create({
chatId: chatId,
username: '',
state: '',
token: '',
groups: []
}).then(_ => {
const resp = 'Holà, je suis le Happy Botday, je suis là pour vous souhaiter vous rapeller les anniversaires de vos potes !\nPour commencer, il faut que quelqu\'un s\'identifie : /connect';
bot.sendMessage(chatId, resp);
})
})
});
// Suppression de l'objet créé lors du /start
bot.onText(/\/reset/, msg => {
const chatId = msg.chat.id;
// Suppression de l'objet
if (schedule[chatId]) {
schedule[chatId].cancel();
delete (schedule[chatId]);
}
return Channel.findOneAndDelete({
chatId: chatId
}).then(_ => {
const resp = 'Toutes vos paramètres ont été supprimés. Pour recommencer à m\'utiliser, faites /start.';
return bot.sendMessage(chatId, resp);
})
})
// Si rien n'a été fait avant, propose un lien de connexion à l'OAuth2 de VR
bot.onText(/\/connect/, (msg, _) => {
const chatId = msg.chat.id;
const state = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
Token.findOne({
Channel.findOne({
chatId: chatId
}).then(token => {
if (!token) {
return Token.create({
username: msg.from.username,
chatId: chatId,
state: state
})
} else if (!token.token) {
return Token.findByIdAndDelete(token._id).then(_ => {
return Token.create({
username: msg.from.username,
chatId: chatId,
state: state
})
})
} else {
bot.sendMessage(chatId, `Une connexion a déjà été faite par @${token.username}. Pour la réinitialiser, faites /disconnect`);
}
}).then(token => {
if (!token) return
const resp = `Pour vous identifier, connectez-vous via l\'OAuth2 de ViaRézo depuis ce lien :\n${config.website.protocol}://${config.website.hostname}/?state=${state}`;
bot.sendMessage(chatId, resp);
}).then(chan => {
// start pas encore fait
if (!chan) return bot.sendMessage(chatId, 'Avant de vous authentifier, faites /start.');
// /connect déjà fait, renvoie vers le lien précédent
if (chan.state.length !== 0) return bot.sendMessage(chatId, `@${chan.username} a déjà fait une demande. Vous pouvez annuler la demande via /cancel ou @${chan.username} peut se connecter depuis ce lien :\n${config.website.protocol}://${config.website.hostname}/?state=${chan.state}.`)
// authentification déjà faite
if (chan.token.length !== 0) return bot.sendMessage(chatId, `Une connexion a déjà été faite par @${chan.username}. Pour la réinitialiser, faites /disconnect`);
// dans le reste des cas, création d'un lien pour l'authentification et enregistrement dans l'objet pour être sur
chan.state = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
chan.username = msg.from.username;
chan.save().then(chan => {
const resp = `Pour vous identifier, connectez-vous via l\'OAuth2 de ViaRézo depuis ce lien :\n${config.website.protocol}://${config.website.hostname}/?state=${chan.state}`;
return bot.sendMessage(chatId, resp);
})
})
});
// Annulation de la demande de connexion
bot.onText(/\/cancel/, msg => {
const chatId = msg.chat.id;
Channel.findOne({
chatId: chatId
}).then(chan => {
// ne peut pas annuler une demande déjà acceptée
if (chan.token.length !== 0) return bot.sendMessage(chatId, 'La demande d\'athentification a déjà été acceptée, elle ne peut pas être annulée. Pour se déconnecter, faites /disconnect.')
chan.username = '';
chan.state = '';
return chan.save().then(chan => {
return bot.sendMessage(chatId, 'La demande d\'authentification a été annulée. Vous pouvez vous reconnecter avec /connect.')
})
})
})
// Permet de déconnecter l'utilisateur qui s'est authentifié sur l'OAuth2
bot.onText(/\/disconnect/, msg => {
const chatId = msg.chat.id;
Channel.findOne({ chatId: chatId }).then(chan => {
// s'il n'y a pas de token, personne ne s'est connecté
if (chan.token.length === 0) return bot.sendMessage(chatId, 'Personne ne s\'est connecté...');
// suppression de tous les paramètres de connexion et save()
username = chan.username;
chan.token = '';
chan.expiration = Date.now(0);
chan.username = '';
chan.save().then(_ => {
return bot.sendMessage(chatId, `@${username} n'est plus connecté à l'OAuth2.`);
})
})
});
bot.onText(/\/disconnect/, (msg, _) => {
// Affiche tous les anniversaires de LinkCS
bot.onText(/\/allbirthdays/, msg => {
const chatId = msg.chat.id;
Token.findOneAndDelete({ chatId: chatId }).then(token => {
if (!token) return bot.sendMessage(chatId, 'Pas de compte connecté');
const resp = `@${token.username} n'est plus connecté à l'OAuth2.`;
bot.sendMessage(chatId, resp);
// recherche du token du chan actuel
Channel.findOne({
chatId: chatId,
}).then(chan => {
return getBirthdays(chan.token)
}).then(users => {
var msg = '**Joyeux anniversaire** à :\n'
users.forEach(user => {
msg += `${user.name}\n`
});
return bot.sendMessage(chatId, msg, { parse_mode: 'Markdown' });
})
});
// Affiche les anniversaires demandés
bot.onText(/\/birthdays/, msg => {
const chatId = msg.chat.id;
// recherche du token du chan actuel
Channel.findOne({
chatId: chatId,
}).then(chan => {
return Promise.all([getBirthdays(chan.token), chan])
}).then(([users, chan]) => {
// récupère que les personnes du jour qui font partie des groupes ciblés
const newUsers = users.filter(user => user.asso.some(asso => chan.groups.indexOf(asso) !== -1));
if (newUsers.length === 0) return bot.sendMessage(chatId, 'Pas d\'anniversaire à souhaiter aujourd\'hui.')
var msg = '**Joyeux anniversaire** à :\n'
newUsers.forEach(user => {
msg += `${user.name}\n`
});
return bot.sendMessage(chatId, msg, { parse_mode: 'Markdown' });
})
});
// recherche des groupes et renvoie le résultat avec leurs ID pour les rajouter
bot.onText(/\/search (.+)/, (msg, match) => {
const chatId = msg.chat.id;
const research = match[1];
Channel.findOne({
chatId: chatId
}).then(chan => {
return searchGroups(chan.token, research)
}).then(groups => {
if (groups.length == 0) return bot.sendMessage(chatId, 'Pas de groupe à ce nom...');
var resp = 'Voici les différentes associations. Faites /add XXXX pour ajouter les anniversaires de ses membres. ';
// LinkCS limite à 20 résultats
if (groups.length == 20) resp += 'La recherche est limitée à 20 choix.'
resp += '\n\n'
groups.forEach(group => {
resp += `${group.name} : \`/add ${group.id}\`\nhttps://linkcs.fr/association/${group.code} \n\n`
})
bot.sendMessage(chatId, resp, { parse_mode: 'Markdown' });
});
})
// Ajout d'un groupe dans la liste des groupes
bot.onText(/\/add (.+)/, (msg, match) => {
const chatId = msg.chat.id;
Channel.findOne({
chatId: chatId
}).then(chan => {
const id = parseInt(match[1].split(' ')[0]);
return Promise.all([getGroupById(chan.token, id), chan, id]);
}).then(([group, chan, id]) => {
// si pas de groupe trouvé
if (!group) return bot.sendMessage(chatId, 'Pas de groupe trouvé ayant cette ID');
// si le groupe y est déjà
if (chan.groups.indexOf(id) !== -1) return bot.sendMessage(chatId, 'Ce groupe est déjà est dans votre liste d\'anniversaire.');
// sinon on le rajoute
chan.groups.push(id);
return chan.save().then(_ => {
bot.sendMessage(chatId, `Ajout du groupe \`${group}\` à la liste des anniversaires.`, { parse_mode: 'Markdown' });
})
})
})
// Ajout du temps de schedule
bot.onText(/\/schedule (.+)/, (msg, match) => {
const chatId = msg.chat.id;
const time = match[1];
if (!RegExp('^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$').test(time)) return bot.sendMessage(chatId, 'Le temps entré n\'est pas au format hh:mm');
const hour = parseInt(time.split(':')[0]);
const minute = parseInt(time.split(':')[1]);
Channel.findOne({
chatId: chatId
}).then(chan => {
if (!chan) return bot.sendMessage(chatId, 'Pas de compte enregistré, faites /start pour commencer');
chan.schedule = time;
chan.save()
}).then(chan => {
schedules[chan.chatId] = schedule.scheduleJob({ hour: hour, minute: minute }, function () {
return getNewToken(chan).then(chan => {
return getBirthdays(chan.token)
}).then(users => {
// récupère que les personnes du jour qui font partie des groupes ciblés
const newUsers = users.filter(user => user.asso.some(asso => chan.groups.indexOf(asso) !== -1));
if (newUsers.length === 0) return
var msg = '**Joyeux anniversaire** à :\n'
newUsers.forEach(user => {
msg += `${user.name}\n`
});
return bot.sendMessage(chan.chatId, msg, { parse_mode: 'Markdown' });
})
});
})
})
// J'étais bien obligé (en vrai c'est pour tester)
bot.onText(/\/nikmarine/, msg => {
const chatId = msg.chat.id;
bot.sendMessage(chatId, 'Nik bien Marine');
console.log(schedules);
})
module.exports = bot;
\ No newline at end of file
const config = require('./config');
const app = require('express')();
const request = require('request');
const rp = require('request-promise');
const bot = require('./telegram');
const Token = require('./models/Token');
const Channel = require('./models/Channel');
app.listen(80, config.website.hostname, () => {
console.log(`[express] Website is up and accessible on ${config.website.protocol}://${config.website.hostname}/`);
......@@ -13,12 +13,12 @@ app.get('/', function (req, res) {
if (!req.query.state) { return res.sendFile(`${__dirname}/index.html`) };
Token.findOne({ state: req.query.state }).then(rep => {
Channel.findOne({ state: req.query.state }).then(chan => {
if (!rep || rep.token) return res.sendFile(`${__dirname}/index.html`);
if (!chan || chan.token) return res.sendFile(`${__dirname}/index.html`);
const redirectURI = `${config.website.protocol}://${config.website.hostname}/auth`;
const url = `https://auth.viarezo.fr/oauth/authorize/?redirect_uri=${redirectURI}&client_id=${config.oauth2.clientid}&response_type=code&state=${rep.state}&scope=${config.oauth2.scope}`;
const url = `https://auth.viarezo.fr/oauth/authorize/?redirect_uri=${redirectURI}&client_id=${config.oauth2.clientid}&response_type=code&state=${chan.state}&scope=${config.oauth2.scope}`;
return res.redirect(301, url);
})
......@@ -28,7 +28,9 @@ app.get('/background.jpg', function (req, res) {
res.sendFile(`${__dirname}/background.jpg`)
})
app.get('/auth', function (req, res) {
if (!req.query.code || !req.query.state) return res.sendFile(`${__dirname}/auth.html`)
const options = {
......@@ -44,21 +46,22 @@ app.get('/auth', function (req, res) {
}
}
request(options, (err, res, body) => {
if (!err && res.statusCode == 200) {
Token.findOne({ state: req.query.state }).then(token => {
rep = JSON.parse(body)
if (!token) { return req.query.state }
token.token = rep.access_token;
token.expiration = rep.expires_at*1000;
token.state = '';
return token.save();
}).then(token => {
bot.sendMessage(token.chatId, `@${token.username} s'est connecté à OAuth2, shall we begin?`)
})
}
return rp(options).then(body => {
return Promise.all([body, Channel.findOne({ state: req.query.state })])
}).then(([body, chan]) => {
if (!chan) { return req.query.state }
rep = JSON.parse(body);
console.log(rep);
chan.token = rep.access_token;
chan.refresh = rep.refresh_token;
chan.expiration = rep.expires_at * 1000;
chan.state = '';
return chan.save()
}).then(chan => {
bot.sendMessage(chan.chatId, `@${chan.username} s'est connecté à OAuth2, shall we begin?`);
return res.redirect(301, `${config.website.protocol}://${config.website.hostname}/auth`)
})
return res.redirect(301, `${config.website.protocol}://${config.website.hostname}/auth`);
})
module.exports = app;
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment