@thaunknown/web-irc
Version:
A TypeScript port of irc-framework's WebIRC client, without the bloat of unnceessary packages.
173 lines • 5.9 kB
JavaScript
import EventEmitter from 'events';
import ircNumerics from './numerics';
import IrcCommand from './command';
import { reduce, find, uniq } from '../util';
import registration from './handlers/registration';
import channel from './handlers/channel';
import user from './handlers/user';
import messaging from './handlers/messaging';
import misc from './handlers/misc';
import generics from './handlers/generics';
export default class IrcCommandHandler extends EventEmitter {
constructor(client) {
super();
// Adds an 'all' event to .emit()
this.addAllEventName();
this.client = client;
this.connection = client.connection;
this.network = client.network;
this.handlers = [];
this.request_extra_caps = [];
this.resetCache();
this.loadHandlers();
}
loadHandlers() {
registration(this);
channel(this);
user(this);
messaging(this);
misc(this);
generics(this);
}
dispatch(message) {
const irc_command = new IrcCommand(message.command.toUpperCase(), message);
// Batched commands will be collected and executed as a transaction
const batch_id = irc_command.getTag('batch');
if (batch_id) {
const cache_key = 'batch.' + batch_id;
if (this.hasCache(cache_key)) {
const cache = this.cache(cache_key);
cache.commands.push(irc_command);
}
else {
// If we don't have this batch ID in cache, it either means that the
// server hasn't sent the starting batch command or that the server
// has already sent the end batch command.
}
}
else {
this.executeCommand(irc_command);
}
}
executeCommand(irc_command) {
let command_name = irc_command.command;
// Check if we have a numeric->command name- mapping for this command
if (ircNumerics[irc_command.command.toUpperCase()]) {
command_name = ircNumerics[irc_command.command.toUpperCase()];
}
if (this.handlers[command_name]) {
this.handlers[command_name](irc_command, this);
}
else {
this.emitUnknownCommand(irc_command);
}
}
requestExtraCaps(cap) {
this.request_extra_caps = uniq(this.request_extra_caps.concat(cap));
}
addHandler(command, handler) {
if (typeof handler !== 'function') {
return false;
}
this.handlers[command] = handler;
}
emitUnknownCommand(command) {
this.emit('unknown command', command);
}
// Adds an 'all' event to .emit()
addAllEventName() {
const original_emit = this.emit;
this.emit = function () {
const args = Array.prototype.slice.call(arguments, 0);
original_emit.apply(this, ['all'].concat(args));
original_emit.apply(this, args);
};
}
/**
* Convert a mode string such as '+k pass', or '-i' to a readable
* format.
* [ { mode: '+k', param: 'pass' } ]
* [ { mode: '-i', param: null } ]
*/
parseModeList(mode_string, mode_params) {
const chanmodes = this.network.options.CHANMODES || [];
let prefixes = this.network.options.PREFIX || [];
let always_param = (chanmodes[0] || '').concat((chanmodes[1] || ''));
const modes = [];
let i;
let j;
let add;
if (!mode_string) {
return modes;
}
prefixes = reduce(prefixes, function (list, prefix) {
list.push(prefix.mode);
return list;
}, []);
always_param = always_param.split('').concat(prefixes);
const hasParam = function (mode, isAdd) {
const matchMode = function (m) {
return m === mode;
};
if (find(always_param, matchMode)) {
return true;
}
if (isAdd && find((chanmodes[2] || '').split(''), matchMode)) {
return true;
}
return false;
};
j = 0;
for (i = 0; i < mode_string.length; i++) {
switch (mode_string[i]) {
case '+':
add = true;
break;
case '-':
add = false;
break;
default:
if (hasParam(mode_string[i], add)) {
modes.push({ mode: (add ? '+' : '-') + mode_string[i], param: mode_params[j] });
j++;
}
else {
modes.push({ mode: (add ? '+' : '-') + mode_string[i], param: null });
}
}
}
return modes;
}
/**
* Cache object for commands buffering data before emitting them
* eg.
* var cache = this.cache('userlist');
* cache.nicks = [];
* cache.destroy();
*/
cache(id) {
let cache = this._caches[id];
if (!cache) {
const destroyCacheFn = (cacheToDestroy, idInCache) => {
return function () {
delete cacheToDestroy[idInCache];
};
};
// We don't want the destoryCache to be iterable
cache = Object.defineProperty({}, 'destroy', {
enumerable: false,
configurable: false,
value: destroyCacheFn(this._caches, id)
});
this._caches[id] = cache;
}
return cache;
}
hasCache(id) {
return this._caches && Object.prototype.hasOwnProperty.call(this._caches, id);
}
resetCache() {
this._caches = Object.create(null);
}
}
//# sourceMappingURL=handler.js.map