diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..1d46371 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + "extends": "google", + "parserOptions": { + "ecmaVersion": 6 + }, + "rules": { + "linebreak-style": 0 + } +}; \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..044f6b4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.js filter=gitignore \ No newline at end of file diff --git a/README.md b/README.md index 707742b..6d8d753 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ BotFather will give you a *token*, something like `123456789:AbCdfGhIJKlmNoQQRsT ## Help -You are welcome to contribute with pull requests, bug reports, ideas and donations. +You are welcome to contribute with pull requests, bug reports, ideas and donations. Join the forum if you have any general purpose questions: [discuss.bostrot.com](https://discuss.bostrot.com) Bitcoin: [1ECPWeTCq93F68BmgYjUgGSV11XuzSPSeM](https://www.blockchain.com/btc/payment_request?address=1ECPWeTCq93F68BmgYjUgGSV11XuzSPSeM¤cy=USD&nosavecurrency=true&message=Bostrot) diff --git a/bin/cache.js b/bin/cache.js index 6bb7bdf..ef40726 100644 --- a/bin/cache.js +++ b/bin/cache.js @@ -1,8 +1,8 @@ -module.exports = { - ticketID: "", - ticketIDs: [], - ticketStatus: {}, - ticketSent: [], - html: "", - noSound: "", -} \ No newline at end of file +module.exports = { + ticketID: '', + ticketIDs: [], + ticketStatus: {}, + ticketSent: [], + html: '', + noSound: '', +}; diff --git a/bin/support.js b/bin/support.js index e7e268c..581081b 100644 --- a/bin/support.js +++ b/bin/support.js @@ -1,243 +1,302 @@ -const Telegraf = require("telegraf") -const { - Extra, - Markup -} = Telegraf -var config = require("../config.js") -var handler = require("./ticket_handler.js"); -var cache = require("./cache.js"); - -const bot = new Telegraf(config.bot_token) - -var cron = require("cron"); -var userInfo = "" -var exec = require("child_process").exec -var cronJob - -cache.html = Extra.HTML() -cache.noSound = Extra.HTML().notifications(false) - -const root = Extra.HTML().markup((m => // inline keyboard for admin dashboard +const Telegraf = require('telegraf'); +const {Extra} = Telegraf; +const config = require('../config.js'); +const handler = require('./ticket_handler.js'); +let cache = require('./cache.js'); + +const bot = new Telegraf(config.bot_token); + +const cron = require('cron'); +const exec = require('child_process').exec; +let cronJob; + +cache.html = Extra.HTML(); // eslint-disable-line no-use-before-define +cache.noSound = Extra + .HTML().notifications(false); // eslint-disable-line no-use-before-define + +const root = Extra.HTML().markup(( // eslint-disable-line no-use-before-define + m // inline keyboard for admin dashboard +) => m.inlineKeyboard([ - m.callbackButton("🔄 Update", "update"), - m.callbackButton("📖 Log", "log"), - m.callbackButton("♻️ Restart", "restart"), - m.callbackButton("🚫 Stop", "stop") - ]))) + m.callbackButton('🔄 Update', 'update'), + m.callbackButton('📖 Log', 'log'), + m.callbackButton('♻️ Restart', 'restart'), + m.callbackButton('🚫 Stop', 'stop'), + ]) +); -bot.action("restart", (ctx) => { // restart other bot +bot.action('restart', (ctx) => { + // restart other bot if (ctx.from.id === config.owner_id) { - var list = "" - ex("service " + config.supported_bot + " restart", function (results) { - setTimeout(function () { - ex("service " + config.supported_bot + " status", function (results) { + ex('service ' + config.supported_bot + ' restart', function(results) { + setTimeout(function() { + ex('service ' + config.supported_bot + ' status', function(results) { if (cronJob !== undefined) { - status = cronJob.running + status = cronJob.running; } - ctx.editMessageText("Current status:\n" + results + "\nCron running: restart", root) - }) - }, 2000) - }) + ctx.editMessageText( + 'Current status:\n' + results + '\nCron running: restart', + root + ); + }); + }, 2000); + }); if (cronJob !== undefined) { if (cronJob.running === false) { - cronJob.start() + cronJob.start(); } } } -}) -bot.action("log", (ctx) => { // send other bots log +}); +bot.action('log', (ctx) => { + // send other bots log if (ctx.from.id == config.owner_id) { - ex("journalctl -u " + config.supported_bot + " -b > /logs/log.txt", function (results) { - ctx.replyWithDocument({ - source: "/logs/log.txt" - }) - }) + ex( + 'journalctl -u ' + config.supported_bot + ' -b > /logs/log.txt', + function(results) { + ctx.replyWithDocument({ + source: '/logs/log.txt', + }); + } + ); } -}) -bot.action("update", (ctx) => { // update admin dasboard"s status +}); +bot.action('update', (ctx) => { + // update admin dasboard"s status if (ctx.from.id == config.owner_id) { - var list = "" - var status - ex("service " + config.supported_bot + " status", function (results) { + let status; + ex('service ' + config.supported_bot + ' status', function(results) { if (cronJob !== undefined) { - status = cronJob.running + status = cronJob.running; } - ctx.editMessageText("Current status:\n" + results + "\nCron running: " + status, root) - }) + ctx.editMessageText( + 'Current status:\n' + results + '\nCron running: ' + status, + root + ); + }); } -}) -bot.action("stop", (ctx) => { // stop the bot +}); +bot.action('stop', (ctx) => { + // stop the bot if (ctx.from.id == config.owner_id) { - ex("service " + config.supported_bot + " stop", function (results) { - ctx.editMessageText("Bitgram stopped", root) - }) + ex('service ' + config.supported_bot + ' stop', function(results) { + ctx.editMessageText('Bitgram stopped', root); + }); if (cronJob !== undefined) { if (cronJob.running === true) { - cronJob.stop() + cronJob.stop(); } } } -}) - -var cronSession = function (ctx) { // check every 5 seconds if other bot is down, if it"s inactive restart it - console.log("Session started.\n") - cronJob = cron.job("*/5 * * * * *", function () { // 5 seconds - ex("systemctl is-active " + config.supported_bot + "", function (results) { - if (results.indexOf("failed") > -1) { // restart on failed - ex("journalctl -u " + config.supported_bot + " -b > /var/www/cache.html/" + config.supported_bot + "/logs/log.txt", function (results) { - ctx.replyWithDocument({ - source: "/var/www/cache.html/" + config.supported_bot + "/logs/log.txt" - }) - }) - ex("service " + config.supported_bot + " start", function (results) { - bot.telegram.sendMessage(config.staffchat_id, "Restarted bot. See log.", cache.html) - }) - } - if (results.indexOf("inactive") > -1) { // restart on inactive - ex("journalctl -u " + config.supported_bot + " -b > /var/www/cache.html/" + config.supported_bot + "/logs/log.txt", function (results) { - ctx.replyWithDocument({ - source: "/var/www/cache.html/" + config.supported_bot + "/logs/log.txt" - }) - }) - ex("service " + config.supported_bot + " start", function (results) { - bot.telegram.sendMessage(config.staffchat_id, "Restarted bot. See log.", cache.html) - }) - } - results = null - }) - }, function () { - bot.telegram.sendMessage(config.staffchat_id, "Stopped cron job.", cache.html) - }) - cronJob.start() -} - -var ex = function execute(command, callback) { // execute command - exec(command, function (error, stdout, stderr) { - callback(stdout) - }) -} - -bot.command("start", ({ // on start reply with chat bot rules - reply, - from, - chat -}) => { - reply(config.startCommandText, cache.html) -}) - -bot.command("id", ({ - reply, - from, - chat -}) => { - reply(from.id + " " + chat.id) -}) -bot.command("faq", (ctx) => { // faq - ctx.reply(config.faqCommandText) -}) - -bot.command("root", (ctx) => { // admin dashboard can only be used by owner - console.log("id " + ctx.from.id) - if ((ctx.from.id).toString() == config.owner_id) { - bot.telegram.sendMessage(config.staffchat_id, "You will receive the logs when the bot crashes.", root) - cronSession(ctx) +}); + +let cronSession = function(ctx) { + // check every 5 seconds if other bot is down, if it"s inactive restart it + console.log('Session started.\n'); + cronJob = cron.job( + '*/5 * * * * *', + function() { + // 5 seconds + ex('systemctl is-active ' + config.supported_bot + '', function(results) { + if (results.indexOf('failed') > -1) { + // restart on failed + ex( + 'journalctl -u ' + + config.supported_bot + + ' -b > /var/www/cache.html/' + + config.supported_bot + + '/logs/log.txt', + function(results) { + ctx.replyWithDocument({ + source: + '/var/www/cache.html/' + + config.supported_bot + + '/logs/log.txt', + }); + } + ); + ex('service ' + config.supported_bot + ' start', function(results) { + bot.telegram.sendMessage( + config.staffchat_id, + 'Restarted bot. See log.', + cache.html + ); + }); + } + if (results.indexOf('inactive') > -1) { + // restart on inactive + ex( + 'journalctl -u ' + + config.supported_bot + + ' -b > /var/www/cache.html/' + + config.supported_bot + + '/logs/log.txt', + function(results) { + ctx.replyWithDocument({ + source: + '/var/www/cache.html/' + + config.supported_bot + + '/logs/log.txt', + }); + } + ); + ex('service ' + config.supported_bot + ' start', function(results) { + bot.telegram.sendMessage( + config.staffchat_id, + 'Restarted bot. See log.', + cache.html + ); + }); + } + results = null; + }); + }, + function() { + bot.telegram.sendMessage( + config.staffchat_id, + 'Stopped cron job.', + cache.html + ); + } + ); + cronJob.start(); +}; + +let ex = function execute(command, callback) { + // execute command + exec(command, function(error, stdout, stderr) { + callback(stdout); + }); +}; + +bot.command('start', ({ // on start reply with chat bot rules + reply, from, chat}) => { + reply(config.startCommandText, cache.html); +}); + +bot.command('id', ({reply, from, chat}) => { + reply(from.id + ' ' + chat.id); +}); +bot.command('faq', (ctx) => { + // faq + ctx.reply(config.faqCommandText); +}); + +bot.command('root', (ctx) => { + // admin dashboard can only be used by owner + console.log('id ' + ctx.from.id); + if (ctx.from.id.toString() == config.owner_id) { + bot.telegram.sendMessage( + config.staffchat_id, + 'You will receive the logs when the bot crashes.', + root + ); + cronSession(ctx); } -}) - -bot.telegram.getMe().then((botInfo) => { // enable for groups (get own username) - bot.options.username = botInfo.username -}) - -const downloadPhotoMiddleware = (ctx, next) => { // download photos - return bot.telegram.getFileLink(ctx.message.photo[0]) - .then((link) => { - ctx.state.fileLink = link - return next() - }) -} - -const downloadVideoMiddleware = (ctx, next) => { // download videos - return bot.telegram.getFileLink(ctx.message.video) - .then((link) => { - ctx.state.fileLink = link - return next() - }) -} - -const downloadDocumentMiddleware = (ctx, next) => { // download documents - console.log(ctx.message) - return bot.telegram.getFileLink(ctx.message.document) - .then((link) => { - ctx.state.fileLink = link - return next() - }) -} +}); + +bot.telegram.getMe().then((botInfo) => { + // enable for groups (get own username) + bot.options.username = botInfo.username; +}); + +const downloadPhotoMiddleware = (ctx, next) => { + // download photos + return bot.telegram.getFileLink(ctx.message.photo[0]).then((link) => { + ctx.state.fileLink = link; + return next(); + }); +}; + +const downloadVideoMiddleware = (ctx, next) => { + // download videos + return bot.telegram.getFileLink(ctx.message.video).then((link) => { + ctx.state.fileLink = link; + return next(); + }); +}; + +const downloadDocumentMiddleware = (ctx, next) => { + // download documents + console.log(ctx.message); + return bot.telegram.getFileLink(ctx.message.document).then((link) => { + ctx.state.fileLink = link; + return next(); + }); +}; // display open tickets -bot.command("open", (ctx) => { - ctx.getChat().then(function (chat) { - if ((chat.id).toString() === config.staffchat_id) { - console.log("chatid", (chat.id).toString()) - ctx.getChatAdministrators().then(function (admins) { - admins = JSON.stringify(admins) +bot.command('open', (ctx) => { + ctx.getChat().then(function(chat) { + if (chat.id.toString() === config.staffchat_id) { + console.log('chatid', chat.id.toString()); + ctx.getChatAdministrators().then(function(admins) { + admins = JSON.stringify(admins); if (admins.indexOf(ctx.from.id) > -1) { - var openTickets = "" - for (var i in cache.ticketIDs) { + let openTickets = ''; + for (let i in cache.ticketIDs) { if (cache.ticketStatus[cache.ticketIDs[i]] === true) { if (openTickets.indexOf(cache.ticketIDs[i]) === -1) { - openTickets += "#" + cache.ticketIDs[i] + "\n" + openTickets += '#' + cache.ticketIDs[i] + '\n'; } } } - setTimeout(function () { - bot.telegram.sendMessage(chat.id, "Open Tickets:\n\n" + openTickets, cache.noSound) - }, 10) + setTimeout(function() { + bot.telegram.sendMessage( + chat.id, + 'Open Tickets:\n\n' + openTickets, + cache.noSound + ); + }, 10); } - }) + }); } - }) -}) + }); +}); // close ticket -bot.command("close", (ctx) => { - ctx.getChat().then(function (chat) { - if ((chat.id).toString() === config.staffchat_id) { - ctx.getChatAdministrators().then(function (admins) { - admins = JSON.stringify(admins) - if (ctx.message.reply_to_message !== undefined && admins.indexOf(ctx.from.id) > -1) { - var replyText = ctx.message.reply_to_message.text; - var userid = replyText.match(new RegExp("#" + "(.*)" + " from")) - cache.ticketStatus[userid[1]] = false +bot.command('close', (ctx) => { + ctx.getChat().then(function(chat) { + if (chat.id.toString() === config.staffchat_id) { + ctx.getChatAdministrators().then(function(admins) { + admins = JSON.stringify(admins); + if ( + ctx.message.reply_to_message !== undefined && + admins.indexOf(ctx.from.id) > -1 + ) { + let replyText = ctx.message.reply_to_message.text; + let userid = replyText.match(new RegExp('#' + '(.*)' + ' from')); + cache.ticketStatus[userid[1]] = false; } - }) + }); } - }) -}) + }); +}); // handle photo input -bot.on("photo", downloadPhotoMiddleware, (ctx, next) => { - handler.photo(bot, ctx) -}) +bot.on('photo', downloadPhotoMiddleware, (ctx, next) => { + handler.photo(bot, ctx); +}); // handle video input -bot.on("video", downloadVideoMiddleware, (ctx, next) => { - handler.photo(bot, ctx) -}) +bot.on('video', downloadVideoMiddleware, (ctx, next) => { + handler.photo(bot, ctx); +}); // handle file input -bot.on("document", downloadDocumentMiddleware, (ctx, next) => { - handler.document(bot, ctx) -}) - +bot.on('document', downloadDocumentMiddleware, (ctx, next) => { + handler.document(bot, ctx); +}); bot.hears(/(.+)/, (ctx) => handler.ticket(bot, ctx)); -bot.startPolling() +bot.startPolling(); /* -If you receive Error: 409: Conflict: can't use getUpdates method while webhook is active, comment bot.startPolling() out, -remove // of the following commands, run your bot once and undo the changes. This will disable the webhook by setting it -to empty. +If you receive Error: 409: Conflict: can't use getUpdates method while +webhook is active, comment bot.startPolling() out, remove // of the following +commands, run your bot once and undo the changes. This will disable the +webhook by setting it to empty. bot.telegram.setWebhook(""); bot.startWebhook("") -*/ \ No newline at end of file +*/ diff --git a/bin/ticket_handler.js b/bin/ticket_handler.js index cc857ec..8642003 100644 --- a/bin/ticket_handler.js +++ b/bin/ticket_handler.js @@ -1,133 +1,242 @@ -var config = require("../config.js") -var cache = require("./cache.js") - -function ticketHandler(bot, ctx) { - ctx.getChat().then(function (chat) { - if ((chat.id).toString() === config.staffchat_id) { - // let staff handle that - staffChat(); - } else if (chat.type === "private") { - // create a ticket and send to staff - customerChat(ctx, bot, chat); - } - }) -} - -// reply to tickets in staff chat -function staffChat() { - // check whether person is an admin - ctx.getChatAdministrators().then(function (admins) { - admins = JSON.stringify(admins) - if (ctx.message.reply_to_message !== undefined && admins.indexOf(ctx.from.id) > -1) { - try { - var replyText = ctx.message.reply_to_message.text; - var replyName = ctx.message.reply_to_message.text; - var userid = replyText.match(new RegExp("#" + "(.*)" + " from")) - var name = replyText.match(new RegExp("from " + "(.*)" + " @")) - if (ctx.message.text === "me") { // accept ticket - bot.telegram.sendMessage(config.staffchat_id, "" + config.lang_ticket + " #" + userid[1] + " " + config.lang_acceptedBy + " " + ctx.message.from.first_name + " -> /open", cache.noSound) - } else { - cache.ticketStatus[userid[1]] = false - bot.telegram.sendMessage(userid[1], config.lang_dear + " " + name[1] + ",\n\n" + ctx.message.text + "\n\n" + config.lang_regards + "\n" + ctx.message.from.first_name, cache.html) - console.log("Answer: " + config.lang_ticket + " #" + cache.tickedID + " " + config.lang_dear + " " + name[1] + " " + ctx.message.text + " " + config.lang_from + " " + ctx.message.from.first_name) - } - cache.ticketSent[cache.tickedID] = undefined; - } catch (e) {} - } - }).catch(function (noAdmin) { - console.log("Error with admins: " + noAdmin) - }) -} - -function customerChat(ctx, bot, chat) { - cache.tickedID = ctx.message.from.id - if (cache.ticketIDs[cache.ticketID] === undefined) { - cache.ticketIDs.push(cache.tickedID) - } - cache.ticketStatus[cache.tickedID] = true - userInfo = "" - userInfo += " " + config.lang_from + " " + ctx.message.from.first_name + " " - userInfo += "@" + ctx.message.from.username + " " + config.lang_language + ": " + ctx.message.from.language_code + "\n\n" - if (cache.ticketSent[cache.tickedID] === undefined) { - bot.telegram.sendMessage(chat.id, config.lang_contactMessage) - bot.telegram.sendMessage(config.staffchat_id, "" + config.lang_ticket + " #" + cache.tickedID + userInfo + ctx.message.text, cache.html) - // wait 5 minutes before this message appears again and don"t send notificatoin sounds in that time to avoid spam - setTimeout(function () { - cache.ticketSent[cache.tickedID] = undefined; - }, config.spam_time) - cache.ticketSent[cache.tickedID] = 0; - } else if (cache.ticketSent[cache.tickedID] < 5) { - cache.ticketSent[cache.tickedID]++; - bot.telegram.sendMessage(config.staffchat_id, "" + config.lang_ticket + " #" + cache.tickedID + userInfo + ctx.message.text, cache.noSound) - } else if (cache.ticketSent[cache.tickedID] === 5) { - cache.ticketSent[cache.tickedID]++; - bot.telegram.sendMessage(chat.id, config.lang_blockedSpam) - } - console.log("Ticket: " + " #" + cache.tickedID + userInfo.replace("\n\n", ": ") + ctx.message.text) -} - -function videoHandler(bot, ctx) { - forwardFile(bot, ctx, function (userInfo) { - bot.telegram.sendVideo(config.staffchat_id, ctx.message.video.file_id, { - caption: config.lang_ticket + ": #" + cache.ticketID + "\n" + userInfo + "\n" + (ctx.message.caption || ""), - }) - }) -} - -function photoHandler(bot, ctx) { - forwardFile(bot, ctx, function (userInfo) { - bot.telegram.sendPhoto(config.staffchat_id, ctx.message.photo[0].file_id, { - caption: config.lang_ticket + ": #" + cache.ticketID + "\n" + userInfo + "\n" + (ctx.message.caption || ""), - }) - }) -} - -function documentHandler(bot, ctx) { - forwardFile(bot, ctx, function (userInfo) { - bot.telegram.sendDocument(config.staffchat_id, ctx.message.document.file_id, { - caption: config.lang_ticket + ": #" + cache.ticketID + "\n" + userInfo + (ctx.message.caption || ""), - }) - }) -} - -function forwardFile(bot, ctx, callback) { - if (cache.ticketSent[cache.tickedID] === undefined) { - fowardHandler(ctx, function (userInfo) { - callback(userInfo); - }); - // wait 5 minutes before this message appears again and don"t send notificatoin sounds in that time to avoid spam - setTimeout(function () { - cache.ticketSent[cache.tickedID] = undefined; - }, config.spam_time) - cache.ticketSent[cache.tickedID] = 0; - } else if (cache.ticketSent[cache.tickedID] < 5) { - cache.ticketSent[cache.tickedID]++; - // TODO: add cache.noSound property for silent notifications - fowardHandler(ctx, function (userInfo) { - callback(userInfo); - }); - } else if (cache.ticketSent[cache.tickedID] === 5) { - cache.ticketSent[cache.tickedID]++; - bot.telegram.sendMessage(chat.id, config.lang_blockedSpam) - } - -} - -function fowardHandler(ctx, callback) { - ctx.getChat().then(function (chat) { - if (chat.type === "private") { - cache.ticketID = ctx.message.from.id - userInfo = "" - userInfo += config.lang_from + " " + ctx.message.from.first_name + " " - userInfo += "@" + ctx.message.from.username + "\n" + config.lang_language + ": " + ctx.message.from.language_code + "\n\n" - callback(userInfo); - } - }) -} - -module.exports = { - ticket: ticketHandler, - photo: photoHandler, - video: videoHandler, - document: documentHandler, -}; \ No newline at end of file +const config = require('../config.js'); +const cache = require('./cache.js'); + +function ticketHandler(bot, ctx) { + ctx.getChat().then(function(chat) { + if (chat.id.toString() === config.staffchat_id) { + // let staff handle that + staffChat(ctx, bot, chat); + } else if (chat.type === 'private') { + // create a ticket and send to staff + customerChat(ctx, bot, chat); + } + }); +} + +// reply to tickets in staff chat +function staffChat(ctx, bot) { + // check whether person is an admin + ctx.getChatAdministrators() + .then(function(admins) { + admins = JSON.stringify(admins); + let replyText; + if ( + ctx.message.reply_to_message !== undefined && + admins.indexOf(ctx.from.id) > -1 + ) { + try { + replyText = ctx.message.reply_to_message.text; + let userid = replyText.match(new RegExp('#' + '(.*)' + ' from')); + let name = replyText.match(new RegExp('from ' + '(.*)' + ' @')); + if (ctx.message.text === 'me') { + // accept ticket + bot.telegram.sendMessage( + config.staffchat_id, + '' + + config.lang_ticket + + ' #' + + userid[1] + + ' ' + + config.lang_acceptedBy + + ' ' + + ctx.message.from.first_name + + ' -> /open', + cache.noSound + ); + } else { + cache.ticketStatus[userid[1]] = false; + bot.telegram.sendMessage( + userid[1], + config.lang_dear + + ' ' + + name[1] + + ',\n\n' + + ctx.message.text + + '\n\n' + + config.lang_regards + + '\n' + + ctx.message.from.first_name, + cache.html + ); + console.log( + 'Answer: ' + + config.lang_ticket + + ' #' + + cache.tickedID + + ' ' + + config.lang_dear + + ' ' + + name[1] + + ' ' + + ctx.message.text + + ' ' + + config.lang_from + + ' ' + + ctx.message.from.first_name + ); + } + cache.ticketSent[cache.tickedID] = undefined; + } catch (e) {} + } + }) + .catch(function(noAdmin) { + console.log('Error with admins: ' + noAdmin); + }); +} + +function customerChat(ctx, bot, chat) { + cache.tickedID = ctx.message.from.id; + if (cache.ticketIDs[cache.ticketID] === undefined) { + cache.ticketIDs.push(cache.tickedID); + } + cache.ticketStatus[cache.tickedID] = true; + userInfo = ''; + userInfo += + ' ' + config.lang_from + ' ' + ctx.message.from.first_name + ' '; + userInfo += + '@' + + ctx.message.from.username + + ' ' + + config.lang_language + + ': ' + + ctx.message.from.language_code + + '\n\n'; + if (cache.ticketSent[cache.tickedID] === undefined) { + bot.telegram.sendMessage(chat.id, config.lang_contactMessage); + bot.telegram.sendMessage( + config.staffchat_id, + '' + + config.lang_ticket + + ' #' + + cache.tickedID + + userInfo + + ctx.message.text, + cache.html + ); + // wait 5 minutes before this message appears again and do not + // send notificatoin sounds in that time to avoid spam + setTimeout(function() { + cache.ticketSent[cache.tickedID] = undefined; + }, config.spam_time); + cache.ticketSent[cache.tickedID] = 0; + } else if (cache.ticketSent[cache.tickedID] < 5) { + cache.ticketSent[cache.tickedID]++; + bot.telegram.sendMessage( + config.staffchat_id, + '' + + config.lang_ticket + + ' #' + + cache.tickedID + + userInfo + + ctx.message.text, + cache.noSound + ); + } else if (cache.ticketSent[cache.tickedID] === 5) { + cache.ticketSent[cache.tickedID]++; + bot.telegram.sendMessage(chat.id, config.lang_blockedSpam); + } + console.log( + 'Ticket: ' + + ' #' + + cache.tickedID + + userInfo.replace('\n\n', ': ') + + ctx.message.text + ); +} + +function videoHandler(bot, ctx) { + forwardFile(bot, ctx, function(userInfo) { + bot.telegram.sendVideo(config.staffchat_id, ctx.message.video.file_id, { + caption: + config.lang_ticket + + ': #' + + cache.ticketID + + '\n' + + userInfo + + '\n' + + (ctx.message.caption || ''), + }); + }); +} + +function photoHandler(bot, ctx) { + forwardFile(bot, ctx, function(userInfo) { + bot.telegram.sendPhoto(config.staffchat_id, ctx.message.photo[0].file_id, { + caption: + config.lang_ticket + + ': #' + + cache.ticketID + + '\n' + + userInfo + + '\n' + + (ctx.message.caption || ''), + }); + }); +} + +function documentHandler(bot, ctx) { + forwardFile(bot, ctx, function(userInfo) { + bot.telegram.sendDocument( + config.staffchat_id, + ctx.message.document.file_id, + { + caption: + config.lang_ticket + + ': #' + + cache.ticketID + + '\n' + + userInfo + + (ctx.message.caption || ''), + } + ); + }); +} + +function forwardFile(bot, ctx, callback) { + if (cache.ticketSent[cache.tickedID] === undefined) { + fowardHandler(ctx, function(userInfo) { + callback(userInfo); + }); + // wait 5 minutes before this message appears again and do not + // send notificatoin sounds in that time to avoid spam + setTimeout(function() { + cache.ticketSent[cache.tickedID] = undefined; + }, config.spam_time); + cache.ticketSent[cache.tickedID] = 0; + } else if (cache.ticketSent[cache.tickedID] < 5) { + cache.ticketSent[cache.tickedID]++; + // TODO: add cache.noSound property for silent notifications + fowardHandler(ctx, function(userInfo) { + callback(userInfo); + }); + } else if (cache.ticketSent[cache.tickedID] === 5) { + cache.ticketSent[cache.tickedID]++; + bot.telegram.sendMessage(chat.id, config.lang_blockedSpam); + } +} + +function fowardHandler(ctx, callback) { + ctx.getChat().then(function(chat) { + if (chat.type === 'private') { + cache.ticketID = ctx.message.from.id; + userInfo = ''; + userInfo += config.lang_from + ' ' + ctx.message.from.first_name + ' '; + userInfo += + '@' + + ctx.message.from.username + + '\n' + + config.lang_language + + ': ' + + ctx.message.from.language_code + + '\n\n'; + callback(userInfo); + } + }); +} + +module.exports = { + ticket: ticketHandler, + photo: photoHandler, + video: videoHandler, + document: documentHandler, +}; diff --git a/config.js b/config.js index 0216267..cd04025 100644 --- a/config.js +++ b/config.js @@ -1,20 +1,23 @@ -module.exports = { - // bot settings - bot_token: "YOUR_BOT_TOKEN", // support bot token - staffchat_id: "SUPERGROUP_CHAT_ID", // telegram staff group chat id eg. -123456789 - owner_id: "YOUR_TELEGRAM_ID", - supported_bot: "service_name", // service name of the supported bot leave empty if you don't have one - spam_time: 5 * 60 * 1000, // time (in MS) in which the user may send up to 5 messages to avoid spam DEFAULT: 5 Minutes - - // customize your language - startCommandText: "Welcome in our support chat! Ask your question here.", - faqCommandText: "Check out our FAQ here: Address to your FAQ", - lang_contactMessage: "Thank you for contacting us. We will answer as soon as possible.", - lang_blockedSpam: "You sent quite a number of questions in the last while. Please calm down and wait until staff reviews them.", - lang_ticket: "Ticket", - lang_acceptedBy: "was accepted by", - lang_dear: "Dear", - lang_regards: "Best regards,", - lang_from: "from", - lang_language: "Language" -}; \ No newline at end of file +module.exports = { + // bot settings + bot_token: 'YOUR_BOT_TOKEN', // support bot token + staffchat_id: 'SUPERGROUP_CHAT_ID', // eg. -123456789 + owner_id: 'YOUR_TELEGRAM_ID', + supported_bot: 'service_name', // service name of the supported bot + spam_time: 5 * 60 * 1000, // time (in MS) in which user may send 5 messages + + // customize your language + startCommandText: 'Welcome in our support chat! Ask your question here.', + faqCommandText: 'Check out our FAQ here: Address to your FAQ', + lang_contactMessage: + `Thank you for contacting us. We will answer as soon as possible.`, + lang_blockedSpam: + `You sent quite a number of questions in the last while. + Please calm down and wait until staff reviews them.`, + lang_ticket: 'Ticket', + lang_acceptedBy: 'was accepted by', + lang_dear: 'Dear', + lang_regards: 'Best regards,', + lang_from: 'from', + lang_language: 'Language', +}; diff --git a/package-lock.json b/package-lock.json index b3aa8b1..251990a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -155,21 +155,21 @@ } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", + "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", + "fast-deep-equal": "2.0.1", "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" } }, "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", "dev": true }, "ansi-align": { @@ -1131,12 +1131,6 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, "co-with-promise": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co-with-promise/-/co-with-promise-4.6.0.tgz", @@ -1188,18 +1182,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "1.1.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "typedarray": "0.0.6" - } - }, "concordance": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/concordance/-/concordance-3.0.0.tgz", @@ -1500,30 +1482,30 @@ "dev": true }, "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.4.0.tgz", + "integrity": "sha512-UIpL91XGex3qtL6qwyCQJar2j3osKxK9e3ano3OcGEIRM4oWIpCkDg9x95AXEC2wMs7PnxzOkPZ2gq+tsMS9yg==", "dev": true, "requires": { - "ajv": "5.5.2", + "ajv": "6.5.3", "babel-code-frame": "6.26.0", "chalk": "2.4.1", - "concat-stream": "1.6.2", - "cross-spawn": "5.1.0", + "cross-spawn": "6.0.5", "debug": "3.1.0", "doctrine": "2.1.0", - "eslint-scope": "3.7.3", + "eslint-scope": "4.0.0", + "eslint-utils": "1.3.1", "eslint-visitor-keys": "1.0.0", - "espree": "3.5.4", + "espree": "4.0.0", "esquery": "1.0.1", "esutils": "2.0.2", "file-entry-cache": "2.0.0", "functional-red-black-tree": "1.0.1", "glob": "7.1.2", "globals": "11.7.0", - "ignore": "3.3.10", + "ignore": "4.0.6", "imurmurhash": "0.1.4", - "inquirer": "3.3.0", + "inquirer": "5.2.0", "is-resolvable": "1.1.0", "js-yaml": "3.12.0", "json-stable-stringify-without-jsonify": "1.0.1", @@ -1536,23 +1518,67 @@ "path-is-inside": "1.0.2", "pluralize": "7.0.0", "progress": "2.0.0", - "regexpp": "1.1.0", + "regexpp": "2.0.0", "require-uncached": "1.0.3", "semver": "5.5.0", "strip-ansi": "4.0.0", "strip-json-comments": "2.0.1", - "table": "4.0.2", + "table": "4.0.3", "text-table": "0.2.0" }, "dependencies": { + "acorn-jsx": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", + "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", + "dev": true, + "requires": { + "acorn": "5.7.1" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "1.0.4", + "path-key": "2.0.1", + "semver": "5.5.0", + "shebang-command": "1.2.0", + "which": "1.3.1" + } + }, + "espree": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", + "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", + "dev": true, + "requires": { + "acorn": "5.7.1", + "acorn-jsx": "4.1.1" + } + }, "globals": { "version": "11.7.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true } } }, + "eslint-config-google": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.9.1.tgz", + "integrity": "sha512-5A83D+lH0PA81QMESKbLJd/a3ic8tPZtwUmqNrxMRo54nfFaUvtt89q/+icQ+fd66c2xQHn0KyFkzJDoAUfpZA==", + "dev": true + }, "eslint-config-standard": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-11.0.0.tgz", @@ -1724,15 +1750,21 @@ "dev": true }, "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { "esrecurse": "4.2.1", "estraverse": "4.2.0" } }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -1860,9 +1892,9 @@ } }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, "fast-diff": { @@ -2825,9 +2857,9 @@ "dev": true }, "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", + "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", "dev": true, "requires": { "ansi-escapes": "3.1.0", @@ -2839,8 +2871,7 @@ "lodash": "4.17.10", "mute-stream": "0.0.7", "run-async": "2.3.0", - "rx-lite": "4.0.8", - "rx-lite-aggregates": "4.0.8", + "rxjs": "5.5.11", "string-width": "2.1.1", "strip-ansi": "4.0.0", "through": "2.3.8" @@ -3143,9 +3174,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -3575,6 +3606,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "nice-try": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", + "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "dev": true + }, "node-fetch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", @@ -3984,6 +4021,12 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, "randomatic": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", @@ -4120,9 +4163,9 @@ } }, "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", + "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", "dev": true }, "regexpu-core": { @@ -4282,19 +4325,21 @@ "is-promise": "2.1.0" } }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "rxjs": { + "version": "5.5.11", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", + "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", "dev": true, "requires": { - "rx-lite": "4.0.8" + "symbol-observable": "1.0.1" + }, + "dependencies": { + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "dev": true + } } }, "safe-buffer": { @@ -4569,13 +4614,13 @@ "dev": true }, "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", + "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "dev": true, "requires": { - "ajv": "5.5.2", - "ajv-keywords": "2.1.1", + "ajv": "6.5.3", + "ajv-keywords": "3.2.0", "chalk": "2.4.1", "lodash": "4.17.10", "slice-ansi": "1.0.0", @@ -4685,12 +4730,6 @@ "prelude-ls": "1.1.2" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "typescript": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz", @@ -4746,6 +4785,15 @@ "xdg-basedir": "3.0.0" } }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "2.1.1" + } + }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", diff --git a/package.json b/package.json index a3811a7..c5f9865 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "telegram-support-bot", - "version": "0.10.0", + "version": "0.20.0", "description": "Telegram Support Bot", "license": "GPLv3", "author": "bostrot ", @@ -14,9 +14,6 @@ }, "main": "bin/support.js", "scripts": { - "lint": "eslint .", - "test": "ava test", - "precommit": "npm run lint && npm test", "start": "node bin/support.js" }, "engines": { @@ -30,15 +27,12 @@ "cron": "^1.3.0" }, "devDependencies": { - "ava": "^0.25.0", - "eslint": "^4.1.1", - "eslint-config-standard": "^11.0.0", - "eslint-plugin-ava": "^4.0.0", + "eslint": "^5.4.0", + "eslint-config-google": "^0.9.1", "eslint-plugin-import": "^2.2.0", "eslint-plugin-node": "^6.0.0", "eslint-plugin-promise": "^3.5.0", - "eslint-plugin-standard": "^3.0.1", - "husky": "^0.14.1" + "eslint-plugin-standard": "^3.0.1" }, "keywords": [ "telegram-support-bot",