diff --git a/index.js b/index.js index 1069383c11f8fd37f415c67439ade65a4bec3def..0f527cba8ff2b37d1eefbc8001169aaecc183c30 100644 --- a/index.js +++ b/index.js @@ -1,40 +1,2 @@ -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'); diff --git a/models/Channel.js b/models/Channel.js new file mode 100644 index 0000000000000000000000000000000000000000..e2f569b871826d76fd0ec511e851bda198a9f35b --- /dev/null +++ b/models/Channel.js @@ -0,0 +1,14 @@ +const mongoose = require('../mongoose'); + +const channelSchema = new mongoose.Schema({ + token: String, + refresh: String, + expiration: Date, + username: String, + chatId: Number, + state: String, + groups: [Number], + scheduleTime: { type: String, validate: /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/ } +}) + +module.exports = mongoose.model('Channel', channelSchema); \ No newline at end of file diff --git a/models/Token.js b/models/Token.js deleted file mode 100644 index 08d7137624b28e530a6f2dbcfebe4bc4a7658b60..0000000000000000000000000000000000000000 --- a/models/Token.js +++ /dev/null @@ -1,11 +0,0 @@ -const mongoose = require('../mongoose'); - -const tokenSchema = new mongoose.Schema({ - token: String, - expiration: Date, - username: String, - chatId: Number, - state: String -}) - -module.exports = mongoose.model('Token', tokenSchema); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aefdfd60334cd586725f6044090883066d39a6ce..b1268b0c938001cb289fd13e4290e2404904724b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 7d85baf15806c91c8188946204cf28aa014be1b1..65d25e1750f419204f79a5b32a7dd22427085b64 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "dependencies": { "express": "^4.16.4", "mongoose": "^5.4.12", + "node-schedule": "^1.3.2", "node-telegram-bot-api": "^0.30.0", "request": "^2.88.0", "request-promise": "^4.2.4" diff --git a/requests.js b/requests.js new file mode 100644 index 0000000000000000000000000000000000000000..76025aa9c182510e041257199c2f300e10fb7244 --- /dev/null +++ b/requests.js @@ -0,0 +1,77 @@ +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 diff --git a/telegram.js b/telegram.js index a0450dd03ee7017b0b2ee4f54e2f35e38f2b0406..cb6fe26adec54ce4013e661f2d2248e1f9f17e5c 100644 --- a/telegram.js +++ b/telegram.js @@ -1,56 +1,230 @@ 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 diff --git a/website.js b/website.js index f19dc98f69e34cfbca681275b2a780ca30d41600..2841acc551d281b9b0c6617e51349963dbe1e26b 100644 --- a/website.js +++ b/website.js @@ -1,9 +1,9 @@ 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