UNPKG

ircgrampp

Version:

IRCGram++ is a complexly simple Telegram <-> IRC Gateway

446 lines (357 loc) 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.TelegramChannel = void 0; var _events = require("events"); var _debug = _interopRequireDefault(require("debug")); var _lodash = require("lodash"); var _nodeTelegramBotApi = _interopRequireDefault(require("node-telegram-bot-api")); var _channelsinfo = _interopRequireDefault(require("./channelsinfo")); var _hooks = require("./hooks"); var _dec, _dec2, _dec3, _dec4, _class; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; } var Promise = require("bluebird"); const debug = { channel: (0, _debug.default)("telegram-channel"), connection: (0, _debug.default)("telegram-connection") }; const NEW_CHAT_PARTICIPANT = "new_chat_participant"; const LEFT_CHAT_PARTICIPANT = "left_chat_participant"; const CHANEXP = /^-?[0-9]+$/; const defaultConfig = { token: null, autoConnect: true }; let instances = []; /** * Telegram channel wrapper */ let TelegramChannel = (_dec = (0, _hooks.syncHookedMethod)('telegramchannel:create', 'channel', 'connector'), _dec2 = (0, _hooks.asyncHookedMethod)('telegramchannel:handle.message', 'user', 'message', 'channelInfo'), _dec3 = (0, _hooks.asyncHookedMethod)('telegramchannel:set.data', 'data', 'sync'), _dec4 = (0, _hooks.asyncHookedMethod)('telegramchannel:message.send', 'message', 'options'), (_class = class TelegramChannel extends _events.EventEmitter { /** * Create channel wrapper for telegram * @param {int|string} channel The channel title, or ID * @param {TelegramConnection} connector The connector instance */ constructor(channel, connector) { super(); this._constructor(channel, connector); } _constructor(channel, connector) { debug.channel("Start channel"); this._channel = channel; this._connector = connector; if (typeof this._channel === 'string' && this._channel.match(CHANEXP)) { debug.channel("Channel is string but has format of ID"); this._channel = parseInt(this._channel, 10); } this._hasInfo = false; this._chatId = null; this._chatType = null; this._chatTitle = null; this._chatLastUpdated = null; let channelsInfo = _channelsinfo.default.getInstance(); let chatInfo = channelsInfo.find(this._channel); this._handlers = { message: this._handleMessage.bind(this), join: this._handleNewParticipant.bind(this), left: this._handleLeftParticipant.bind(this) }; if (chatInfo) { debug.channel("Have info about channel, setting"); this.setData(chatInfo, false); } else { debug.channel("Don't have info about channel, bind"); this.bind(); } return this; } _handleMessage(user, message) { if (!message) { debug.channel("unknow message, ignoring"); return; } this.emit("message", user, message); } _handleNewParticipant(...args) { this.emit("join", ...args); } _handleLeftParticipant(...args) { this.emit("left", ...args); } _handleInfoUpdate(data) { if (data.id === this._channel || data.title === this._channel) { this.setData(data); } } unbind() { let prefix = this.channelPrefix; debug.channel(`unbind channel ${prefix}`); this._connector.removeListener(`${prefix}:message`, this._handlers.message); this._connector.removeListener(`${prefix}:newparticipant`, this._handleNewParticipant); this._connector.removeListener(`${prefix}:leftparticipant`, this._handleLeftParticipant); this._connector.removeListener("chatinformationupdate", this._handleInfoUpdate); } bind() { let prefix = this.channelPrefix; debug.channel(`bind channel ${prefix}`); this._connector.on(`${prefix}:message`, this._handlers.message); this._connector.on(`${prefix}:newparticipant`, this._handleNewParticipant.bind(this)); this._connector.on(`${prefix}:leftparticipant`, this._handleLeftParticipant.bind(this)); this._connector.on("chatinformationupdate", this._handleInfoUpdate.bind(this)); } /** * Set channel information * @param {object} data */ setData(data, sync = true) { this.unbind(); if (sync && (this._chatTitle !== data.title || this._chatType !== data.type)) { _channelsinfo.default.getInstance().save(data); } this._hasInfo = true; this._chatId = data.id; this._chatType = data.type; this._chatTitle = data.title; this._chatLastUpdated = data.updated; this.bind(); debug.channel("Information updateted"); this.emit("updateinformation", data); } /** * Wait to get channel information, this is necesary for exampleto * send a message * @return {Promise} */ waitInformation() { if (this._hasInfo) { return Promise.resolve(); } return new Promise(resolve => { this.once("updateinformation", () => { return resolve(); }); }); } /** * Send a message to the channel * @param {string} message */ sendMessage(message, options) { return this.waitInformation().then(() => { return this._connector.send(this._chatId, message, options || {}); }); } /** * Get the channel prefix * @return {string} The channel prefix */ get channelPrefix() { if (this._hasInfo) { return `#${this._chatId}`; } else if (typeof this._channel === "string") { return `@@${this._channel}`; } else if (typeof this._channel === "number") { return `#${this._channel}`; } else { throw new Error("Unknow channel type"); } } /** * Name of channel * @property name */ get name() { return this.channelPrefix; } }, (_applyDecoratedDescriptor(_class.prototype, "_constructor", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "_constructor"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "_handleMessage", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, "_handleMessage"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "setData", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, "setData"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "sendMessage", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, "sendMessage"), _class.prototype)), _class)); /** * Telegram connection helper */ exports.TelegramChannel = TelegramChannel; class TelegramConnection extends _events.EventEmitter { /** * Create new connection with telegram * @param {object} options Options * @param {string} options.token Telegram API token */ constructor(options) { super(); this._tgBot = null; this._options = (0, _lodash.assignIn)({}, defaultConfig, options); this._channels = []; if (this._options.autoConnect) { (0, _lodash.noop)(this.tgBot); } instances.push(this); } /** * Send a message * @param {string|number} chatId The chat ID * @param {string} msg Message */ send(chatId, msg, options) { return this.tgBot.sendMessage(chatId, msg, options); } /** * Get the chat info from a incomming message * @param {object} msg * @return {object} */ getChatInfo(msg) { let { chat } = msg; return { chatId: chat.id, chatType: chat.type, chatTitle: chat.title }; } /** * Generate a chat event prefix * @param {object} msg * @return {object} */ getChatEvPrefix(msg) { let { chatId, chatTitle } = this.getChatInfo(msg); return { idPrefix: `#${chatId}`, titlePrefix: `@@${chatTitle}` }; } /** * Refresh the chat information when it's received a new message * @param {object} chatData */ refreshChatInformation(chatData) { let { id, title, type } = chatData; let now = new Date().getTime(); let channelsInfo = _channelsinfo.default.getInstance(); let current = channelsInfo.find(id); if (current && current.type === type && current.id === id && current.title === title) { return; } let data = (0, _lodash.assignIn)({}, chatData, { updated: now }); channelsInfo.save(data); debug.connection("channel info update", data.id, data.title); this.emit("chatinformationupdate", data); } /** * Follow a channel * @param {string|number} channelId The channel to follow * @return {TelegramChannel} The channel wrapper */ followChannel(channelId) { let channel = new TelegramChannel(channelId, this); this._channels.push(channel); return channel; } /** * TelegramBot connection * @see https://github.com/yagop/node-telegram-bot-api * @property tgBot */ get tgBot() { if (this._tgBot) { return this._tgBot; } debug.connection("Token", this._options.token); this._tgBot = new _nodeTelegramBotApi.default(this._options.token, { polling: true }); this._tgBot.on("message", msg => { let { from, text, chat } = msg; let { idPrefix, titlePrefix } = this.getChatEvPrefix(msg); this.refreshChatInformation(chat); debug.connection(`${titlePrefix} (${idPrefix}): ${from.id} -> ${text}`); this.emit("telegram:message", chat, from, text); this.emit(`${idPrefix}:message`, from, text, chat); this.emit(`${titlePrefix}:message`, from, text, chat); }); this._tgBot.on("new_chat_participant", msg => { let newParticipant = msg[NEW_CHAT_PARTICIPANT]; let { idPrefix, titlePrefix } = this.getChatEvPrefix(msg); this.refreshChatInformation(msg.chat); debug.connection(`${titlePrefix} (${idPrefix}): ` + `new participant ${newParticipant}`); this.emit("telegram:newparticipant", msg.chat, newParticipant); this.emit(`${idPrefix}:newparticipant`, newParticipant); this.emit(`${titlePrefix}:newparticipant`, newParticipant); }); this._tgBot.on("left_chat_participant", msg => { let { idPrefix, titlePrefix } = this.getChatEvPrefix(msg); let leftParticipant = msg[LEFT_CHAT_PARTICIPANT]; this.refreshChatInformation(msg.chat); debug.connection(`${titlePrefix} (${idPrefix}): ` + `left participant ${leftParticipant}`); this.emit("telegram:leftparticipant", msg.chat, leftParticipant); this.emit(`${idPrefix}:leftparticipant`, leftParticipant); this.emit(`${titlePrefix}:leftparticipant`, leftParticipant); }); this._tgBot.getMe().then(me => { this._me = me; this.emit("getme"); }); return this._tgBot; } /** * Information about the connected bot * @property */ get me() { if (this._me) { return Promise.resolve(this._me); } return new Promise(resolve => { this.once("getme", () => { return resolve(this._me); }); }); } /** * The token that is connected the bot * @property */ get token() { return this._options.token; } /** * Get an instance of TelegramConnector by token, if the instance * does not exists, it's created, like getInstance, but by token * @param {string} token The token * @param {object} options Options for new instance creation * @return {TelegramConnection} */ static getByToken(token, options = {}) { let instance = instances.find(i => i.token === token); if (!instance) { instance = new TelegramConnection((0, _lodash.assignIn)(options, { token })); } return instance; } } exports.default = TelegramConnection;