Initial commit
This commit is contained in:
commit
ea7f81d9fe
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/node_modules/
|
||||
/lib/
|
3
.npmignore
Normal file
3
.npmignore
Normal file
@ -0,0 +1,3 @@
|
||||
src/
|
||||
.prettierrc
|
||||
.vscode
|
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"files.insertFinalNewline": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"eslint.validate": [
|
||||
"typescript",
|
||||
],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
72
package-lock.json
generated
Normal file
72
package-lock.json
generated
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "@icynet/irclib",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@icynet/irclib",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.7.18",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.7.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz",
|
||||
"integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
|
||||
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "18.7.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz",
|
||||
"integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
|
||||
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
19
package.json
Normal file
19
package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@icynet/irclib",
|
||||
"version": "1.0.0",
|
||||
"description": "IRC library written in TypeScript",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "tsc",
|
||||
"prepublish": "npm run build"
|
||||
},
|
||||
"author": "Evert Prants",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.7.18",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.8.3"
|
||||
}
|
||||
}
|
22
src/bot.ts
Normal file
22
src/bot.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { format } from 'util';
|
||||
import { IRCSocketConnector } from './connector/net.connector';
|
||||
import { IRCConnectionWrapper } from './irc';
|
||||
import { IIRCOptions } from './types/irc.interfaces';
|
||||
|
||||
export class IRCBot extends IRCConnectionWrapper {
|
||||
constructor(options: IIRCOptions) {
|
||||
super(options, IRCSocketConnector);
|
||||
}
|
||||
|
||||
send(to: string, message: string, ...args: any[]) {
|
||||
this.write('PRIVMSG %s :%s', to, format(message, ...args));
|
||||
}
|
||||
|
||||
notice(to: string, message: string, ...args: any[]) {
|
||||
this.write('NOTICE %s :%s', to, format(message, ...args));
|
||||
}
|
||||
|
||||
nick(newNick: string) {
|
||||
this.write('NICK %s', newNick);
|
||||
}
|
||||
}
|
83
src/connector/net.connector.ts
Normal file
83
src/connector/net.connector.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import net, { Socket } from 'net';
|
||||
import tls, { TLSSocket } from 'tls';
|
||||
import { formatWithOptions } from 'util';
|
||||
import { IRCConnector } from '../types/impl.interface';
|
||||
import { SimpleEventEmitter } from '../utility/simple-event-emitter';
|
||||
|
||||
export class IRCSocketConnector
|
||||
extends SimpleEventEmitter
|
||||
implements IRCConnector
|
||||
{
|
||||
connected = false;
|
||||
private socket?: Socket | TLSSocket;
|
||||
|
||||
constructor(
|
||||
public secure: boolean,
|
||||
public host: string,
|
||||
public port?: number,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(): Promise<void> {
|
||||
const opts = { host: this.host, port: this.port || 6667 };
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const onConnect = () => {
|
||||
this.connected = true;
|
||||
resolve();
|
||||
};
|
||||
|
||||
try {
|
||||
if (this.secure) {
|
||||
this.socket = tls.connect(opts, onConnect);
|
||||
} else {
|
||||
this.socket = net.connect(opts, onConnect);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
return reject(e);
|
||||
}
|
||||
|
||||
this.handle();
|
||||
});
|
||||
}
|
||||
|
||||
async destroy(): Promise<void> {
|
||||
this.connected = false;
|
||||
this.socket?.destroy();
|
||||
this.socket = undefined;
|
||||
}
|
||||
|
||||
write(format: string, ...args: any[]): void {
|
||||
this.socket?.write(
|
||||
formatWithOptions({ colors: false }, format, ...args) + '\r\n',
|
||||
);
|
||||
}
|
||||
|
||||
private handle() {
|
||||
this.socket?.setDefaultEncoding('utf-8');
|
||||
|
||||
let buffer: string = '';
|
||||
this.socket?.on('data', (chunk: string) => {
|
||||
buffer += chunk;
|
||||
const data = buffer.split('\r\n');
|
||||
buffer = data.pop() || '';
|
||||
|
||||
data.forEach((line: string) => {
|
||||
if (line.indexOf('PING') === 0) {
|
||||
this.socket?.write('PONG' + line.substring(4) + '\r\n');
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('data', line);
|
||||
});
|
||||
});
|
||||
|
||||
this.socket?.on('error', (err) => this.emit('error', err));
|
||||
this.socket?.on('close', (err) => {
|
||||
this.connected = false;
|
||||
this.socket = undefined;
|
||||
this.emit('close', err);
|
||||
});
|
||||
}
|
||||
}
|
79
src/connector/websocket.connector.ts
Normal file
79
src/connector/websocket.connector.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { formatWithOptions } from 'util';
|
||||
import { IRCConnector } from '../types/impl.interface';
|
||||
import { SimpleEventEmitter } from '../utility/simple-event-emitter';
|
||||
|
||||
export class IRCWebSocketConnector
|
||||
extends SimpleEventEmitter
|
||||
implements IRCConnector
|
||||
{
|
||||
connected = false;
|
||||
private socket?: WebSocket;
|
||||
|
||||
constructor(
|
||||
public secure: boolean,
|
||||
public host: string,
|
||||
public port?: number,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(): Promise<void> {
|
||||
const url = `ws${this.secure ? 's' : ''}://${this.host}:${
|
||||
this.port || 6667
|
||||
}`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const onConnect = () => {
|
||||
this.connected = true;
|
||||
resolve();
|
||||
};
|
||||
|
||||
try {
|
||||
this.socket = new WebSocket(url);
|
||||
} catch (e: unknown) {
|
||||
return reject(e);
|
||||
}
|
||||
|
||||
this.socket?.addEventListener('open', onConnect);
|
||||
this.handle();
|
||||
});
|
||||
}
|
||||
|
||||
async destroy(): Promise<void> {
|
||||
this.connected = false;
|
||||
this.socket?.close();
|
||||
this.socket = undefined;
|
||||
}
|
||||
|
||||
write(format: string, ...args: any[]): void {
|
||||
this.socket?.send(
|
||||
formatWithOptions({ colors: false }, format, ...args) + '\r\n',
|
||||
);
|
||||
}
|
||||
|
||||
private handle() {
|
||||
let buffer: string = '';
|
||||
this.socket?.addEventListener('message', (event) => {
|
||||
const chunk = event.data.toString();
|
||||
buffer += chunk;
|
||||
const data = buffer.split('\r\n');
|
||||
buffer = data.pop() || '';
|
||||
|
||||
data.forEach((line: string) => {
|
||||
if (line.indexOf('PING') === 0) {
|
||||
this.socket?.send('PONG' + line.substring(4) + '\r\n');
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('data', line);
|
||||
});
|
||||
});
|
||||
|
||||
this.socket?.addEventListener('error', (err) => this.emit('error', err));
|
||||
this.socket?.addEventListener('close', (err) => {
|
||||
this.connected = false;
|
||||
this.socket = undefined;
|
||||
this.emit('close', err);
|
||||
});
|
||||
}
|
||||
}
|
56
src/examples/connection-test.ts
Normal file
56
src/examples/connection-test.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { IRCBot } from '../bot';
|
||||
import { NickServValidator } from '../utility/nickserv-validator';
|
||||
|
||||
const bot = new IRCBot({
|
||||
host: 'icynet.eu',
|
||||
nick: 'MyTestBot',
|
||||
channels: ['#squeebot'],
|
||||
nickserv: {
|
||||
enabled: true,
|
||||
command: 'STATUS',
|
||||
},
|
||||
});
|
||||
|
||||
const nickserv = new NickServValidator(bot);
|
||||
|
||||
bot.on('authenticated', () => {
|
||||
console.log('Successful connection!');
|
||||
});
|
||||
|
||||
bot.on('server-supports', (supported) => {
|
||||
console.log(supported);
|
||||
});
|
||||
|
||||
// bot.on('line', console.log);
|
||||
|
||||
bot.on('message', ({ message, to, nickname }) => {
|
||||
console.log(`[${to}] ${nickname}: ${message}`);
|
||||
|
||||
if (message.startsWith('!test')) {
|
||||
nickserv
|
||||
.getNickStatus(nickname)
|
||||
.then((valid) =>
|
||||
bot.send(
|
||||
to,
|
||||
`Hello, %s! ${
|
||||
valid ? 'You are logged in.' : 'You are not logged in.'
|
||||
}`,
|
||||
nickname,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (message.startsWith('!whois')) {
|
||||
bot.whois(nickname).then(console.log);
|
||||
}
|
||||
|
||||
if (message.startsWith('!ping')) {
|
||||
bot.getPing().then((res) => bot.send(to, `Pong: ${res / 1000}s`));
|
||||
}
|
||||
});
|
||||
|
||||
bot.on('disconnect', console.log);
|
||||
|
||||
bot.on('names', console.log);
|
||||
|
||||
bot.connect();
|
12
src/index.ts
Normal file
12
src/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export * from './types/irc.interfaces';
|
||||
export * from './types/impl.interface';
|
||||
|
||||
export * from './utility/collector';
|
||||
export * from './utility/nickserv-validator';
|
||||
export * from './utility/parser';
|
||||
export * from './utility/simple-event-emitter';
|
||||
export * from './utility/truncate';
|
||||
export * from './utility/user-mapper';
|
||||
export * from './utility/whois-parser';
|
||||
|
||||
export * from './irc';
|
519
src/irc.ts
Normal file
519
src/irc.ts
Normal file
@ -0,0 +1,519 @@
|
||||
import {
|
||||
IRCCommunicator,
|
||||
IRCConnector,
|
||||
IRCConnectorConstructor,
|
||||
} from './types/impl.interface';
|
||||
import {
|
||||
IIRCLine,
|
||||
IIRCOptions,
|
||||
INickStore,
|
||||
IQueue,
|
||||
} from './types/irc.interfaces';
|
||||
import { Collector, WhoisCollector } from './utility/collector';
|
||||
import { parse } from './utility/parser';
|
||||
import { SimpleEventEmitter } from './utility/simple-event-emitter';
|
||||
import { parseWhois, WhoisResponse } from './utility/whois-parser';
|
||||
|
||||
const encodeBase64 = (input: string) => {
|
||||
if (window !== undefined && btoa !== undefined) {
|
||||
return btoa(input);
|
||||
} else if (Buffer !== undefined) {
|
||||
return Buffer.from(input).toString('base64');
|
||||
}
|
||||
return input;
|
||||
};
|
||||
|
||||
export class IRCConnectionWrapper
|
||||
extends SimpleEventEmitter
|
||||
implements IRCCommunicator
|
||||
{
|
||||
public channels: string[] = [];
|
||||
public queue: IQueue[] = [];
|
||||
public nickservStore: { [key: string]: INickStore } = {};
|
||||
public authenticated = false;
|
||||
public serverData: { [key: string]: any } = {
|
||||
name: '',
|
||||
supportedModes: {},
|
||||
serverSupports: {},
|
||||
};
|
||||
|
||||
public connection?: IRCConnector;
|
||||
|
||||
private _supportsDone = false;
|
||||
private _lastLineWasSupports = false;
|
||||
|
||||
constructor(
|
||||
public options: IIRCOptions,
|
||||
public connector: IRCConnectorConstructor,
|
||||
) {
|
||||
super();
|
||||
if (!options.username) {
|
||||
options.username = options.nick;
|
||||
}
|
||||
this.handlers();
|
||||
}
|
||||
|
||||
write(format: string, ...args: any[]): void {
|
||||
this.connection?.write(format, ...args);
|
||||
}
|
||||
|
||||
private handlers(): void {
|
||||
this.on('authenticated', () => {
|
||||
if (this.options.channels?.length) {
|
||||
this.joinMissingChannels(this.options.channels);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private authenticate(): void {
|
||||
if (this.options.sasl) {
|
||||
this.write('CAP REQ :sasl');
|
||||
}
|
||||
|
||||
if (this.options.password && !this.options.sasl) {
|
||||
this.write('PASS %s', this.options.password);
|
||||
}
|
||||
|
||||
this.write(
|
||||
'USER %s 8 * :%s',
|
||||
this.options.username,
|
||||
this.options.realname || 'realname',
|
||||
);
|
||||
this.write('NICK %s', this.options.nick);
|
||||
}
|
||||
|
||||
private joinMissingChannels(arr: string[]): void {
|
||||
if (arr) {
|
||||
for (const i in arr) {
|
||||
let chan = arr[i];
|
||||
if (chan.indexOf('#') !== 0) {
|
||||
chan = '#' + chan;
|
||||
}
|
||||
|
||||
if (this.channels.indexOf(chan) === -1) {
|
||||
this.write('JOIN %s', chan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleServerLine(line: IIRCLine): void {
|
||||
if (this.queue.length) {
|
||||
let skipHandling = false;
|
||||
const afterModifyQueue: IQueue[] = [];
|
||||
this.queue.forEach((entry) => {
|
||||
if (!entry.untracked) {
|
||||
if (entry.await && line.command === entry.await) {
|
||||
if (
|
||||
((entry.from &&
|
||||
line.user.nickname.toLowerCase() ===
|
||||
entry.from.toLowerCase()) ||
|
||||
!entry.from) &&
|
||||
entry.do
|
||||
) {
|
||||
skipHandling = true;
|
||||
entry.do(line, entry.buffer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
entry.additional &&
|
||||
entry.additional.includes(line.command) &&
|
||||
entry.digest
|
||||
) {
|
||||
skipHandling = true;
|
||||
entry.digest(line);
|
||||
}
|
||||
}
|
||||
|
||||
afterModifyQueue.push(entry);
|
||||
});
|
||||
|
||||
this.queue = afterModifyQueue;
|
||||
|
||||
if (skipHandling) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this._lastLineWasSupports &&
|
||||
!this._supportsDone &&
|
||||
line.command !== '005'
|
||||
) {
|
||||
this._lastLineWasSupports = false;
|
||||
this._supportsDone = true;
|
||||
this.emit('supported-modes', this.serverData.supportedModes);
|
||||
this.emit('server-supports', this.serverData.serverSupports);
|
||||
}
|
||||
|
||||
switch (line.command.toLowerCase()) {
|
||||
case 'cap':
|
||||
if (
|
||||
line.trailing === 'sasl' &&
|
||||
line.arguments?.[1] === 'ACK' &&
|
||||
!this.authenticated
|
||||
) {
|
||||
this.write('AUTHENTICATE PLAIN');
|
||||
}
|
||||
break;
|
||||
case '+':
|
||||
case ':+': {
|
||||
if (this.authenticated) {
|
||||
return;
|
||||
}
|
||||
|
||||
const authline = encodeBase64(
|
||||
this.options.nick +
|
||||
'\x00' +
|
||||
this.options.username +
|
||||
'\x00' +
|
||||
this.options.password,
|
||||
);
|
||||
this.write('AUTHENTICATE %s', authline);
|
||||
break;
|
||||
}
|
||||
case '353': {
|
||||
const isUpQueued = this.queue.find(
|
||||
(item) => item.await === '366' && item.from === line.arguments![2],
|
||||
);
|
||||
|
||||
if (isUpQueued) {
|
||||
isUpQueued.buffer.push(line);
|
||||
} else {
|
||||
this.queue.push({
|
||||
untracked: true,
|
||||
from: line.arguments![2],
|
||||
await: '366',
|
||||
additional: ['353'],
|
||||
buffer: [line],
|
||||
do: (bline, data) => {
|
||||
this.emit('names', {
|
||||
channel: bline.arguments![1],
|
||||
list: [
|
||||
...data.map((cline: IIRCLine) =>
|
||||
(cline.trailing || '').split(' '),
|
||||
),
|
||||
].flat(),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '366': {
|
||||
const isUpQueued = this.queue.find(
|
||||
(item) => item.await === '366' && item.from === line.arguments![1],
|
||||
);
|
||||
if (isUpQueued) {
|
||||
isUpQueued.do(line, isUpQueued.buffer);
|
||||
this.queue.splice(this.queue.indexOf(isUpQueued), 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '433':
|
||||
const newNick = this.options.nick + '_';
|
||||
this.write('NICK %s', newNick);
|
||||
this.options.nick = newNick;
|
||||
break;
|
||||
case '904':
|
||||
this.emit('error', {
|
||||
error: new Error(line.trailing),
|
||||
fatal: true,
|
||||
});
|
||||
break;
|
||||
case '903':
|
||||
this.write('CAP END');
|
||||
break;
|
||||
case 'notice':
|
||||
case 'privmsg':
|
||||
if (!line.user.nickname || line.user.nickname === '') {
|
||||
return;
|
||||
}
|
||||
this.emit('message', {
|
||||
type: line.command.toLowerCase(),
|
||||
message: line.trailing,
|
||||
to: line.arguments?.[0],
|
||||
nickname: line.user.nickname,
|
||||
raw: line,
|
||||
});
|
||||
break;
|
||||
case '001':
|
||||
if (!this.authenticated) {
|
||||
this.serverData.name = line.user.hostname;
|
||||
this.authenticated = true;
|
||||
|
||||
// Set nick to what the server actually thinks is our nick
|
||||
this.options.nick = line.arguments?.[0] || this.options.nick;
|
||||
this.authenticated = true;
|
||||
this.emit('authenticated', true);
|
||||
|
||||
// Send a whois request for self in order to reliably fetch hostname of self
|
||||
this.write('WHOIS %s', this.options.nick);
|
||||
}
|
||||
break;
|
||||
case '005': {
|
||||
this._lastLineWasSupports = true;
|
||||
if (!line.arguments) {
|
||||
break;
|
||||
}
|
||||
|
||||
const argv = line.arguments?.slice(1);
|
||||
for (const entry of argv) {
|
||||
if (entry.indexOf('=') !== -1) {
|
||||
const t = entry.split('=') as string[];
|
||||
if (t[0] === 'PREFIX') {
|
||||
const d = t[1].match(/\((\w+)\)(.*)/);
|
||||
if (d) {
|
||||
const r = d[1].split('');
|
||||
const aa = d[2].split('');
|
||||
r.forEach((value, index) => {
|
||||
this.serverData.supportedModes[value] = aa[index];
|
||||
});
|
||||
}
|
||||
} else if (t[0] === 'NETWORK') {
|
||||
this.serverData.network = t[1];
|
||||
} else if (t[0] === 'CHANNELLEN') {
|
||||
this.serverData.maxChannelLength = parseInt(t[1], 10);
|
||||
}
|
||||
|
||||
let numeral: string | number = t[1];
|
||||
if (!isNaN(parseInt(numeral, 10))) {
|
||||
numeral = parseInt(numeral, 10);
|
||||
}
|
||||
this.serverData.serverSupports[t[0]] = numeral;
|
||||
} else {
|
||||
this.serverData.serverSupports[entry] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Set hostname from 396 (non-standard)
|
||||
case '396':
|
||||
this.options.hostname = line.arguments?.[1];
|
||||
break;
|
||||
// Set hostname from self-whois
|
||||
case '311':
|
||||
if (line.arguments?.[1] !== this.options.nick) {
|
||||
return;
|
||||
}
|
||||
this.options.hostname = line.arguments?.[3];
|
||||
break;
|
||||
case '321':
|
||||
this.emit('channel-list-item', {
|
||||
channel: 'Channel',
|
||||
users: 'Users',
|
||||
topic: 'Topic',
|
||||
});
|
||||
break;
|
||||
case '322':
|
||||
this.emit('channel-list-item', {
|
||||
channel: line.arguments![1],
|
||||
users: line.arguments![2],
|
||||
topic: line.trailing,
|
||||
});
|
||||
break;
|
||||
case 'quit':
|
||||
if (line.user.nickname !== this.options.nick) {
|
||||
if (this.nickservStore[line.user.nickname]) {
|
||||
delete this.nickservStore[line.user.nickname];
|
||||
}
|
||||
|
||||
this.emit('leave', {
|
||||
nickname: line.user.nickname,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'nick':
|
||||
if (line.user.nickname === this.options.nick) {
|
||||
this.options.nick = line.arguments?.[0] || 'unknown';
|
||||
} else if (this.nickservStore[line.user.nickname]) {
|
||||
delete this.nickservStore[line.user.nickname];
|
||||
}
|
||||
this.emit('nick', {
|
||||
oldNick: line.user.nickname,
|
||||
newNick: line.arguments?.[0],
|
||||
});
|
||||
break;
|
||||
case 'join':
|
||||
if (line.user.nickname === this.options.nick && line.trailing) {
|
||||
this.channels.push(line.trailing);
|
||||
}
|
||||
|
||||
this.emit('join', {
|
||||
nickname: line.user.nickname,
|
||||
channel: line.trailing,
|
||||
});
|
||||
break;
|
||||
case 'mode':
|
||||
let isChannelMode = false;
|
||||
let method = '+';
|
||||
let lts = line.trailing ? line.trailing.split(' ') : [];
|
||||
if (line.arguments![0].indexOf('#') !== -1) {
|
||||
isChannelMode = true;
|
||||
}
|
||||
|
||||
let modes: string | string[] = line.arguments![1];
|
||||
|
||||
if (!modes && line.trailing !== '') {
|
||||
modes = line.trailing!;
|
||||
}
|
||||
|
||||
let sender = line.user.nickname;
|
||||
if (sender === '') {
|
||||
sender = line.user.hostname;
|
||||
}
|
||||
|
||||
method = modes.substring(0, 1);
|
||||
modes = modes.substring(1).split('');
|
||||
let pass = [];
|
||||
|
||||
if (isChannelMode) {
|
||||
for (let i in modes) {
|
||||
let mode = modes[i];
|
||||
let modei = parseInt(i);
|
||||
if (this.serverData.supportedModes[mode]) {
|
||||
this.emit('channel-mode', {
|
||||
type: method,
|
||||
mode: mode,
|
||||
modeTarget: line.arguments![2]
|
||||
? line.arguments![2 + modei]
|
||||
? line.arguments![2 + modei]
|
||||
: lts[modei - 1]
|
||||
: lts[modei],
|
||||
...line,
|
||||
});
|
||||
} else {
|
||||
pass.push(mode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pass = modes;
|
||||
}
|
||||
|
||||
if (pass.length > 0) {
|
||||
this.emit('user-mode', {
|
||||
type: method,
|
||||
mode: pass.join(''),
|
||||
modeTarget: line.arguments![0],
|
||||
...line,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'part':
|
||||
case 'kick':
|
||||
if (line.user.nickname === this.options.nick && line.arguments) {
|
||||
const indexAt = this.channels.indexOf(line.arguments[0]);
|
||||
if (indexAt !== -1) {
|
||||
this.channels.splice(indexAt, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('leave', {
|
||||
nickname: line.user.nickname,
|
||||
channel: line.arguments?.[0],
|
||||
});
|
||||
break;
|
||||
case 'error':
|
||||
this.emit('error', { fatal: true, error: new Error(line.raw) });
|
||||
if (!this.authenticated) {
|
||||
this.connection?.destroy();
|
||||
this.connection = undefined;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async connect(): Promise<void> {
|
||||
if (this.connection) {
|
||||
await this.connection.destroy();
|
||||
}
|
||||
|
||||
this.connection = new this.connector(
|
||||
this.options.ssl ?? false,
|
||||
this.options.host,
|
||||
this.options.port,
|
||||
);
|
||||
|
||||
this.connection.on('close', (data) => {
|
||||
this.emit('disconnect', {
|
||||
type: 'sock_closed',
|
||||
raw: data,
|
||||
message: 'Connection closed.',
|
||||
});
|
||||
this.connection = undefined;
|
||||
this.authenticated = false;
|
||||
});
|
||||
|
||||
this.connection.on('error', (data) => {
|
||||
this.emit('error', { fatal: true, error: new Error(data) });
|
||||
this.connection = undefined;
|
||||
this.authenticated = false;
|
||||
});
|
||||
|
||||
this.connection.on('data', (line: string) => {
|
||||
const parsedLine = parse(line);
|
||||
this.emit('line', parsedLine);
|
||||
this.handleServerLine(parsedLine);
|
||||
});
|
||||
|
||||
await this.connection.connect();
|
||||
|
||||
this.authenticate();
|
||||
}
|
||||
|
||||
public async disconnect(reason?: string): Promise<void> {
|
||||
if (!this.connected) {
|
||||
if (this.connection) {
|
||||
await this.connection.destroy();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.write('QUIT :%s', reason || 'Client exiting');
|
||||
|
||||
// Wait for exit
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (!this.connected) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
public get connected() {
|
||||
return this.connection?.connected ?? false;
|
||||
}
|
||||
|
||||
public async getPing(): Promise<number> {
|
||||
return new Promise<number>((resolve) => {
|
||||
const sendTime = Date.now();
|
||||
|
||||
this.useCollector(
|
||||
new Collector('PONG', (line) => {
|
||||
resolve(Date.now() - sendTime);
|
||||
}),
|
||||
'PING :%s',
|
||||
this.serverData.name,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public async whois(nick: string): Promise<WhoisResponse> {
|
||||
return new Promise((resolve) => {
|
||||
this.useCollector(
|
||||
new WhoisCollector((data) => resolve(parseWhois(data))),
|
||||
'WHOIS %s',
|
||||
nick,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public useCollector(collector: IQueue, line: string, ...args: any[]) {
|
||||
this.queue.push(collector);
|
||||
this.write(line, ...args);
|
||||
}
|
||||
}
|
0
src/types/events.ts
Normal file
0
src/types/events.ts
Normal file
15
src/types/impl.interface.ts
Normal file
15
src/types/impl.interface.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export interface IRCCommunicator {
|
||||
emit(event: string, ...args: any[]): void;
|
||||
on(event: string, handler: (...args: any[]) => void): void;
|
||||
write(format: string, ...args: any[]): void;
|
||||
}
|
||||
|
||||
export interface IRCConnector extends IRCCommunicator {
|
||||
connected: boolean;
|
||||
connect(): Promise<void>;
|
||||
destroy(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IRCConnectorConstructor {
|
||||
new (secure: boolean, host: string, port?: number): IRCConnector;
|
||||
}
|
55
src/types/irc.interfaces.ts
Normal file
55
src/types/irc.interfaces.ts
Normal file
@ -0,0 +1,55 @@
|
||||
export interface IIRCUser {
|
||||
nickname: string;
|
||||
username: string;
|
||||
hostname: string;
|
||||
}
|
||||
|
||||
export interface IIRCLine {
|
||||
user: IIRCUser;
|
||||
command: string;
|
||||
arguments?: string[];
|
||||
trailing?: string;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
export interface IUserLine {
|
||||
command: string;
|
||||
arguments?: string[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface IQueue<T = any> {
|
||||
untracked?: boolean;
|
||||
await: string;
|
||||
additional?: string[];
|
||||
from?: string;
|
||||
buffer?: T;
|
||||
do(line: IIRCLine, data?: T): void;
|
||||
digest?(line: IIRCLine): void;
|
||||
}
|
||||
|
||||
export interface INickServOptions {
|
||||
enabled: boolean;
|
||||
command: string;
|
||||
nickservBot?: string;
|
||||
responseCommand?: string;
|
||||
}
|
||||
|
||||
export interface IIRCOptions {
|
||||
nick: string;
|
||||
host: string;
|
||||
username?: string;
|
||||
hostname?: string;
|
||||
realname?: string;
|
||||
port?: number;
|
||||
password?: string | null;
|
||||
sasl?: boolean;
|
||||
ssl?: boolean;
|
||||
channels?: string[];
|
||||
nickserv?: INickServOptions;
|
||||
}
|
||||
|
||||
export interface INickStore {
|
||||
checked: number;
|
||||
result: boolean;
|
||||
}
|
99
src/utility/collector.ts
Normal file
99
src/utility/collector.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { IIRCLine, IQueue } from '../types/irc.interfaces';
|
||||
|
||||
export class Collector implements IQueue<IIRCLine> {
|
||||
constructor(
|
||||
public await: string,
|
||||
private resolve: (lines: IIRCLine) => void,
|
||||
public from?: string,
|
||||
) {}
|
||||
|
||||
do(line: IIRCLine): void {
|
||||
this.resolve(line);
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiLineCollector implements IQueue<IIRCLine[]> {
|
||||
public buffer: IIRCLine[] = [];
|
||||
|
||||
constructor(
|
||||
public await: string,
|
||||
public additional: string[],
|
||||
private resolve: (lines: IIRCLine[]) => void,
|
||||
) {}
|
||||
|
||||
do(line: IIRCLine, data: IIRCLine[]): void {
|
||||
this.resolve([...data, line]);
|
||||
}
|
||||
|
||||
digest(line: IIRCLine): void {
|
||||
this.buffer.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full WHOIS response from the server
|
||||
*
|
||||
* `311` - Start of WHOIS, Nickname, hostmask, realname
|
||||
*
|
||||
* `319` - Channels
|
||||
*
|
||||
* `378` - Connecting from
|
||||
*
|
||||
* `379` - User modes
|
||||
*
|
||||
* `312` - Server and server name
|
||||
*
|
||||
* `313` - User title
|
||||
*
|
||||
* `330` - Login time
|
||||
*
|
||||
* `335` - Is a bot
|
||||
*
|
||||
* `307` - Registered
|
||||
*
|
||||
* `671` - Secure connection
|
||||
*
|
||||
* `317` - Sign on time and idle time
|
||||
*
|
||||
* `318` - End of WHOIS
|
||||
*/
|
||||
export class WhoisCollector extends MultiLineCollector {
|
||||
constructor(resolve: (lines: IIRCLine[]) => void) {
|
||||
super(
|
||||
'318', // End of WHOIS
|
||||
[
|
||||
'311', // Start of WHOIS, Nickname, hostmask, realname
|
||||
'319', // Channels
|
||||
'378', // Connecting from
|
||||
'379', // User modes
|
||||
'312', // Server and server name
|
||||
'313', // User title
|
||||
'330', // Login time
|
||||
'335', // Is a bot
|
||||
'307', // Registered
|
||||
'671', // Secure connection
|
||||
'317', // Sign on time and idle time
|
||||
],
|
||||
resolve,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full WHO response from the server
|
||||
*
|
||||
* `352` - WHO line: `<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real_name>`
|
||||
*
|
||||
* `315` - end of WHO
|
||||
*/
|
||||
export class WhoCollector extends MultiLineCollector {
|
||||
constructor(resolve: (lines: IIRCLine[]) => void) {
|
||||
super(
|
||||
'315', // End of WHO
|
||||
[
|
||||
'352', // WHO line: <channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real_name>
|
||||
],
|
||||
resolve,
|
||||
);
|
||||
}
|
||||
}
|
72
src/utility/nickserv-validator.ts
Normal file
72
src/utility/nickserv-validator.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { IRCConnectionWrapper } from '../irc';
|
||||
import { INickServOptions, INickStore } from '../types/irc.interfaces';
|
||||
import { Collector } from './collector';
|
||||
|
||||
export class NickServCollector extends Collector {
|
||||
constructor(
|
||||
nickservOptions: INickServOptions,
|
||||
resolve: (authed: boolean) => void,
|
||||
) {
|
||||
super(
|
||||
nickservOptions.responseCommand || 'NOTICE',
|
||||
(line) => {
|
||||
if (!line.trailing) {
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
const splitline = line.trailing.trim().split(' ');
|
||||
let result = false;
|
||||
if (splitline[2] !== '0') {
|
||||
result = true;
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
},
|
||||
nickservOptions.nickservBot || 'NickServ',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class NickServValidator {
|
||||
public nickservStore: { [key: string]: INickStore } = {};
|
||||
|
||||
constructor(public irc: IRCConnectionWrapper) {}
|
||||
|
||||
async getNickStatus(nickname: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
if (this.nickservStore[nickname] != null) {
|
||||
if (this.nickservStore[nickname].result === true) {
|
||||
resolve(true);
|
||||
return;
|
||||
} else {
|
||||
if (this.nickservStore[nickname].checked < Date.now() - 1800000) {
|
||||
// 30 minutes
|
||||
delete this.nickservStore[nickname];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.irc.options.nickserv &&
|
||||
this.irc.options.nickserv.enabled &&
|
||||
this.irc.options.nickserv.command
|
||||
) {
|
||||
this.irc.useCollector(
|
||||
new NickServCollector(
|
||||
this.irc.options.nickserv,
|
||||
(result: boolean) => {
|
||||
this.nickservStore[nickname] = {
|
||||
result,
|
||||
checked: Date.now(),
|
||||
};
|
||||
resolve(result);
|
||||
},
|
||||
),
|
||||
'PRIVMSG nickserv :%s %s',
|
||||
this.irc.options.nickserv.command,
|
||||
nickname,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
63
src/utility/parser.ts
Normal file
63
src/utility/parser.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { IIRCLine } from '../types/irc.interfaces';
|
||||
|
||||
function parseERROR(line: string[]): IIRCLine {
|
||||
const final: IIRCLine = {
|
||||
user: { nickname: '', username: '', hostname: '' },
|
||||
command: 'ERROR',
|
||||
trailing: '',
|
||||
raw: line.join(' '),
|
||||
};
|
||||
|
||||
let pass1 = line.slice(1).join(' ');
|
||||
if (pass1.indexOf(':') === 0) {
|
||||
pass1 = pass1.substring(1);
|
||||
}
|
||||
|
||||
final.trailing = pass1;
|
||||
|
||||
return final;
|
||||
}
|
||||
|
||||
export function parse(rawline: string): IIRCLine {
|
||||
const final: IIRCLine = {
|
||||
user: {
|
||||
nickname: '',
|
||||
username: '',
|
||||
hostname: '',
|
||||
},
|
||||
command: '',
|
||||
arguments: [],
|
||||
trailing: '',
|
||||
raw: rawline,
|
||||
};
|
||||
|
||||
const pass1 =
|
||||
rawline.indexOf(':') === 0
|
||||
? rawline.substring(1).split(' ')
|
||||
: rawline.split(' ');
|
||||
if (pass1[0] === 'ERROR') {
|
||||
return parseERROR(pass1);
|
||||
}
|
||||
|
||||
if (pass1[0].indexOf('!') !== -1) {
|
||||
const nickuser = pass1[0].split('!');
|
||||
final.user.nickname = nickuser[0];
|
||||
const userhost = nickuser[1].split('@');
|
||||
final.user.username = userhost[0];
|
||||
final.user.hostname = userhost[1];
|
||||
} else {
|
||||
final.user.hostname = pass1[0];
|
||||
}
|
||||
|
||||
final.command = pass1[1];
|
||||
|
||||
const pass2 = pass1.slice(2).join(' ');
|
||||
if (pass2.indexOf(':') !== -1) {
|
||||
final.arguments = pass2.substring(0, pass2.indexOf(' :')).split(' ');
|
||||
final.trailing = pass2.substring(pass2.indexOf(':') + 1);
|
||||
} else {
|
||||
final.arguments = pass2.split(' ');
|
||||
}
|
||||
|
||||
return final;
|
||||
}
|
38
src/utility/simple-event-emitter.ts
Normal file
38
src/utility/simple-event-emitter.ts
Normal file
@ -0,0 +1,38 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
17
src/utility/truncate.ts
Normal file
17
src/utility/truncate.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// Chop message into pieces recursively, splitting them at lenoffset
|
||||
export function truncate(msg: string, lenoffset: number): string[] {
|
||||
let pieces: string[] = [];
|
||||
if (msg.length <= lenoffset) {
|
||||
pieces.push(msg);
|
||||
} else {
|
||||
const m1 = msg.substring(0, lenoffset);
|
||||
const m2 = msg.substring(lenoffset);
|
||||
pieces.push(m1);
|
||||
if (m2.length > lenoffset) {
|
||||
pieces = pieces.concat(truncate(m2, lenoffset));
|
||||
} else {
|
||||
pieces.push(m2);
|
||||
}
|
||||
}
|
||||
return pieces;
|
||||
}
|
63
src/utility/user-mapper.ts
Normal file
63
src/utility/user-mapper.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { IUserLine } from '../types/irc.interfaces';
|
||||
import { truncate } from './truncate';
|
||||
|
||||
export function mapUserInput(data: IUserLine, msgMaxLength = 512): string[][] {
|
||||
let output: string[][] = [];
|
||||
switch (data.command) {
|
||||
case 'topic':
|
||||
output.push([
|
||||
'TOPIC %s',
|
||||
data.arguments![0],
|
||||
data.message !== '' ? ' :' + data.message : '',
|
||||
]);
|
||||
break;
|
||||
case 'kick':
|
||||
output.push(['KICK %s :%s', data.arguments!.join(' '), data.message]);
|
||||
break;
|
||||
case 'part':
|
||||
output.push(['PART %s :%s', data.arguments![0], data.message]);
|
||||
break;
|
||||
case 'nick':
|
||||
case 'whois':
|
||||
case 'who':
|
||||
case 'names':
|
||||
case 'join':
|
||||
output.push(['JOIN %s', data.arguments![0]]);
|
||||
break;
|
||||
case 'quit':
|
||||
output.push(['QUIT :%s', data.message]);
|
||||
break;
|
||||
case 'privmsg':
|
||||
case 'notice':
|
||||
const split = data.message.split('\n');
|
||||
for (const splitMsg of split) {
|
||||
const messages = truncate(splitMsg, msgMaxLength);
|
||||
output.push(
|
||||
...messages.map((msg) => [
|
||||
'%s %s :%s',
|
||||
data.command.toUpperCase(),
|
||||
data.arguments![0],
|
||||
msg,
|
||||
]),
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'list':
|
||||
output.push([data.command.toUpperCase()]);
|
||||
break;
|
||||
case 'ctcp':
|
||||
let ctcpmsg = '';
|
||||
|
||||
if (data.arguments![1].toLowerCase() === 'ping') {
|
||||
ctcpmsg = 'PING ' + Math.floor(Date.now() / 1000);
|
||||
} else {
|
||||
ctcpmsg = data.message;
|
||||
}
|
||||
|
||||
output.push(['PRIVMSG %s :\x01%s\x01', data.arguments![0], ctcpmsg]);
|
||||
break;
|
||||
default:
|
||||
output.push([data.command.toUpperCase(), data.message]);
|
||||
}
|
||||
return output;
|
||||
}
|
67
src/utility/whois-parser.ts
Normal file
67
src/utility/whois-parser.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { IIRCLine } from '../types/irc.interfaces';
|
||||
|
||||
export interface WhoisResponse {
|
||||
nickname?: string;
|
||||
hostmask?: string;
|
||||
realname?: string;
|
||||
channels?: string[];
|
||||
connectingFrom?: string;
|
||||
usingModes?: string;
|
||||
server?: string;
|
||||
serverName?: string;
|
||||
title?: string;
|
||||
loggedInAs?: string;
|
||||
bot?: boolean;
|
||||
registered?: boolean;
|
||||
secure?: boolean;
|
||||
signOnTime?: number;
|
||||
idleSeconds?: number;
|
||||
}
|
||||
|
||||
export function parseWhois(lines: IIRCLine[]) {
|
||||
const data: WhoisResponse = {};
|
||||
|
||||
lines.forEach((line) => {
|
||||
switch (line.command) {
|
||||
case '311':
|
||||
data.nickname = line.arguments![1];
|
||||
data.hostmask = `${line.arguments![2]}@${line.arguments![3]}`;
|
||||
data.realname = line.trailing || '';
|
||||
break;
|
||||
case '319':
|
||||
data.channels = line.trailing?.split(' ');
|
||||
break;
|
||||
case '378':
|
||||
data.connectingFrom = line.trailing;
|
||||
break;
|
||||
case '379':
|
||||
data.usingModes = line.trailing;
|
||||
break;
|
||||
case '312':
|
||||
data.server = line.arguments![2];
|
||||
data.serverName = line.trailing || '';
|
||||
break;
|
||||
case '313':
|
||||
data.title = line.trailing;
|
||||
break;
|
||||
case '330':
|
||||
data.loggedInAs = line.arguments![2];
|
||||
break;
|
||||
case '335':
|
||||
data.bot = true;
|
||||
break;
|
||||
case '307':
|
||||
data.registered = true;
|
||||
break;
|
||||
case '671':
|
||||
data.secure = true;
|
||||
break;
|
||||
case '317':
|
||||
data.signOnTime = parseInt(line.arguments![3], 10);
|
||||
data.idleSeconds = parseInt(line.arguments![2], 10);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
"module": "commonjs" /* Specify what module code is generated. */,
|
||||
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
|
||||
"outDir": "./lib" /* Specify an output folder for all emitted files. */,
|
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user