'use strict';
/**
* TelegramService
* @namespace TelegramService
* @description Manages telegram service two way communication
*/
const hooks = require('./hooks');
const config = require('./config');
const logger = require('./logger');
const _ = require('underscore');
const s = require('underscore.string');
_.mixin(s.exports());
const Promise = require('promise');
const Telegram = require('node-telegram-bot-api');
/**
* @property {Telegram} api Link to Telegram Module
* @private
* @memberof TelegramService
*/
let api = null;
/**
* @property {Boolean} initialized If initialized
* @private
* @memberof TelegramService
*/
let initialized = false;
let next_manage_reply = {};
/**
* @function register_message_hook
* @description Check if call is authorized
* @static
* @param {Object} hook Hook to be registered on Telegram Bot
* @memberof TelegramService
* @public
* @returns {Promise}
*/
const register_message_hook = function (hook) {
return new Promise(function (resolve, reject) {
let match = null;
if (hook.match) {
match = hook.match;
} else {
return reject(new Error('No matching string'));
}
let manager = _.bind(function (msg, match) {
manage_message(msg, match, this);
}, hook);
api.onText(match, manager);
resolve();
});
};
/**
* @function manage_message
* @description Check if call is authorized
* @static
* @param {Object} message Message received from the Bot
* @param {String[]} matches Regex captured matches
* @param {Object} hook Managed hook
* @memberof TelegramService
* @private
*/
const manage_message = function (message, matches, hook) {
if (message.from && message.from.username && (!config.get('allowed_usernames') || ((config.get('allowed_usernames') && _.contains(config.get('allowed_usernames')), message.from.username.toLowerCase())))) {
let message_text = message.text || message.caption;
if (message_text) {
message_text = _.clean(message_text);
if (!TelegramService.is_hooked() && message.chat && message.chat.id) {
TelegramService.set_hook_id(message.chat.id);
logger.log(`Hooked to chat id #${TelegramService.get_hook_id()}`);
}
logger.log(`Executing ${hook.full_name}`);
hook.action(message, TelegramService, matches).then(function (response) {
logger.notify(`Executed ${hook.full_name}`);
}).catch(function (error) {
logger.error(`Error executing ${hook.full_name}: ${error}`);
});
}
}
};
/**
* @class
* @classdesc Manages telegram service two way communication
*/
const TelegramService = {
register_message_hook: register_message_hook,
/**
* @function get_hook_id
* @description Get Bot chat ID
* @static
* @memberof TelegramService
* @public
* @returns {Number}
*/
get_hook_id: function () {
return process.env.TEL_CID || config.get('telegram:chat_id');
},
/**
* @function is_hooked
* @description Check if Chat ID has been linked
* @static
* @memberof TelegramService
* @public
* @returns {Boolean}
*/
is_hooked: function () {
return !!(TelegramService.get_hook_id() || 0);
},
/**
* @function set_hook_id
* @description Set Bot chat ID
* @static
* @param {Number} id Chat ID to be set
* @memberof TelegramService
* @public
* @returns {Number}
*/
set_hook_id: function (id) {
process.env.TEL_CID = id;
config.set('telegram:chat_id', id);
return TelegramService.get_hook_id();
},
/**
* @function respond
* @description Respond to a user's message
* @static
* @param {Object} message Message you want answer to
* @param {String} content Answer's content
* @param {Boolean} plain Disable markdown/html parse mode
* @memberof TelegramService
* @public
* @returns {Promise}
*/
respond: function (message, content, plain) {
let chat_id = (message.chat.id || TelegramService.get_hook_id());
if (chat_id) {
return api.sendMessage(chat_id, content, {
parse_mode: plain === true ? null : config.get('telegram:parse_mode'),
reply_to_message_id: message.id
});
} else {
let error = new Error('Telegram service not hooked. Send first message.');
return Promise.reject(error);
}
},
/**
* @function send
* @description Send a message to user
* @static
* @param {String} content Message's content
* @param {Boolean|Array} reply Force a user reply using normal or customized keyboard
* @param {Array} accepted_responses Validates user response
* @param {Boolean} one_time_keyboard Close custom keyboard after use
* @param {Boolean} plain Disable markdown/html parse mode
* @memberof TelegramService
* @public
* @returns {Promise}
*/
send: function (content, reply, accepted_responses, one_time_keyboard, plain) {
if (TelegramService.is_hooked()) {
let options = {
parse_mode: plain === true ? null : config.get('telegram:parse_mode')
};
let has_keyboard = false;
if (_.isBoolean(reply) && reply) {
options.reply_markup = {
force_reply: true
};
} else if (_.isArray(reply) && reply.length > 0) {
has_keyboard = true;
if (!accepted_responses) {
accepted_responses = [];
_.each(reply, function (el) {
if (_.isArray(el)) {
accepted_responses = _.union(accepted_responses, el);
} else {
accepted_responses.push(accepted_responses);
}
});
accepted_responses = _.map(accepted_responses, function (el) {
return _.trim(el).toString().toLowerCase();
});
}
options.reply_markup = {
force_reply: true,
keyboard: reply,
one_time_keyboard: one_time_keyboard !== false
};
}
if (options.reply_markup) {
return new Promise(function (resolve, reject) {
return api.sendMessage(TelegramService.get_hook_id(), content, options).then(function (output) {
if (output && output.message_id) {
let chat_id = (output.chat.id || TelegramService.get_hook_id());
let manage_reply = function (reply_message) {
if (reply_message && reply_message.text) {
let reply_message_compare = _.trim(reply_message.text).toLowerCase();
if (accepted_responses && _.isArray(accepted_responses) && accepted_responses.length) {
if (_.contains(accepted_responses, reply_message_compare)) {
resolve(reply_message);
} else {
reject(new Error('Reply response invalid'));
}
} else {
resolve(reply_message);
}
} else {
reject(new Error('Reply message not received'));
}
};
if (has_keyboard) {
next_manage_reply[chat_id] = {
resolve: manage_reply,
reject: reject
};
} else {
api.onReplyToMessage(chat_id, output.message_id, manage_reply);
}
} else {
reject(new Error('Reply message not sent'));
}
}).catch(reject);
});
} else {
return api.sendMessage(TelegramService.get_hook_id(), content, options);
}
} else {
let error = new Error('Telegram service not hooked. Send first message.');
return Promise.reject(error);
}
},
/**
* @function init
* @description Initialize Telegram Manager
* @static
* @param {Number} tcid Chat ID (if provided)
* @param {Boolean} is_command If it's an 'one shot' command will not poll or log.
* @memberof TelegramService
* @public
* @returns {Promise}
*/
init: function (tcid, is_command) {
return new Promise(function (resolve, reject) {
hooks.load().then(function () {
let token = config.get('telegram:token') || process.env.TEL_TOKEN;
if (tcid) {
TelegramService.set_hook_id(tcid);
}
api = new Telegram(token, {
polling: (is_command ? false : {
interval: (config.get('telegram:interval') || 1000) * 1,
timeout: (config.get('telegram:interval') || 1000) * 6
})
});
api.getMe().then(function (data) {
data = data || {};
if (is_command !== true) {
logger.log(`Using bot @${data.username}, ${data.first_name}, ID: ${data.id}`);
if (TelegramService.is_hooked()) {
logger.log(`Hooked to chat id #${TelegramService.get_hook_id()}`);
} else {
logger.log(`Telegram not hooked. Waiting first message to hook to chat.`);
}
}
api.on('message', function (message) {
let chat_id = (message.chat.id || TelegramService.get_hook_id());
if (message && chat_id && next_manage_reply[chat_id]) {
let handler = next_manage_reply[chat_id];
handler.resolve(message);
delete next_manage_reply[chat_id];
}
});
initialized = true;
resolve(TelegramService);
}).catch(reject);
}).catch(reject);
});
}
};
module.exports = TelegramService;