ircgrampp
Version:
IRCGram++ is a complexly simple Telegram <-> IRC Gateway
446 lines (357 loc) • 12.4 kB
JavaScript
"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;