tons of useful additions
This commit is contained in:
parent
7f28f41ca9
commit
146c7ba900
@ -1,7 +1,7 @@
|
|||||||
import { format } from 'util';
|
|
||||||
import { IRCSocketConnector } from './connector/net.connector';
|
import { IRCSocketConnector } from './connector/net.connector';
|
||||||
import { IRCConnectionWrapper } from './irc';
|
import { IRCConnectionWrapper } from './irc';
|
||||||
import { IIRCOptions } from './types/irc.interfaces';
|
import { IIRCOptions } from './types/irc.interfaces';
|
||||||
|
import { formatstr } from './utility/formatstr';
|
||||||
|
|
||||||
export class IRCBot extends IRCConnectionWrapper {
|
export class IRCBot extends IRCConnectionWrapper {
|
||||||
constructor(options: IIRCOptions) {
|
constructor(options: IIRCOptions) {
|
||||||
@ -9,11 +9,11 @@ export class IRCBot extends IRCConnectionWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
send(to: string, message: string, ...args: any[]) {
|
send(to: string, message: string, ...args: any[]) {
|
||||||
this.write('PRIVMSG %s :%s', to, format(message, ...args));
|
this.write('PRIVMSG %s :%s', to, formatstr(message, ...args));
|
||||||
}
|
}
|
||||||
|
|
||||||
notice(to: string, message: string, ...args: any[]) {
|
notice(to: string, message: string, ...args: any[]) {
|
||||||
this.write('NOTICE %s :%s', to, format(message, ...args));
|
this.write('NOTICE %s :%s', to, formatstr(message, ...args));
|
||||||
}
|
}
|
||||||
|
|
||||||
nick(newNick: string) {
|
nick(newNick: string) {
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import net, { Socket } from 'net';
|
import net, { Socket } from 'net';
|
||||||
import tls, { TLSSocket } from 'tls';
|
import tls, { TLSSocket } from 'tls';
|
||||||
import { formatWithOptions } from 'util';
|
import { ConnectorEvents } from '../types/events';
|
||||||
import { IRCConnector } from '../types/impl.interface';
|
import { IRCConnector } from '../types/impl.interface';
|
||||||
import { SimpleEventEmitter } from '../utility/simple-event-emitter';
|
import { formatstr } from '../utility/formatstr';
|
||||||
|
import { TypedEventEmitter } from '../utility/typed-event-emitter';
|
||||||
|
|
||||||
export class IRCSocketConnector
|
export class IRCSocketConnector
|
||||||
extends SimpleEventEmitter
|
extends TypedEventEmitter<ConnectorEvents>
|
||||||
implements IRCConnector
|
implements IRCConnector
|
||||||
{
|
{
|
||||||
connected = false;
|
connected = false;
|
||||||
@ -15,12 +16,13 @@ export class IRCSocketConnector
|
|||||||
public secure: boolean,
|
public secure: boolean,
|
||||||
public host: string,
|
public host: string,
|
||||||
public port?: number,
|
public port?: number,
|
||||||
|
public connOpts?: Record<string, unknown>,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(): Promise<void> {
|
connect(): Promise<void> {
|
||||||
const opts = { host: this.host, port: this.port || 6667 };
|
const opts = { host: this.host, port: this.port || 6667, ...this.connOpts };
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const onConnect = () => {
|
const onConnect = () => {
|
||||||
@ -49,9 +51,7 @@ export class IRCSocketConnector
|
|||||||
}
|
}
|
||||||
|
|
||||||
write(format: string, ...args: any[]): void {
|
write(format: string, ...args: any[]): void {
|
||||||
this.socket?.write(
|
this.socket?.write(formatstr(format, ...args) + '\r\n');
|
||||||
formatWithOptions({ colors: false }, format, ...args) + '\r\n',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handle() {
|
private handle() {
|
||||||
@ -64,7 +64,7 @@ export class IRCSocketConnector
|
|||||||
buffer = data.pop() || '';
|
buffer = data.pop() || '';
|
||||||
|
|
||||||
data.forEach((line: string) => {
|
data.forEach((line: string) => {
|
||||||
if (line.indexOf('PING') === 0) {
|
if (line.indexOf('PING') === 0 && !this.connOpts?.skipPings) {
|
||||||
this.socket?.write('PONG' + line.substring(4) + '\r\n');
|
this.socket?.write('PONG' + line.substring(4) + '\r\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { formatWithOptions } from 'util';
|
import { ConnectorEvents } from '../types/events';
|
||||||
import { IRCConnector } from '../types/impl.interface';
|
import { IRCConnector } from '../types/impl.interface';
|
||||||
import { SimpleEventEmitter } from '../utility/simple-event-emitter';
|
import { formatstr } from '../utility/formatstr';
|
||||||
|
import { TypedEventEmitter } from '../utility/typed-event-emitter';
|
||||||
|
|
||||||
export class IRCWebSocketConnector
|
export class IRCWebSocketConnector
|
||||||
extends SimpleEventEmitter
|
extends TypedEventEmitter<ConnectorEvents>
|
||||||
implements IRCConnector
|
implements IRCConnector
|
||||||
{
|
{
|
||||||
connected = false;
|
connected = false;
|
||||||
@ -13,14 +14,17 @@ export class IRCWebSocketConnector
|
|||||||
public secure: boolean,
|
public secure: boolean,
|
||||||
public host: string,
|
public host: string,
|
||||||
public port?: number,
|
public port?: number,
|
||||||
|
public connOpts?: Record<string, unknown>,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(): Promise<void> {
|
connect(): Promise<void> {
|
||||||
const url = `ws${this.secure ? 's' : ''}://${this.host}:${
|
let url = `ws${this.secure ? 's' : ''}://${this.host}:${this.port || 6667}`;
|
||||||
this.port || 6667
|
|
||||||
}`;
|
if (this.connOpts?.path) {
|
||||||
|
url += ('/' + this.connOpts.path) as string;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const onConnect = () => {
|
const onConnect = () => {
|
||||||
@ -46,9 +50,7 @@ export class IRCWebSocketConnector
|
|||||||
}
|
}
|
||||||
|
|
||||||
write(format: string, ...args: any[]): void {
|
write(format: string, ...args: any[]): void {
|
||||||
this.socket?.send(
|
this.socket?.send(formatstr(format, ...args) + '\r\n');
|
||||||
formatWithOptions({ colors: false }, format, ...args) + '\r\n',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handle() {
|
private handle() {
|
||||||
@ -60,7 +62,7 @@ export class IRCWebSocketConnector
|
|||||||
buffer = data.pop() || '';
|
buffer = data.pop() || '';
|
||||||
|
|
||||||
data.forEach((line: string) => {
|
data.forEach((line: string) => {
|
||||||
if (line.indexOf('PING') === 0) {
|
if (line.indexOf('PING') === 0 && !this.connOpts?.skipPings) {
|
||||||
this.socket?.send('PONG' + line.substring(4) + '\r\n');
|
this.socket?.send('PONG' + line.substring(4) + '\r\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -73,7 +75,7 @@ export class IRCWebSocketConnector
|
|||||||
this.socket?.addEventListener('close', (err) => {
|
this.socket?.addEventListener('close', (err) => {
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
this.socket = undefined;
|
this.socket = undefined;
|
||||||
this.emit('close', err);
|
this.emit('close', err.reason);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,10 @@ bot.on('server-supports', (supported) => {
|
|||||||
console.log(supported);
|
console.log(supported);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bot.on('supported-modes', (supported) => {
|
||||||
|
console.log(supported);
|
||||||
|
});
|
||||||
|
|
||||||
// bot.on('line', console.log);
|
// bot.on('line', console.log);
|
||||||
|
|
||||||
bot.on('message', ({ message, to, nickname }) => {
|
bot.on('message', ({ message, to, nickname }) => {
|
||||||
@ -38,14 +42,32 @@ bot.on('message', ({ message, to, nickname }) => {
|
|||||||
nickname,
|
nickname,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.startsWith('!whois')) {
|
if (message.startsWith('!whois')) {
|
||||||
bot.whois(nickname).then(console.log);
|
bot.whois(nickname).then(console.log);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.startsWith('!who')) {
|
||||||
|
bot.who(to).then(console.log);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.startsWith('!names')) {
|
||||||
|
bot.names(to).then(console.log);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.startsWith('!list')) {
|
||||||
|
bot.list().then(console.log);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.startsWith('!ping')) {
|
if (message.startsWith('!ping')) {
|
||||||
bot.getPing().then((res) => bot.send(to, `Pong: ${res / 1000}s`));
|
bot.getPing().then((res) => bot.send(to, `Pong: ${res / 1000}s`));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
|
export * from './types/events';
|
||||||
export * from './types/irc.interfaces';
|
export * from './types/irc.interfaces';
|
||||||
export * from './types/impl.interface';
|
export * from './types/impl.interface';
|
||||||
|
|
||||||
export * from './utility/collector';
|
export * from './utility/collector';
|
||||||
|
export * from './utility/estimate-prefix';
|
||||||
|
export * from './utility/formatstr';
|
||||||
|
export * from './utility/mode-from-prefix';
|
||||||
export * from './utility/nickserv-validator';
|
export * from './utility/nickserv-validator';
|
||||||
export * from './utility/parser';
|
export * from './utility/parser';
|
||||||
export * from './utility/simple-event-emitter';
|
export * from './utility/typed-event-emitter';
|
||||||
export * from './utility/truncate';
|
export * from './utility/truncate';
|
||||||
export * from './utility/user-mapper';
|
export * from './utility/user-mapper';
|
||||||
export * from './utility/whois-parser';
|
export * from './utility/whois-parser';
|
||||||
|
|
||||||
|
export * from './spec/command-replies';
|
||||||
|
export * from './spec/error-replies';
|
||||||
|
|
||||||
export * from './irc';
|
export * from './irc';
|
||||||
|
143
src/irc.ts
143
src/irc.ts
@ -1,12 +1,19 @@
|
|||||||
|
import { IRCCommunicatorEvents } from './types/events';
|
||||||
import {
|
import {
|
||||||
IRCCommunicator,
|
IRCCommunicator,
|
||||||
IRCConnector,
|
IRCConnector,
|
||||||
IRCConnectorConstructor,
|
IRCConnectorConstructor,
|
||||||
} from './types/impl.interface';
|
} from './types/impl.interface';
|
||||||
import { IIRCLine, IIRCOptions, IQueue } from './types/irc.interfaces';
|
import { IIRCLine, IIRCOptions, IQueue } from './types/irc.interfaces';
|
||||||
import { Collector, WhoisCollector } from './utility/collector';
|
import {
|
||||||
|
Collector,
|
||||||
|
MultiLineCollector,
|
||||||
|
WhoCollector,
|
||||||
|
WhoisCollector,
|
||||||
|
} from './utility/collector';
|
||||||
import { parse } from './utility/parser';
|
import { parse } from './utility/parser';
|
||||||
import { SimpleEventEmitter } from './utility/simple-event-emitter';
|
import { TypedEventEmitter } from './utility/typed-event-emitter';
|
||||||
|
import { parseWho, WhoResponse } from './utility/who-parser';
|
||||||
import { parseWhois, WhoisResponse } from './utility/whois-parser';
|
import { parseWhois, WhoisResponse } from './utility/whois-parser';
|
||||||
|
|
||||||
const encodeBase64 = (input: string) => {
|
const encodeBase64 = (input: string) => {
|
||||||
@ -18,8 +25,9 @@ const encodeBase64 = (input: string) => {
|
|||||||
return input;
|
return input;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: typed emitter
|
||||||
export class IRCConnectionWrapper
|
export class IRCConnectionWrapper
|
||||||
extends SimpleEventEmitter
|
extends TypedEventEmitter<IRCCommunicatorEvents>
|
||||||
implements IRCCommunicator
|
implements IRCCommunicator
|
||||||
{
|
{
|
||||||
public channels: string[] = [];
|
public channels: string[] = [];
|
||||||
@ -91,9 +99,9 @@ export class IRCConnectionWrapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleServerLine(line: IIRCLine): void {
|
private pumpQueue(line: IIRCLine): boolean {
|
||||||
if (this.queue.length) {
|
|
||||||
let skipHandling = false;
|
let skipHandling = false;
|
||||||
|
if (this.queue.length) {
|
||||||
const afterModifyQueue: IQueue[] = [];
|
const afterModifyQueue: IQueue[] = [];
|
||||||
this.queue.forEach((entry) => {
|
this.queue.forEach((entry) => {
|
||||||
if (!entry.untracked) {
|
if (!entry.untracked) {
|
||||||
@ -103,6 +111,7 @@ export class IRCConnectionWrapper
|
|||||||
line.user.nickname.toLowerCase() ===
|
line.user.nickname.toLowerCase() ===
|
||||||
entry.from.toLowerCase()) ||
|
entry.from.toLowerCase()) ||
|
||||||
!entry.from) &&
|
!entry.from) &&
|
||||||
|
(!entry.match || (entry.match && entry.match(line))) &&
|
||||||
entry.do
|
entry.do
|
||||||
) {
|
) {
|
||||||
skipHandling = true;
|
skipHandling = true;
|
||||||
@ -125,10 +134,14 @@ export class IRCConnectionWrapper
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue = afterModifyQueue;
|
this.queue = afterModifyQueue;
|
||||||
|
|
||||||
if (skipHandling) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return skipHandling;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleServerLine(line: IIRCLine): void {
|
||||||
|
if (this.pumpQueue(line)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -169,8 +182,9 @@ export class IRCConnectionWrapper
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '353': {
|
case '353': {
|
||||||
|
// RPL_NAMEREPLY
|
||||||
const isUpQueued = this.queue.find(
|
const isUpQueued = this.queue.find(
|
||||||
(item) => item.await === '366' && item.from === line.arguments![2],
|
(item) => item.await === '366' && item.from === line.arguments[2],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isUpQueued) {
|
if (isUpQueued) {
|
||||||
@ -178,13 +192,13 @@ export class IRCConnectionWrapper
|
|||||||
} else {
|
} else {
|
||||||
this.queue.push({
|
this.queue.push({
|
||||||
untracked: true,
|
untracked: true,
|
||||||
from: line.arguments![2],
|
from: line.arguments[2],
|
||||||
await: '366',
|
await: '366',
|
||||||
additional: ['353'],
|
additional: ['353'],
|
||||||
buffer: [line],
|
buffer: [line],
|
||||||
do: (bline, data) => {
|
do: (bline, data) => {
|
||||||
this.emit('names', {
|
this.emit('names', {
|
||||||
channel: bline.arguments![1],
|
channel: bline.arguments[1],
|
||||||
list: [
|
list: [
|
||||||
...data.map((cline: IIRCLine) =>
|
...data.map((cline: IIRCLine) =>
|
||||||
(cline.trailing || '').split(' '),
|
(cline.trailing || '').split(' '),
|
||||||
@ -197,8 +211,9 @@ export class IRCConnectionWrapper
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '366': {
|
case '366': {
|
||||||
|
// RPL_ENDOFNAMES
|
||||||
const isUpQueued = this.queue.find(
|
const isUpQueued = this.queue.find(
|
||||||
(item) => item.await === '366' && item.from === line.arguments![1],
|
(item) => item.await === '366' && item.from === line.arguments[1],
|
||||||
);
|
);
|
||||||
if (isUpQueued) {
|
if (isUpQueued) {
|
||||||
isUpQueued.do(line, isUpQueued.buffer);
|
isUpQueued.do(line, isUpQueued.buffer);
|
||||||
@ -206,18 +221,18 @@ export class IRCConnectionWrapper
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '433':
|
case '433': // ERR_NICKNAMEINUSE
|
||||||
const newNick = this.options.nick + '_';
|
const newNick = this.options.nick + '_';
|
||||||
this.write('NICK %s', newNick);
|
this.write('NICK %s', newNick);
|
||||||
this.options.nick = newNick;
|
this.options.nick = newNick;
|
||||||
break;
|
break;
|
||||||
case '904':
|
case '904': // SASL fail
|
||||||
this.emit('error', {
|
this.emit('error', {
|
||||||
error: new Error(line.trailing),
|
error: new Error(line.trailing),
|
||||||
fatal: true,
|
fatal: true,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case '903':
|
case '903': // SASL success
|
||||||
this.write('CAP END');
|
this.write('CAP END');
|
||||||
break;
|
break;
|
||||||
case 'notice':
|
case 'notice':
|
||||||
@ -233,7 +248,7 @@ export class IRCConnectionWrapper
|
|||||||
raw: line,
|
raw: line,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case '001':
|
case '001': // RPL_WELCOME
|
||||||
if (!this.authenticated) {
|
if (!this.authenticated) {
|
||||||
this.serverData.name = line.user.hostname;
|
this.serverData.name = line.user.hostname;
|
||||||
this.authenticated = true;
|
this.authenticated = true;
|
||||||
@ -248,6 +263,7 @@ export class IRCConnectionWrapper
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '005': {
|
case '005': {
|
||||||
|
// RPL_ISUPPORT
|
||||||
this._lastLineWasSupports = true;
|
this._lastLineWasSupports = true;
|
||||||
if (!line.arguments) {
|
if (!line.arguments) {
|
||||||
break;
|
break;
|
||||||
@ -288,30 +304,32 @@ export class IRCConnectionWrapper
|
|||||||
this.options.hostname = line.arguments?.[1];
|
this.options.hostname = line.arguments?.[1];
|
||||||
break;
|
break;
|
||||||
// Set hostname from self-whois
|
// Set hostname from self-whois
|
||||||
case '311':
|
case '311': // RPL_WHOISUSER
|
||||||
if (line.arguments?.[1] !== this.options.nick) {
|
if (line.arguments?.[1] !== this.options.nick) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.options.hostname = line.arguments?.[3];
|
this.options.hostname = line.arguments?.[3];
|
||||||
break;
|
break;
|
||||||
case '321':
|
case '321': // RPL_LISTSTART
|
||||||
this.emit('channel-list-item', {
|
this.emit('channel-list-item', {
|
||||||
channel: 'Channel',
|
channel: 'Channel',
|
||||||
users: 'Users',
|
users: 'Users',
|
||||||
topic: 'Topic',
|
topic: 'Topic',
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case '322':
|
case '322': // RPL_LIST
|
||||||
this.emit('channel-list-item', {
|
this.emit('channel-list-item', {
|
||||||
channel: line.arguments![1],
|
channel: line.arguments[1],
|
||||||
users: line.arguments![2],
|
users: line.arguments[2],
|
||||||
topic: line.trailing,
|
topic: line.trailing,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'quit':
|
case 'quit':
|
||||||
if (line.user.nickname !== this.options.nick) {
|
if (line.user.nickname !== this.options.nick) {
|
||||||
this.emit('leave', {
|
this.emit('leave', {
|
||||||
|
type: 'quit',
|
||||||
nickname: line.user.nickname,
|
nickname: line.user.nickname,
|
||||||
|
reason: line.trailing,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -338,14 +356,14 @@ export class IRCConnectionWrapper
|
|||||||
let isChannelMode = false;
|
let isChannelMode = false;
|
||||||
let method = '+';
|
let method = '+';
|
||||||
let lts = line.trailing ? line.trailing.split(' ') : [];
|
let lts = line.trailing ? line.trailing.split(' ') : [];
|
||||||
if (line.arguments![0].indexOf('#') !== -1) {
|
if (line.arguments[0].indexOf('#') !== -1) {
|
||||||
isChannelMode = true;
|
isChannelMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let modes: string | string[] = line.arguments![1];
|
let modes: string | string[] = line.arguments[1];
|
||||||
|
|
||||||
if (!modes && line.trailing !== '') {
|
if (!modes && line.trailing !== '') {
|
||||||
modes = line.trailing!;
|
modes = line.trailing;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sender = line.user.nickname;
|
let sender = line.user.nickname;
|
||||||
@ -365,9 +383,9 @@ export class IRCConnectionWrapper
|
|||||||
this.emit('channel-mode', {
|
this.emit('channel-mode', {
|
||||||
type: method,
|
type: method,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
modeTarget: line.arguments![2]
|
modeTarget: line.arguments[2]
|
||||||
? line.arguments![2 + modei]
|
? line.arguments[2 + modei]
|
||||||
? line.arguments![2 + modei]
|
? line.arguments[2 + modei]
|
||||||
: lts[modei - 1]
|
: lts[modei - 1]
|
||||||
: lts[modei],
|
: lts[modei],
|
||||||
...line,
|
...line,
|
||||||
@ -384,7 +402,7 @@ export class IRCConnectionWrapper
|
|||||||
this.emit('user-mode', {
|
this.emit('user-mode', {
|
||||||
type: method,
|
type: method,
|
||||||
mode: pass.join(''),
|
mode: pass.join(''),
|
||||||
modeTarget: line.arguments![0],
|
modeTarget: line.arguments[0],
|
||||||
...line,
|
...line,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -399,8 +417,10 @@ export class IRCConnectionWrapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.emit('leave', {
|
this.emit('leave', {
|
||||||
|
type: line.command.toLowerCase(),
|
||||||
nickname: line.user.nickname,
|
nickname: line.user.nickname,
|
||||||
channel: line.arguments?.[0],
|
channel: line.arguments?.[0],
|
||||||
|
reason: line.trailing,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
@ -422,6 +442,7 @@ export class IRCConnectionWrapper
|
|||||||
this.options.ssl ?? false,
|
this.options.ssl ?? false,
|
||||||
this.options.host,
|
this.options.host,
|
||||||
this.options.port,
|
this.options.port,
|
||||||
|
this.options.connOpts,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.connection.on('close', (data) => {
|
this.connection.on('close', (data) => {
|
||||||
@ -490,6 +511,11 @@ export class IRCConnectionWrapper
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous WHOIS query on `nick`
|
||||||
|
* @param nick Nick to query
|
||||||
|
* @returns Parsed WHOIS response object
|
||||||
|
*/
|
||||||
public async whois(nick: string): Promise<WhoisResponse> {
|
public async whois(nick: string): Promise<WhoisResponse> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.useCollector(
|
this.useCollector(
|
||||||
@ -500,6 +526,67 @@ export class IRCConnectionWrapper
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous WHO query on `target`
|
||||||
|
* @param target Channel or nick to query
|
||||||
|
* @returns Parsed WHO response object array
|
||||||
|
*/
|
||||||
|
public async who(target: string): Promise<WhoResponse[]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.useCollector(
|
||||||
|
new WhoCollector((lines) => resolve(parseWho(lines))),
|
||||||
|
'WHO %s',
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of names in a channel asynchronously
|
||||||
|
* @param channel Channel to query
|
||||||
|
* @returns String list of nicks (with mode prefixes preserved)
|
||||||
|
*/
|
||||||
|
public async names(channel: string): Promise<string[]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.useCollector(
|
||||||
|
new MultiLineCollector('366', ['353'], (lines) => {
|
||||||
|
resolve(
|
||||||
|
[
|
||||||
|
...lines
|
||||||
|
.filter(({ command }) => command === '353')
|
||||||
|
.map((cline: IIRCLine) => (cline.trailing || '').split(' ')),
|
||||||
|
].flat(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
'NAMES %s',
|
||||||
|
channel,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of channels asynchronously
|
||||||
|
* @returns Channel list in `[<channel>, <# visible>, <topic>][]` format
|
||||||
|
*/
|
||||||
|
public async list(): Promise<string[][]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.useCollector(
|
||||||
|
new MultiLineCollector('323', ['322'], (lines) => {
|
||||||
|
resolve(
|
||||||
|
lines
|
||||||
|
.filter(({ command }) => command === '322')
|
||||||
|
.map((line) => [
|
||||||
|
line.arguments[1],
|
||||||
|
line.arguments[2],
|
||||||
|
line.trailing,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
'LIST',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public useCollector(collector: IQueue, line: string, ...args: any[]) {
|
public useCollector(collector: IQueue, line: string, ...args: any[]) {
|
||||||
this.queue.push(collector);
|
this.queue.push(collector);
|
||||||
this.write(line, ...args);
|
this.write(line, ...args);
|
||||||
|
110
src/spec/command-replies.ts
Normal file
110
src/spec/command-replies.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
export const RPL_COMMAND = [
|
||||||
|
['300', 'RPL_NONE', ''],
|
||||||
|
['302', 'RPL_USERHOST', ':[<reply>{<space><reply>}]'],
|
||||||
|
['303', 'RPL_ISON', ':[<nick> {<space><nick>}]'],
|
||||||
|
['301', 'RPL_AWAY', '<nick> :<away message>'],
|
||||||
|
['305', 'RPL_UNAWAY', ':You are no longer marked as being away'],
|
||||||
|
['306', 'RPL_NOWAWAY', ':You have been marked as being away'],
|
||||||
|
['307', 'RPL_WHOISREGNICK', '<nick> :is a registered nick'],
|
||||||
|
['311', 'RPL_WHOISUSER', '<nick> <user> <host> * :<real name>'],
|
||||||
|
['312', 'RPL_WHOISSERVER', '<nick> <server> :<server info>'],
|
||||||
|
['313', 'RPL_WHOISOPERATOR', '<nick> :is an IRC operator'],
|
||||||
|
[
|
||||||
|
'317',
|
||||||
|
'RPL_WHOISIDLE',
|
||||||
|
'<nick> <integer> [<integer>] :seconds idle[, signon at]',
|
||||||
|
],
|
||||||
|
['318', 'RPL_ENDOFWHOIS', '<nick> :End of /WHOIS list'],
|
||||||
|
['319', 'RPL_WHOISCHANNELS', '<nick> :{[@|+]<channel><space>}'],
|
||||||
|
['314', 'RPL_WHOWASUSER', '<nick> <user> <host> * :<real name>'],
|
||||||
|
['369', 'RPL_ENDOFWHOWAS', '<nick> :End of WHOWAS'],
|
||||||
|
['321', 'RPL_LISTSTART', 'Channel :Users Name'],
|
||||||
|
['322', 'RPL_LIST', '<channel> <# visible> :<topic>'],
|
||||||
|
['323', 'RPL_LISTEND', ':End of /LIST'],
|
||||||
|
['324', 'RPL_CHANNELMODEIS', '<channel> <mode> <mode params>'],
|
||||||
|
['324', 'RPL_WHOISACCOUNT', '<nick> <authname> :is logged in as'],
|
||||||
|
['331', 'RPL_NOTOPIC', '<channel> :No topic is set'],
|
||||||
|
['332', 'RPL_TOPIC', '<channel> :<topic>'],
|
||||||
|
['333', 'RPL_TOPICWHOTIME', '<nick> <user> <host> :<integer>'],
|
||||||
|
['335', 'RPL_WHOISBOT', ':is a bot on network'],
|
||||||
|
['341', 'RPL_INVITING', '<channel> <nick>'],
|
||||||
|
['342', 'RPL_SUMMONING', '<user> :Summoning user to IRC'],
|
||||||
|
['351', 'RPL_VERSION', '<version>.<debuglevel> <server> :<comments>'],
|
||||||
|
[
|
||||||
|
'352',
|
||||||
|
'RPL_WHOREPLY',
|
||||||
|
'<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name>',
|
||||||
|
],
|
||||||
|
['315', 'RPL_ENDOFWHO', '<name> :End of /WHO list'],
|
||||||
|
['353', 'RPL_NAMREPLY', '<channel> :[[@|+]<nick> [[@|+]<nick> [...]]]'],
|
||||||
|
['366', 'RPL_ENDOFNAMES', '<channel> :End of /NAMES list'],
|
||||||
|
['364', 'RPL_LINKS', '<mask> <server> :<hopcount> <server info>'],
|
||||||
|
['365', 'RPL_ENDOFLINKS', '<mask> :End of /LINKS list'],
|
||||||
|
['367', 'RPL_BANLIST', '<channel> <banid>'],
|
||||||
|
['368', 'RPL_ENDOFBANLIST', '<channel> :End of channel ban list'],
|
||||||
|
['371', 'RPL_INFO', ':<string>'],
|
||||||
|
['374', 'RPL_ENDOFINFO', ':End of /INFO list'],
|
||||||
|
['375', 'RPL_MOTDSTART', ':- <server> Message of the day - '],
|
||||||
|
['372', 'RPL_MOTD', ':- <text>'],
|
||||||
|
['376', 'RPL_ENDOFMOTD', ':End of /MOTD command'],
|
||||||
|
['379', 'RPL_WHOISMODES', '<nick> <modes> :is using modes'],
|
||||||
|
['381', 'RPL_YOUREOPER', ':You are now an IRC operator'],
|
||||||
|
['382', 'RPL_REHASHING', '<config file> :Rehashing'],
|
||||||
|
['391', 'RPL_TIME', "<server> :<string showing server's local time>"],
|
||||||
|
['392', 'RPL_USERSSTART', ':UserID Terminal Host'],
|
||||||
|
['393', 'RPL_USERS', ':%-8s %-9s %-8s'],
|
||||||
|
['394', 'RPL_ENDOFUSERS', ':End of users'],
|
||||||
|
['395', 'RPL_NOUSERS', ':Nobody logged in'],
|
||||||
|
[
|
||||||
|
'200',
|
||||||
|
'RPL_TRACELINK',
|
||||||
|
'Link <version & debug level> <destination> <next server>',
|
||||||
|
],
|
||||||
|
['201', 'RPL_TRACECONNECTING', 'Try. <class> <server>'],
|
||||||
|
['202', 'RPL_TRACEHANDSHAKE', 'H.S. <class> <server>'],
|
||||||
|
['203', 'RPL_TRACEUNKNOWN', '???? <class> [<client IP address in dot form>]'],
|
||||||
|
['204', 'RPL_TRACEOPERATOR', 'Oper <class> <nick>'],
|
||||||
|
['205', 'RPL_TRACEUSER', 'User <class> <nick>'],
|
||||||
|
[
|
||||||
|
'206',
|
||||||
|
'RPL_TRACESERVER',
|
||||||
|
'Serv <class> <int>S <int>C <server> <nick!user|*!*>@<host|server>',
|
||||||
|
],
|
||||||
|
['208', 'RPL_TRACENEWTYPE', '<newtype> 0 <client name>'],
|
||||||
|
['261', 'RPL_TRACELOG', 'File <logfile> <debug level>'],
|
||||||
|
[
|
||||||
|
'211',
|
||||||
|
'RPL_STATSLINKINFO',
|
||||||
|
'<linkname> <sendq> <sent messages> <sent bytes> <received messages> <received bytes> <time open>',
|
||||||
|
],
|
||||||
|
['212', 'RPL_STATSCOMMANDS', '<command> <count>'],
|
||||||
|
['213', 'RPL_STATSCLINE', 'C <host> * <name> <port> <class>'],
|
||||||
|
['214', 'RPL_STATSNLINE', 'N <host> * <name> <port> <class>'],
|
||||||
|
['215', 'RPL_STATSILINE', 'I <host> * <host> <port> <class>'],
|
||||||
|
['216', 'RPL_STATSKLINE', 'K <host> * <username> <port> <class>'],
|
||||||
|
[
|
||||||
|
'218',
|
||||||
|
'RPL_STATSYLINE',
|
||||||
|
'Y <class> <ping frequency> <connect frequency> <max sendq>',
|
||||||
|
],
|
||||||
|
['219', 'RPL_ENDOFSTATS', '<stats letter> :End of /STATS report'],
|
||||||
|
['241', 'RPL_STATSLLINE', 'L <hostmask> * <servername> <maxdepth>'],
|
||||||
|
['242', 'RPL_STATSUPTIME', ':Server Up %d days %d:%02d:%02d'],
|
||||||
|
['243', 'RPL_STATSOLINE', 'O <hostmask> * <name>'],
|
||||||
|
['244', 'RPL_STATSHLINE', 'H <hostmask> * <servername>'],
|
||||||
|
['221', 'RPL_UMODEIS', '<user mode string>'],
|
||||||
|
[
|
||||||
|
'251',
|
||||||
|
'RPL_LUSERCLIENT',
|
||||||
|
':There are <integer> users and <integer> invisible on <integer> servers',
|
||||||
|
],
|
||||||
|
['252', 'RPL_LUSEROP', '<integer> :operator(s) online'],
|
||||||
|
['253', 'RPL_LUSERUNKNOWN', '<integer> :unknown connection(s)'],
|
||||||
|
['254', 'RPL_LUSERCHANNELS', '<integer> :channels formed'],
|
||||||
|
['255', 'RPL_LUSERME', ':I have <integer> clients and <integer> servers'],
|
||||||
|
['256', 'RPL_ADMINME', '<server> :Administrative info'],
|
||||||
|
['257', 'RPL_ADMINLOC1', ':<admin info>'],
|
||||||
|
['258', 'RPL_ADMINLOC2', ':<admin info>'],
|
||||||
|
['259', 'RPL_ADMINEMAIL', ':<admin info>'],
|
||||||
|
['671', 'RPL_WHOISSECURE', '<nick> [<type>] :is using a secure connection'],
|
||||||
|
];
|
59
src/spec/error-replies.ts
Normal file
59
src/spec/error-replies.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
export const RPL_ERROR = [
|
||||||
|
['401', 'ERR_NOSUCHNICK', '<nickname> :No such nick/channel'],
|
||||||
|
['402', 'ERR_NOSUCHSERVER', '<server name> :No such server'],
|
||||||
|
['403', 'ERR_NOSUCHCHANNEL', '<channel name> :No such channel'],
|
||||||
|
['404', 'ERR_CANNOTSENDTOCHAN', '<channel name> :Cannot send to channel'],
|
||||||
|
[
|
||||||
|
'405',
|
||||||
|
'ERR_TOOMANYCHANNELS',
|
||||||
|
'<channel name> :You have joined too many channels',
|
||||||
|
],
|
||||||
|
['406', 'ERR_WASNOSUCHNICK', '<nickname> :There was no such nickname'],
|
||||||
|
[
|
||||||
|
'407',
|
||||||
|
'ERR_TOOMANYTARGETS',
|
||||||
|
'<target> :Duplicate recipients. No message delivered',
|
||||||
|
],
|
||||||
|
['409', 'ERR_NOORIGIN', ':No origin specified'],
|
||||||
|
['411', 'ERR_NORECIPIENT', ':No recipient given (<command>)'],
|
||||||
|
['412', 'ERR_NOTEXTTOSEND', ':No text to send'],
|
||||||
|
['413', 'ERR_NOTOPLEVEL', '<mask> :No toplevel domain specified'],
|
||||||
|
['414', 'ERR_WILDTOPLEVEL', '<mask> :Wildcard in toplevel domain'],
|
||||||
|
['421', 'ERR_UNKNOWNCOMMAND', '<command> :Unknown command'],
|
||||||
|
['422', 'ERR_NOMOTD', ':MOTD File is missing'],
|
||||||
|
['423', 'ERR_NOADMININFO', '<server> :No administrative info available'],
|
||||||
|
['424', 'ERR_FILEERROR', ':File error doing <file op> on <file>'],
|
||||||
|
['431', 'ERR_NONICKNAMEGIVEN', ':No nickname given'],
|
||||||
|
['432', 'ERR_ERRONEUSNICKNAME', '<nick> :Erroneus nickname'],
|
||||||
|
['433', 'ERR_NICKNAMEINUSE', '<nick> :Nickname is already in use'],
|
||||||
|
['436', 'ERR_NICKCOLLISION', '<nick> :Nickname collision KILL'],
|
||||||
|
[
|
||||||
|
'441',
|
||||||
|
'ERR_USERNOTINCHANNEL',
|
||||||
|
"<nick> <channel> :They aren't on that channel",
|
||||||
|
],
|
||||||
|
['442', 'ERR_NOTONCHANNEL', "<channel> :You're not on that channel"],
|
||||||
|
['443', 'ERR_USERONCHANNEL', '<user> <channel> :is already on channel'],
|
||||||
|
['444', 'ERR_NOLOGIN', '<user> :User not logged in'],
|
||||||
|
['445', 'ERR_SUMMONDISABLED', ':SUMMON has been disabled'],
|
||||||
|
['446', 'ERR_USERSDISABLED', ':USERS has been disabled'],
|
||||||
|
['451', 'ERR_NOTREGISTERED', ':You have not registered'],
|
||||||
|
['461', 'ERR_NEEDMOREPARAMS', '<command> :Not enough parameters'],
|
||||||
|
['462', 'ERR_ALREADYREGISTRED', ':You may not reregister'],
|
||||||
|
['463', 'ERR_NOPERMFORHOST', ":Your host isn't among the privileged"],
|
||||||
|
['464', 'ERR_PASSWDMISMATCH', ':Password incorrect'],
|
||||||
|
['465', 'ERR_YOUREBANNEDCREEP', ':You are banned from this server'],
|
||||||
|
['467', 'ERR_KEYSET', '<channel> :Channel key already set'],
|
||||||
|
['471', 'ERR_CHANNELISFULL', '<channel> :Cannot join channel (+l)'],
|
||||||
|
['472', 'ERR_UNKNOWNMODE', '<char> :is unknown mode char to me'],
|
||||||
|
['473', 'ERR_INVITEONLYCHAN', '<channel> :Cannot join channel (+i)'],
|
||||||
|
['474', 'ERR_BANNEDFROMCHAN', '<channel> :Cannot join channel (+b)'],
|
||||||
|
['475', 'ERR_BADCHANNELKEY', '<channel> :Cannot join channel (+k)'],
|
||||||
|
['481', 'ERR_NOPRIVILEGES', ":Permission Denied- You're not an IRC operator"],
|
||||||
|
['482', 'ERR_CHANOPRIVSNEEDED', "<channel> :You're not channel operator"],
|
||||||
|
['483', 'ERR_CANTKILLSERVER', ':You cant kill a server!'],
|
||||||
|
['489', 'ERR_SECUREONLYCHAN', '<channel> :Secure connection required'],
|
||||||
|
['491', 'ERR_NOOPERHOST', ':No O-lines for your host'],
|
||||||
|
['501', 'ERR_UMODEUNKNOWNFLAG', ':Unknown MODE flag'],
|
||||||
|
['502', 'ERR_USERSDONTMATCH', ':Cant change mode for other users'],
|
||||||
|
];
|
@ -0,0 +1,92 @@
|
|||||||
|
import { IIRCLine } from './irc.interfaces';
|
||||||
|
|
||||||
|
export type ConnectorEvents = {
|
||||||
|
error: (error: any) => void;
|
||||||
|
data: (data: string) => void;
|
||||||
|
close: (reason: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IRCCommunicatorEvents = {
|
||||||
|
/**
|
||||||
|
* Parsed line from the IRC server.
|
||||||
|
*/
|
||||||
|
line: (line: IIRCLine) => void;
|
||||||
|
/**
|
||||||
|
* Supported channel user modes from the server (e.g. `ohv: @%+`)
|
||||||
|
*/
|
||||||
|
'supported-modes': (modes: Record<string, unknown>) => void;
|
||||||
|
/**
|
||||||
|
* Everything this server supports. See IRC documentation for command `005` or `RPL_ISUPPORT` for more info.
|
||||||
|
*/
|
||||||
|
'server-supports': (supports: Record<string, unknown>) => void;
|
||||||
|
/**
|
||||||
|
* Nicks in a channel.
|
||||||
|
*/
|
||||||
|
names: (data: { channel: string; list: string[] }) => void;
|
||||||
|
/**
|
||||||
|
* Error event from the server.
|
||||||
|
*/
|
||||||
|
error: (data: { error: Error; fatal: boolean }) => void;
|
||||||
|
/**
|
||||||
|
* Message event from the server.
|
||||||
|
*/
|
||||||
|
message: (data: {
|
||||||
|
type: string;
|
||||||
|
message: string;
|
||||||
|
to: string;
|
||||||
|
nickname: string;
|
||||||
|
raw: IIRCLine;
|
||||||
|
}) => void;
|
||||||
|
/**
|
||||||
|
* Emitted when the login to the server was successful.
|
||||||
|
*
|
||||||
|
* **Note:** "login" in this case doesn't account for NickServ or SASL success.
|
||||||
|
*/
|
||||||
|
authenticated: (authed: boolean) => void;
|
||||||
|
'channel-list-item': (data: {
|
||||||
|
channel: string;
|
||||||
|
users: string;
|
||||||
|
topic: string;
|
||||||
|
}) => void;
|
||||||
|
/**
|
||||||
|
* User leaving event. Type can be `quit`, `kick` or `part`.
|
||||||
|
*/
|
||||||
|
leave: (data: {
|
||||||
|
type: string;
|
||||||
|
nickname: string;
|
||||||
|
channel?: string;
|
||||||
|
reason?: string;
|
||||||
|
}) => void;
|
||||||
|
/**
|
||||||
|
* User changed their nickname.
|
||||||
|
*/
|
||||||
|
nick: (data: { oldNick: string; newNick: string }) => void;
|
||||||
|
/**
|
||||||
|
* User joined a channel you are in.
|
||||||
|
*/
|
||||||
|
join: (data: { nickname: string; channel: string }) => void;
|
||||||
|
/**
|
||||||
|
* A mode was set on a user in a channel you're in.
|
||||||
|
*/
|
||||||
|
'channel-mode': (
|
||||||
|
data: {
|
||||||
|
type: string;
|
||||||
|
mode: string;
|
||||||
|
modeTarget: string;
|
||||||
|
} & IIRCLine,
|
||||||
|
) => void;
|
||||||
|
/**
|
||||||
|
* A mode was set on a user, usually you.
|
||||||
|
*/
|
||||||
|
'user-mode': (
|
||||||
|
data: {
|
||||||
|
type: string;
|
||||||
|
mode: string;
|
||||||
|
modeTarget: string;
|
||||||
|
} & IIRCLine,
|
||||||
|
) => void;
|
||||||
|
/**
|
||||||
|
* Disconnected from the server.
|
||||||
|
*/
|
||||||
|
disconnect: (data: { type: string; raw: any; message: string }) => void;
|
||||||
|
};
|
@ -11,5 +11,10 @@ export interface IRCConnector extends IRCCommunicator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IRCConnectorConstructor {
|
export interface IRCConnectorConstructor {
|
||||||
new (secure: boolean, host: string, port?: number): IRCConnector;
|
new (
|
||||||
|
secure: boolean,
|
||||||
|
host: string,
|
||||||
|
port?: number,
|
||||||
|
opts?: Record<string, unknown>,
|
||||||
|
): IRCConnector;
|
||||||
}
|
}
|
||||||
|
@ -5,46 +5,145 @@ export interface IIRCUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IIRCLine {
|
export interface IIRCLine {
|
||||||
|
/**
|
||||||
|
* Sender information
|
||||||
|
*/
|
||||||
user: IIRCUser;
|
user: IIRCUser;
|
||||||
|
/**
|
||||||
|
* Raw IRC command. You may need to map this to `RPL_ERROR` or `RPL_COMMAND` depending on your needs.
|
||||||
|
*/
|
||||||
command: string;
|
command: string;
|
||||||
arguments?: string[];
|
/**
|
||||||
trailing?: string;
|
* Arguments of the IRC command. They appear after the command and before the trailing (` :`)
|
||||||
|
*/
|
||||||
|
arguments: string[];
|
||||||
|
/**
|
||||||
|
* Trailing of the IRC command. This is the text after ` :` (excluded)
|
||||||
|
*/
|
||||||
|
trailing: string;
|
||||||
|
/**
|
||||||
|
* Raw line from the server.
|
||||||
|
*/
|
||||||
raw: string;
|
raw: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserLine {
|
export interface IUserLine {
|
||||||
command: string;
|
command: string;
|
||||||
arguments?: string[];
|
arguments: string[];
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQueue<T = any> {
|
export interface IQueue<T = any> {
|
||||||
untracked?: boolean;
|
untracked?: boolean;
|
||||||
|
/**
|
||||||
|
* Wait for this command from the server.
|
||||||
|
*/
|
||||||
await: string;
|
await: string;
|
||||||
|
/**
|
||||||
|
* Append lines with these commands to buffer.
|
||||||
|
*/
|
||||||
additional?: string[];
|
additional?: string[];
|
||||||
|
/**
|
||||||
|
* From nickname, used for NOTICE and PRIVMSG collectors.
|
||||||
|
*/
|
||||||
from?: string;
|
from?: string;
|
||||||
|
/**
|
||||||
|
* Buffer list of additional data.
|
||||||
|
*/
|
||||||
buffer?: T;
|
buffer?: T;
|
||||||
|
/**
|
||||||
|
* Resolve the collector.
|
||||||
|
* @param line The line resolving to `await`
|
||||||
|
* @param data Additional data in `buffer`, usually only present when using `additional`
|
||||||
|
*/
|
||||||
do(line: IIRCLine, data?: T): void;
|
do(line: IIRCLine, data?: T): void;
|
||||||
|
/**
|
||||||
|
* Match the lines you're looking for.
|
||||||
|
* @param line Server line
|
||||||
|
*/
|
||||||
|
match?(line: IIRCLine): boolean;
|
||||||
|
/**
|
||||||
|
* Line matching commands in `additional` will call this function.
|
||||||
|
* Use it to populate the `buffer`.
|
||||||
|
* @param line Server line
|
||||||
|
*/
|
||||||
digest?(line: IIRCLine): void;
|
digest?(line: IIRCLine): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INickServOptions {
|
export interface INickServOptions {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
/**
|
||||||
|
* NickServ login status command.
|
||||||
|
*/
|
||||||
command: string;
|
command: string;
|
||||||
|
/**
|
||||||
|
* NickServ bot name, defaults to NickServ.
|
||||||
|
*/
|
||||||
nickservBot?: string;
|
nickservBot?: string;
|
||||||
|
/**
|
||||||
|
* Response command to wait from NickServ, defaults to NOTICE.
|
||||||
|
*/
|
||||||
responseCommand?: string;
|
responseCommand?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIRCOptions {
|
export interface IIRCOptions {
|
||||||
|
/**
|
||||||
|
* IRC nickname
|
||||||
|
*/
|
||||||
nick: string;
|
nick: string;
|
||||||
|
/**
|
||||||
|
* IRC server hostname
|
||||||
|
*/
|
||||||
host: string;
|
host: string;
|
||||||
|
/**
|
||||||
|
* IRC username
|
||||||
|
*/
|
||||||
username?: string;
|
username?: string;
|
||||||
|
/**
|
||||||
|
* Your user's hostname, this will be set automatically on connect.
|
||||||
|
* Setting it manually has no effect.
|
||||||
|
*/
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
|
/**
|
||||||
|
* IRC realname
|
||||||
|
*/
|
||||||
realname?: string;
|
realname?: string;
|
||||||
|
/**
|
||||||
|
* IRC server port
|
||||||
|
*/
|
||||||
port?: number;
|
port?: number;
|
||||||
password?: string | null;
|
/**
|
||||||
|
* IRC server password.
|
||||||
|
* Sometimes also used as nickserv password.
|
||||||
|
*/
|
||||||
|
password?: string;
|
||||||
|
/**
|
||||||
|
* Enable SASL authentication.
|
||||||
|
*/
|
||||||
sasl?: boolean;
|
sasl?: boolean;
|
||||||
|
/**
|
||||||
|
* Enable SSL
|
||||||
|
*/
|
||||||
ssl?: boolean;
|
ssl?: boolean;
|
||||||
|
/**
|
||||||
|
* Set +B mode on self (servers may not all support this!)
|
||||||
|
*/
|
||||||
|
bot?: boolean;
|
||||||
|
/**
|
||||||
|
* List of channels to join on connect
|
||||||
|
*/
|
||||||
channels?: string[];
|
channels?: string[];
|
||||||
|
/**
|
||||||
|
* Additional NickServ options
|
||||||
|
*/
|
||||||
nickserv?: INickServOptions;
|
nickserv?: INickServOptions;
|
||||||
|
/**
|
||||||
|
* Additional options for connections, usually passed right along to socket
|
||||||
|
* without additional alterations.
|
||||||
|
*
|
||||||
|
* Special cases for included connections:
|
||||||
|
* - `path` - `IRCWebSocketConnector` will append this to the WebSocket URL.
|
||||||
|
* - `skipPings` - Included connectors will not respond to PINGs if set.
|
||||||
|
*/
|
||||||
|
connOpts?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import { IIRCLine, IQueue } from '../types/irc.interfaces';
|
import { IIRCLine, IQueue } from '../types/irc.interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect a single line from the server.
|
||||||
|
*/
|
||||||
export class Collector implements IQueue<IIRCLine> {
|
export class Collector implements IQueue<IIRCLine> {
|
||||||
constructor(
|
constructor(
|
||||||
public await: string,
|
public await: string,
|
||||||
private resolve: (lines: IIRCLine) => void,
|
private resolve: (lines: IIRCLine) => void,
|
||||||
public from?: string,
|
public from?: string,
|
||||||
|
public match?: (line: IIRCLine) => boolean,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
do(line: IIRCLine): void {
|
do(line: IIRCLine): void {
|
||||||
@ -12,6 +16,9 @@ export class Collector implements IQueue<IIRCLine> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect lines from the server.
|
||||||
|
*/
|
||||||
export class MultiLineCollector implements IQueue<IIRCLine[]> {
|
export class MultiLineCollector implements IQueue<IIRCLine[]> {
|
||||||
public buffer: IIRCLine[] = [];
|
public buffer: IIRCLine[] = [];
|
||||||
|
|
||||||
@ -19,6 +26,7 @@ export class MultiLineCollector implements IQueue<IIRCLine[]> {
|
|||||||
public await: string,
|
public await: string,
|
||||||
public additional: string[],
|
public additional: string[],
|
||||||
private resolve: (lines: IIRCLine[]) => void,
|
private resolve: (lines: IIRCLine[]) => void,
|
||||||
|
public match?: (line: IIRCLine) => boolean,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
do(line: IIRCLine, data: IIRCLine[]): void {
|
do(line: IIRCLine, data: IIRCLine[]): void {
|
||||||
@ -58,7 +66,10 @@ export class MultiLineCollector implements IQueue<IIRCLine[]> {
|
|||||||
* `318` - End of WHOIS
|
* `318` - End of WHOIS
|
||||||
*/
|
*/
|
||||||
export class WhoisCollector extends MultiLineCollector {
|
export class WhoisCollector extends MultiLineCollector {
|
||||||
constructor(resolve: (lines: IIRCLine[]) => void) {
|
constructor(
|
||||||
|
resolve: (lines: IIRCLine[]) => void,
|
||||||
|
match?: (line: IIRCLine) => boolean,
|
||||||
|
) {
|
||||||
super(
|
super(
|
||||||
'318', // End of WHOIS
|
'318', // End of WHOIS
|
||||||
[
|
[
|
||||||
@ -75,6 +86,7 @@ export class WhoisCollector extends MultiLineCollector {
|
|||||||
'317', // Sign on time and idle time
|
'317', // Sign on time and idle time
|
||||||
],
|
],
|
||||||
resolve,
|
resolve,
|
||||||
|
match,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,13 +99,17 @@ export class WhoisCollector extends MultiLineCollector {
|
|||||||
* `315` - end of WHO
|
* `315` - end of WHO
|
||||||
*/
|
*/
|
||||||
export class WhoCollector extends MultiLineCollector {
|
export class WhoCollector extends MultiLineCollector {
|
||||||
constructor(resolve: (lines: IIRCLine[]) => void) {
|
constructor(
|
||||||
|
resolve: (lines: IIRCLine[]) => void,
|
||||||
|
match?: (line: IIRCLine) => boolean,
|
||||||
|
) {
|
||||||
super(
|
super(
|
||||||
'315', // End of WHO
|
'315', // End of WHO
|
||||||
[
|
[
|
||||||
'352', // WHO line: <channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real_name>
|
'352', // WHO line: <channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real_name>
|
||||||
],
|
],
|
||||||
resolve,
|
resolve,
|
||||||
|
match,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
src/utility/estimate-prefix.ts
Normal file
25
src/utility/estimate-prefix.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { IIRCOptions } from '../types/irc.interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate the length of the message the actual server will send to clients,
|
||||||
|
* you can use this information to truncate/split your messages.
|
||||||
|
*
|
||||||
|
* `:nickname!username@hostname command args :trailing\r\n`
|
||||||
|
* @param options IRC options (nick, hostname, username)
|
||||||
|
* @param command Command to be sent
|
||||||
|
* @param args Arguments for the command
|
||||||
|
* @returns Predictive length of the message without the trailing
|
||||||
|
*/
|
||||||
|
export const estimateMessagePrefixLength = (
|
||||||
|
options: IIRCOptions,
|
||||||
|
command: string,
|
||||||
|
args: string[],
|
||||||
|
) => {
|
||||||
|
const header =
|
||||||
|
options.nick.length +
|
||||||
|
(options.hostname || '').length +
|
||||||
|
(options.username || '').length +
|
||||||
|
4 +
|
||||||
|
2;
|
||||||
|
return command.length + args.join(' ').length + 3 + header;
|
||||||
|
};
|
47
src/utility/formatstr.ts
Normal file
47
src/utility/formatstr.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Basic string formatting, akin to `sprintf` or `util.format`, but limited.
|
||||||
|
* Only supports `%o` for arrays, `%j` for objects, `%s` for strings and `%d` for numbers.
|
||||||
|
*
|
||||||
|
* You should really be using template literals instead (developer's note to self, lol).
|
||||||
|
* @param fmt Input
|
||||||
|
* @param args Arguments
|
||||||
|
* @returns Formatted string
|
||||||
|
*/
|
||||||
|
export function formatstr(fmt: string, ...args: any[]) {
|
||||||
|
const re = /(%?)(%([ojds]))/g;
|
||||||
|
if (args.length) {
|
||||||
|
fmt = fmt.replace(re, function (match, escaped, ptn, flag) {
|
||||||
|
let arg = args.shift();
|
||||||
|
switch (flag) {
|
||||||
|
case 'o':
|
||||||
|
if (Array.isArray(arg)) {
|
||||||
|
arg = JSON.stringify(arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
arg = '' + arg;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
arg = Number(arg);
|
||||||
|
break;
|
||||||
|
case 'j':
|
||||||
|
arg = JSON.stringify(arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!escaped) {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
args.unshift(arg);
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// arguments remain after formatting
|
||||||
|
if (args.length) {
|
||||||
|
fmt += ' ' + args.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt = fmt.replace('%%', '%');
|
||||||
|
|
||||||
|
return '' + fmt;
|
||||||
|
}
|
15
src/utility/mode-from-prefix.ts
Normal file
15
src/utility/mode-from-prefix.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Get a channel user mode from their prefix.
|
||||||
|
* @param prefixOrNick Prefix on its own (`@`, `+`, etc) or a nick `@Diamond`
|
||||||
|
* @param supportedModes Object of channel modes `{ 'o': '@' }`
|
||||||
|
* @returns Mode for prefix symbol
|
||||||
|
*/
|
||||||
|
export const modeFromPrefix = (
|
||||||
|
prefixOrNick: string,
|
||||||
|
supportedModes: Record<string, string>,
|
||||||
|
) => {
|
||||||
|
const pfx = prefixOrNick.substring(0, 1);
|
||||||
|
return Object.keys(supportedModes)[
|
||||||
|
Object.values(supportedModes).indexOf(pfx)
|
||||||
|
];
|
||||||
|
};
|
@ -1,21 +1,11 @@
|
|||||||
import { IIRCLine } from '../types/irc.interfaces';
|
import { IIRCLine } from '../types/irc.interfaces';
|
||||||
|
|
||||||
function parseERROR(line: string[]): IIRCLine {
|
function skipAndLine(line: string[]): string {
|
||||||
const final: IIRCLine = {
|
let result = line.slice(1).join(' ');
|
||||||
user: { nickname: '', username: '', hostname: '' },
|
if (result.indexOf(':') === 0) {
|
||||||
command: 'ERROR',
|
result = result.substring(1);
|
||||||
trailing: '',
|
|
||||||
raw: line.join(' '),
|
|
||||||
};
|
|
||||||
|
|
||||||
let pass1 = line.slice(1).join(' ');
|
|
||||||
if (pass1.indexOf(':') === 0) {
|
|
||||||
pass1 = pass1.substring(1);
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
final.trailing = pass1;
|
|
||||||
|
|
||||||
return final;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parse(rawline: string): IIRCLine {
|
export function parse(rawline: string): IIRCLine {
|
||||||
@ -35,8 +25,11 @@ export function parse(rawline: string): IIRCLine {
|
|||||||
rawline.indexOf(':') === 0
|
rawline.indexOf(':') === 0
|
||||||
? rawline.substring(1).split(' ')
|
? rawline.substring(1).split(' ')
|
||||||
: rawline.split(' ');
|
: rawline.split(' ');
|
||||||
if (pass1[0] === 'ERROR') {
|
|
||||||
return parseERROR(pass1);
|
if (pass1[0] === 'PING' || pass1[0] === 'ERROR') {
|
||||||
|
final.command = pass1[0];
|
||||||
|
final.trailing = skipAndLine(pass1);
|
||||||
|
return final;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pass1[0].indexOf('!') !== -1) {
|
if (pass1[0].indexOf('!') !== -1) {
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
export type EventHandler = (...args: any[]) => void;
|
|
||||||
|
|
||||||
export class SimpleEventEmitter {
|
|
||||||
private _handlers: { [x: string]: EventHandler[] } = {};
|
|
||||||
|
|
||||||
on(event: string, fn: EventHandler) {
|
|
||||||
if (typeof fn !== 'function') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._handlers[event]) {
|
|
||||||
this._handlers[event] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this._handlers[event].push(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(event: string, ...args: any[]): void {
|
|
||||||
if (!this._handlers[event]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._handlers[event]
|
|
||||||
.filter((fn) => fn && typeof fn === 'function')
|
|
||||||
.forEach((fn) => fn(...args));
|
|
||||||
}
|
|
||||||
|
|
||||||
removeEventListener(event: string, fn: EventHandler): void {
|
|
||||||
if (!this._handlers[event] || typeof fn !== 'function') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const indexOf = this._handlers[event].indexOf(fn);
|
|
||||||
if (indexOf > -1) {
|
|
||||||
this._handlers[event].splice(indexOf, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
65
src/utility/typed-event-emitter.ts
Normal file
65
src/utility/typed-event-emitter.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
export type EventMap = {
|
||||||
|
[key: string]: (...args: any) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class TypedEventEmitter<Events extends EventMap> {
|
||||||
|
private _handlers: {
|
||||||
|
[x: string]: { fn: (...args: any) => void; once: boolean }[];
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
addListener<E extends keyof Events>(event: E, fn: Events[E], once = false) {
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._handlers[event as string]) {
|
||||||
|
this._handlers[event as string] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this._handlers[event as string].push({ fn, once });
|
||||||
|
}
|
||||||
|
|
||||||
|
on<E extends keyof Events>(event: E, fn: Events[E]) {
|
||||||
|
this.addListener<E>(event, fn, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
once<E extends keyof Events>(event: E, fn: Events[E]) {
|
||||||
|
this.addListener<E>(event, fn, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit<E extends keyof Events>(event: E, ...args: Parameters<Events[E]>): void {
|
||||||
|
if (!this._handlers[event as string]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._handlers[event as string]
|
||||||
|
.filter((handler) => handler && typeof handler.fn === 'function')
|
||||||
|
.forEach((handler) => {
|
||||||
|
handler.fn(...(args as []));
|
||||||
|
if (handler.once) {
|
||||||
|
this.removeEventListener(event, handler.fn as Events[E]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEventListener<E extends keyof Events>(event: E, fn: Events[E]): void {
|
||||||
|
if (!this._handlers[event as string] || typeof fn !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexOf = this._handlers[event as string].findIndex(
|
||||||
|
(entry) => entry.fn === fn,
|
||||||
|
);
|
||||||
|
if (indexOf > -1) {
|
||||||
|
this._handlers[event as string].splice(indexOf, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAllListeners<E extends keyof Events>(event: E): void {
|
||||||
|
if (!this._handlers[event as string]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this._handlers[event as string];
|
||||||
|
}
|
||||||
|
}
|
@ -7,22 +7,22 @@ export function mapUserInput(data: IUserLine, msgMaxLength = 512): string[][] {
|
|||||||
case 'topic':
|
case 'topic':
|
||||||
output.push([
|
output.push([
|
||||||
'TOPIC %s',
|
'TOPIC %s',
|
||||||
data.arguments![0],
|
data.arguments[0],
|
||||||
data.message !== '' ? ' :' + data.message : '',
|
data.message !== '' ? ' :' + data.message : '',
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case 'kick':
|
case 'kick':
|
||||||
output.push(['KICK %s :%s', data.arguments!.join(' '), data.message]);
|
output.push(['KICK %s :%s', data.arguments.join(' '), data.message]);
|
||||||
break;
|
break;
|
||||||
case 'part':
|
case 'part':
|
||||||
output.push(['PART %s :%s', data.arguments![0], data.message]);
|
output.push(['PART %s :%s', data.arguments[0], data.message]);
|
||||||
break;
|
break;
|
||||||
case 'nick':
|
case 'nick':
|
||||||
case 'whois':
|
case 'whois':
|
||||||
case 'who':
|
case 'who':
|
||||||
case 'names':
|
case 'names':
|
||||||
case 'join':
|
case 'join':
|
||||||
output.push(['JOIN %s', data.arguments![0]]);
|
output.push(['JOIN %s', data.arguments[0]]);
|
||||||
break;
|
break;
|
||||||
case 'quit':
|
case 'quit':
|
||||||
output.push(['QUIT :%s', data.message]);
|
output.push(['QUIT :%s', data.message]);
|
||||||
@ -36,7 +36,7 @@ export function mapUserInput(data: IUserLine, msgMaxLength = 512): string[][] {
|
|||||||
...messages.map((msg) => [
|
...messages.map((msg) => [
|
||||||
'%s %s :%s',
|
'%s %s :%s',
|
||||||
data.command.toUpperCase(),
|
data.command.toUpperCase(),
|
||||||
data.arguments![0],
|
data.arguments[0],
|
||||||
msg,
|
msg,
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
@ -48,13 +48,13 @@ export function mapUserInput(data: IUserLine, msgMaxLength = 512): string[][] {
|
|||||||
case 'ctcp':
|
case 'ctcp':
|
||||||
let ctcpmsg = '';
|
let ctcpmsg = '';
|
||||||
|
|
||||||
if (data.arguments![1].toLowerCase() === 'ping') {
|
if (data.arguments[1].toLowerCase() === 'ping') {
|
||||||
ctcpmsg = 'PING ' + Math.floor(Date.now() / 1000);
|
ctcpmsg = 'PING ' + Math.floor(Date.now() / 1000);
|
||||||
} else {
|
} else {
|
||||||
ctcpmsg = data.message;
|
ctcpmsg = data.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.push(['PRIVMSG %s :\x01%s\x01', data.arguments![0], ctcpmsg]);
|
output.push(['PRIVMSG %s :\x01%s\x01', data.arguments[0], ctcpmsg]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
output.push([data.command.toUpperCase(), data.message]);
|
output.push([data.command.toUpperCase(), data.message]);
|
||||||
|
29
src/utility/who-parser.ts
Normal file
29
src/utility/who-parser.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { IIRCLine } from '../types/irc.interfaces';
|
||||||
|
|
||||||
|
export interface WhoResponse {
|
||||||
|
channel?: string;
|
||||||
|
username?: string;
|
||||||
|
hostname?: string;
|
||||||
|
server?: string;
|
||||||
|
nickname?: string;
|
||||||
|
modes?: string[];
|
||||||
|
oper?: boolean;
|
||||||
|
hops?: number;
|
||||||
|
realname?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseWho = (lines: IIRCLine[]): WhoResponse[] => {
|
||||||
|
return lines
|
||||||
|
.filter(({ command }) => command === '352')
|
||||||
|
.map((line) => ({
|
||||||
|
channel: line.arguments[1],
|
||||||
|
username: line.arguments[2],
|
||||||
|
hostname: line.arguments[3],
|
||||||
|
server: line.arguments[4],
|
||||||
|
nickname: line.arguments[5],
|
||||||
|
modes: line.arguments[6].split(''),
|
||||||
|
oper: line.arguments[6].includes('*'),
|
||||||
|
hops: parseInt(line.trailing.split(' ')[0], 10),
|
||||||
|
realname: line.trailing.split(' ').slice(1).join(' '),
|
||||||
|
}));
|
||||||
|
};
|
@ -24,8 +24,8 @@ export function parseWhois(lines: IIRCLine[]) {
|
|||||||
lines.forEach((line) => {
|
lines.forEach((line) => {
|
||||||
switch (line.command) {
|
switch (line.command) {
|
||||||
case '311':
|
case '311':
|
||||||
data.nickname = line.arguments![1];
|
data.nickname = line.arguments[1];
|
||||||
data.hostmask = `${line.arguments![2]}@${line.arguments![3]}`;
|
data.hostmask = `${line.arguments[2]}@${line.arguments[3]}`;
|
||||||
data.realname = line.trailing || '';
|
data.realname = line.trailing || '';
|
||||||
break;
|
break;
|
||||||
case '319':
|
case '319':
|
||||||
@ -38,14 +38,14 @@ export function parseWhois(lines: IIRCLine[]) {
|
|||||||
data.usingModes = line.trailing;
|
data.usingModes = line.trailing;
|
||||||
break;
|
break;
|
||||||
case '312':
|
case '312':
|
||||||
data.server = line.arguments![2];
|
data.server = line.arguments[2];
|
||||||
data.serverName = line.trailing || '';
|
data.serverName = line.trailing || '';
|
||||||
break;
|
break;
|
||||||
case '313':
|
case '313':
|
||||||
data.title = line.trailing;
|
data.title = line.trailing;
|
||||||
break;
|
break;
|
||||||
case '330':
|
case '330':
|
||||||
data.loggedInAs = line.arguments![2];
|
data.loggedInAs = line.arguments[2];
|
||||||
break;
|
break;
|
||||||
case '335':
|
case '335':
|
||||||
data.bot = true;
|
data.bot = true;
|
||||||
@ -57,8 +57,8 @@ export function parseWhois(lines: IIRCLine[]) {
|
|||||||
data.secure = true;
|
data.secure = true;
|
||||||
break;
|
break;
|
||||||
case '317':
|
case '317':
|
||||||
data.signOnTime = parseInt(line.arguments![3], 10);
|
data.signOnTime = parseInt(line.arguments[3], 10);
|
||||||
data.idleSeconds = parseInt(line.arguments![2], 10);
|
data.idleSeconds = parseInt(line.arguments[2], 10);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user