ircgrampp
Version:
IRCGram++ is a complexly simple Telegram <-> IRC Gateway
582 lines (456 loc) • 16.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.UserBridge = exports.resolveNick = exports.defaultConfig = exports.messages = void 0;
var _events = require("events");
var _debug = _interopRequireDefault(require("debug"));
var _lodash = require("lodash");
var _escapeStringRegexp = _interopRequireDefault(require("escape-string-regexp"));
var _crc = require("crc-32");
var _ircColors = _interopRequireDefault(require("irc-colors"));
var _hooks = require("./hooks");
var _irc = _interopRequireDefault(require("./irc"));
var _telegram = _interopRequireDefault(require("./telegram"));
var _util = require("./util");
var _dec, _dec2, _dec3, _class, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _class2;
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; }
const debug = (0, _debug.default)("bridge");
const colors = ['navy', 'green', 'red', 'maroom', 'violet', 'olive', 'lime', 'teal', 'cyan', 'blue', 'pink'];
const messages = {
join: "[IRC/{nick} * joined to the channel *]"
};
exports.messages = messages;
const defaultConfig = {
irc: null,
telegram: null,
lang: null,
oneConnectionByUser: false,
showJoinLeft: true,
prefix: "telegram_",
suffix: "",
ircScapeCharacter: ""
};
exports.defaultConfig = defaultConfig;
let userInstances = [];
/**
* Resolve nicks with the prefix and suffix options
* @param {object} userData
* @param {object} options
* @return {string}
*/
const resolveNick = function (userData, options) {
"use strict";
let name = (userData.username || `${userData.first_name}-${userData.id}`).replace(/[^a-z_]+/ig, '');
let {
prefix,
suffix
} = options;
return `${prefix}${name}${suffix}`;
};
/**
* User bridge, for oneConnectionByUser option
*/
exports.resolveNick = resolveNick;
let UserBridge = (_dec = (0, _hooks.syncHookedMethod)('userbridge:create', 'userData', 'options'), _dec2 = (0, _hooks.syncHookedMethod)('userbridge:get.channel'), _dec3 = (0, _hooks.syncHookedMethod)('userbridge:get.by.options', 'userData', 'options'), (_class = class UserBridge extends _events.EventEmitter {
/**
* Create new UserBridge
* @param {object} userData The telegram format user information
* @param {object} options Bridge options
*/
constructor(userData, options) {
super();
this._constructor(userData, options);
}
_constructor(userData, options) {
this._options = (0, _lodash.assignIn)({}, {
prefix: "tele_",
suffix: ""
}, options);
this._userData = userData;
this._name = userData.username || `${userData.first_name}-${userData.id}`;
let rnick = resolveNick(userData, this._options);
let serveropts = (0, _lodash.assignIn)({}, options.irc, {
nick: rnick
});
debug(`Search irc connection for ${rnick}@${serveropts.server}`);
this._ircConnection = _irc.default.getByServerOptions(serveropts);
userInstances.push(this);
return this;
}
/**
* Get channel from the user bridge
* @param {string} channelName IRC channel name
* @return {IRCChannel}
*/
getChannel(channelName) {
let channel = this._ircConnection.getChannel(channelName);
if (!channel) {
channel = this._ircConnection.addChannel(channelName);
}
return channel;
}
get originalNick() {
return resolveNick(this._userData, this._options);
}
/**
* Final nickname (applied prefix/suffix);
* @property nick
*/
get nick() {
return this._ircConnection.nick;
}
/**
* Original name (telegram username)
* @property name
*/
get name() {
return this._name;
}
/**
* Original telegram id
* @property tid
*/
get tid() {
return this._userData.id;
}
/**
* Unique identifier, generated with username and server options
* @property identifier
*/
get identifier() {
let tid = this.tid;
let ircIdent = this._ircConnection.identifier;
return `${tid}@@${ircIdent}`;
}
/**
* Get UserBridge by options
* @param {object} userData Original information
* @param {object} options Bridge options
* @return {UserBridge}
*/
static getByOptions(userData, options) {
let rnick = resolveNick(userData, options);
let ircOptions = (0, _lodash.assignIn)({}, options.irc, {
nick: rnick
});
let irc = _irc.default.getByServerOptions(ircOptions);
let ircIdent = irc.identifier;
let userIdent = `${userData.id}@@${ircIdent}`;
debug(`Find for UserBridge ${userIdent}`);
let instance = userInstances.find(u => u.identifier === userIdent);
if (!instance) {
debug("Not foud, creating");
instance = new UserBridge(userData, options);
}
return instance;
}
}, (_applyDecoratedDescriptor(_class.prototype, "_constructor", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "_constructor"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "getChannel", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, "getChannel"), _class.prototype), _applyDecoratedDescriptor(_class, "getByOptions", [_dec3], Object.getOwnPropertyDescriptor(_class, "getByOptions"), _class)), _class));
/**
* Bridge class
*/
exports.UserBridge = UserBridge;
let Bridge = (_dec4 = (0, _hooks.syncHookedMethod)('bridge:create', 'name', 'ircChannel', 'telegramChannel', 'options'), _dec5 = (0, _hooks.syncHookedMethod)('bridge:get.irc.color', 'nick'), _dec6 = (0, _hooks.syncHookedMethod)("bridge:get.irc.user"), _dec7 = (0, _hooks.syncHookedMethod)('bridge:get.irc.user.channel'), _dec8 = (0, _hooks.asyncHookedMethod)('bridge:irc.handle.message', 'user', 'message'), _dec9 = (0, _hooks.asyncHookedMethod)('bridge:telegram.handle.message', 'user', 'message'), (_class2 = class Bridge extends _events.EventEmitter {
/**
* Create new bridge
* @param {string} name The name of the bridge
* @param {object} options Options
* @param {object} irc IRC options
* @param {object} telegram Telegram options
* @param {string} [lang] Language of bridge messages
* @param {bool} [oneConnectionByUser] Create one new connection to IRC by
* each new user in telegram. By
* default is false
* @param {string} [prefix] User nickname prefix
* @param {string} [suffix] User nickname suffix
*/
constructor(name, ircChannel, telegramChannel, options) {
super();
this._constructor(name, ircChannel, telegramChannel, options);
}
_constructor(name, ircChannel, telegramChannel, options) {
this._options = (0, _lodash.assignIn)({}, defaultConfig, options);
this._ircUsers = [];
debug(`Create bridge ${name}`);
this._name = name;
this._ircConnector = _irc.default.getByServerOptions(this._options.irc);
this._ircChannel = this._ircConnector.addChannel(ircChannel);
this._telegramConnector = _telegram.default.getByToken(this._options.telegram.token, this._options.telegram);
this._telegramChannel = this._telegramConnector.followChannel(telegramChannel);
this._telegramSuccessMe = false;
this._ircSuccessRegistered = false;
this._handlers = {
ircMessage: this._handleIRCMessage.bind(this),
ircAction: this._handleIRCAction.bind(this),
ircJoin: this._handleIRCJoin.bind(this),
ircLeft: this._handleIRCLeft.bind(this),
ircTopic: this._handleIRCTopic.bind(this),
telegramMessage: this._handleTelegramMessage.bind(this),
telegramJoin: this._handleTelegramJoin.bind(this),
telegramLeft: this._handleTelegramLeft.bind(this)
};
this._nickColorCache = [];
this.bind();
return this;
}
/**
* Translate IRC nicks to Telegram nicks
* @param {string} message Message to translate nicks
* @return {string} Final message
*/
_translateIrcNicks(message) {
return this._ircUsers.map(x => {
let {
name,
nick
} = x;
return {
name,
nick
};
}).reduce((message, replaces) => {
let {
name,
nick
} = replaces;
let snick = (0, _escapeStringRegexp.default)(nick);
let regexptxt = `\\b${snick}\\b`;
let regexp = new RegExp(regexptxt, "g");
return message.replace(regexp, `@${name}`);
}, message);
}
/**
* Get a color from username
* @param {string} nick
* @return {string} Color name
*/
_getIrcColor(nick) {
let c = this._nickColorCache.find(x => x.str === nick);
if (c) {
return c.color;
}
let colorIndex = Math.abs((0, _crc.str)(nick) % colors.length);
let color = colors[colorIndex];
this._nickColorCache.push({
str: nick,
color
});
if (this._nickColorCache.length > 30) {
this._nickColorCache.shift();
}
return color;
}
/**
* Get UserBridge from a telegram user
* @param {object} userData Telegram user information
* @return {UserBridge}
*/
_getIrcUser(userData) {
let user = this._ircUsers.find(x => x.tid === userData.id);
if (!user) {
user = UserBridge.getByOptions(userData, this._options);
this._ircUsers.push(user);
}
return user;
}
/**
* Return true if there are a UserBridge with the passed irc nick
* @param {string} nick IRC nick
* @return {bool}
*/
_haveIrcUser(nick) {
debug(`Check if have user ${nick}`);
if (nick === this._ircConnector.nick) {
return true;
}
return !!this._ircUsers.find(x => x.nick === nick);
}
/**
* Return the final IRCChannel from an UserBridge
* @param {object} userData Telegram user information
* @return {IRCChannel}
*/
_getIrcUserChan(userData) {
let user = this._getIrcUser(userData);
let chan = user.getChannel(this._ircChannel.name);
return chan;
}
/**
* Remove an user from IRC users list
* @param {UserBridge} user UserBridge insntance
* @return {UserBridge}
*/
_removeIrcUser(user) {
this._ircUsers = this._ircUsers.filter(x => x.identifier !== user.identifier);
return user;
}
/**
* Handle incomming IRC message
* @param {string} user IRC user
* @param {string} message Message
*/
_handleIRCMessage(user, message) {
if (this._haveIrcUser(user)) {
return;
}
debug("irc in message", user);
if (this._options.oneConnectionByUser) {
message = this._translateIrcNicks(message);
}
let msg = `*<${(0, _util.escapeMarkdown)(user)}>* ${(0, _util.escapeMarkdown)(message)}`;
this._telegramChannel.sendMessage(msg, {
"parse_mode": 'markdown'
});
}
/**
* Handle incomming IRC action
* @param {string} user IRC user
* @param {string} message Message
*/
_handleIRCAction(user, message) {
if (this._haveIrcUser(user)) {
return;
}
debug("irc in action", user);
if (this._options.oneConnectionByUser) {
message = this._translateIrcNicks(message);
}
let msg = `_*${(0, _util.escapeMarkdown)(user)}* ${(0, _util.escapeMarkdown)(message)}_`;
this._telegramChannel.sendMessage(msg, {
"parse_mode": 'markdown'
});
}
/**
* Handle incomming IRC join message
* @param {string} user IRC user
*/
_handleIRCJoin(user) {
if (this._haveIrcUser(user)) {
return;
}
debug("irc join", user);
if (this._options.showJoinLeft) {
let msg = `***${(0, _util.escapeMarkdown)(user)}*** _has joined_`;
this._telegramChannel.sendMessage(msg, {
"parse_mode": 'markdown'
});
}
}
/**
* Handle IRC part message
* @param {string} user Irc user
*/
_handleIRCLeft(user) {
if (this._haveIrcUser(user)) {
return;
}
debug("irc left", user);
if (this._options.showJoinLeft) {
let msg = `***${(0, _util.escapeMarkdown)(user)}*** _left the channel_`;
this._telegramChannel.sendMessage(msg, {
"parse_mode": 'markdown'
});
}
}
/**
* Handle IRC topic message
* @param {string} user Irc user
*/
_handleIRCTopic(channel, topic, nick) {
if (this._haveIrcUser(nick)) {
return;
}
debug("irc topic", topic, nick);
let msg = `***${(0, _util.escapeMarkdown)(nick)}*** _changed the topic to:_\n`;
msg += '```' + topic.replace(/`/g, '\'') + '```';
this._telegramChannel.sendMessage(msg, {
"parse_mode": "markdown"
});
}
/**
* Handle Telegram incomming message
* @param {object} user Telegram user data
* @param {string} message Text message
*/
_handleTelegramMessage(user, message) {
debug("telegram in message", user);
let scapeChar = this._options.ircScapeCharacter;
if (this._options.oneConnectionByUser) {
let chan = this._getIrcUserChan(user);
chan.sendMessage(message);
} else if (scapeChar && message.startsWith(scapeChar)) {
this._ircChannel.sendMessage(message);
} else {
let msg;
if (this._options.useIrcColors) {
let color = this._getIrcColor(user.username);
let nick = _ircColors.default[color].bold(`[Telegram/@${user.username}]`);
let txt = _ircColors.default.stripColorsAndStyle(message);
msg = `${nick} ${txt}`;
} else {
msg = `[Telegram/@${user.username}] ${message}`;
}
this._ircChannel.sendMessage(msg);
}
}
/**
* Handle Telegram join message
* @param {object} user Telegram user data
*/
_handleTelegramJoin(user) {
debug("telegram in join", user);
if (this._options.oneConnectionByUser) {
this._getIrcUserChan(user);
} else {
let msg = `[Telegram] ${user.username} join`;
this._ircChannel.sendMessage(msg);
}
}
/**
* Handle telegram user left message
* @param {object} user Telegram user data
*/
_handleTelegramLeft(user) {
debug("telegram left", user);
if (this._options.oneConnectionByUser) {
if (this._haveIrcUser(user)) {
let chan = this._getIrcUserChan(user);
chan.destroy();
}
} else {
let msg = `[Telegram] ${user.username} left`;
this._ircChannel.sendMessage(msg);
}
}
/**
* Bind all events
* @private
*/
bind() {
// IRC
this._ircConnector.waitForRegistered().then(() => {
this._ircSuccessRegistered = true;
this.emit("irc:registered");
});
this._ircChannel.on("message", this._handlers.ircMessage);
this._ircChannel.on("action", this._handlers.ircAction);
this._ircChannel.on("join", this._handlers.ircJoin);
this._ircChannel.on("left", this._handlers.ircLeft);
this._ircChannel.on("topic", this._handlers.ircTopic); // Telegram
this._telegramConnector.me.then(me => {
this._telegramSuccessMe = true;
this.emit("telegram:me", me);
});
this._telegramChannel.on("message", this._handlers.telegramMessage);
this._telegramChannel.on("join", this._handlers.telegramJoin);
this._telegramChannel.on("left", this._handlers.telegramLeft);
}
/**
* @property {String} name
*/
get name() {
return this._name;
}
}, (_applyDecoratedDescriptor(_class2.prototype, "_constructor", [_dec4], Object.getOwnPropertyDescriptor(_class2.prototype, "_constructor"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "_getIrcColor", [_dec5], Object.getOwnPropertyDescriptor(_class2.prototype, "_getIrcColor"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "_getIrcUser", [_dec6], Object.getOwnPropertyDescriptor(_class2.prototype, "_getIrcUser"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "_getIrcUserChan", [_dec7], Object.getOwnPropertyDescriptor(_class2.prototype, "_getIrcUserChan"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "_handleIRCMessage", [_dec8], Object.getOwnPropertyDescriptor(_class2.prototype, "_handleIRCMessage"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "_handleTelegramMessage", [_dec9], Object.getOwnPropertyDescriptor(_class2.prototype, "_handleTelegramMessage"), _class2.prototype)), _class2));
exports.default = Bridge;