rename classes, add irc connection interface

This commit is contained in:
Evert Prants 2022-09-25 09:22:01 +03:00
parent a5c7c22d54
commit d32ae78c56
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
9 changed files with 142 additions and 95 deletions

View File

@ -9,7 +9,7 @@ Zero dependencies library for creating anything using the IRC protocol - bots, c
There is an example usage documented in [src/examples/connection-test.ts](https://gitlab.icynet.eu/IcyNetwork/irclib/-/blob/master/src/examples/connection-test.ts), but basically you have two ways to create an IRC client connection: There is an example usage documented in [src/examples/connection-test.ts](https://gitlab.icynet.eu/IcyNetwork/irclib/-/blob/master/src/examples/connection-test.ts), but basically you have two ways to create an IRC client connection:
1. Use the `IRCBot` class. This just takes connection options and it uses `IRCSocketConnector` (so it will not work in the browser!) 1. Use the `IRCBot` class. This just takes connection options and it uses `IRCSocketConnector` (so it will not work in the browser!)
2. Use the `IRCConnectionWrapper` class directly. To use this, you need to provide your own connector in addition to the options. 2. Use the `IRCConnection` class directly. To use this, you need to provide your own connector in addition to the options.
### Connection options ### Connection options

View File

@ -1,22 +1,30 @@
import { IRCSocketConnector } from './connector/net.connector'; import { IRCSocketConnector } from './connector/net.connector';
import { IRCConnectionWrapper } from './irc'; import { IRCConnection } from './irc';
import { IIRCOptions } from './types/irc.interfaces'; import { IIRCOptions } from './types/irc.interfaces';
import { formatstr } from './utility/formatstr'; import { formatstr } from './utility/formatstr';
export class IRCBot extends IRCConnectionWrapper { export class IRCBot extends IRCConnection {
constructor(options: IIRCOptions) { constructor(options: IIRCOptions) {
super(options, IRCSocketConnector); super(options, IRCSocketConnector);
} }
/**
* Send a message to the server (`PRIVMSG`)
* @param to Recipient, a channel or a nick
* @param message Message to send
* @param args Additional arguments for message
*/
send(to: string, message: string, ...args: any[]) { send(to: string, message: string, ...args: any[]) {
this.write('PRIVMSG %s :%s', to, formatstr(message, ...args)); this.write('PRIVMSG %s :%s', to, formatstr(message, ...args));
} }
/**
* Send a message to the server (`NOTICE`)
* @param to Recipient, a channel or a nick
* @param message Message to send
* @param args Additional arguments for message
*/
notice(to: string, message: string, ...args: any[]) { notice(to: string, message: string, ...args: any[]) {
this.write('NOTICE %s :%s', to, formatstr(message, ...args)); this.write('NOTICE %s :%s', to, formatstr(message, ...args));
} }
nick(newNick: string) {
this.write('NICK %s', newNick);
}
} }

View File

@ -1,13 +1,13 @@
import net, { Socket } from 'net'; import net, { Socket } from 'net';
import tls, { TLSSocket } from 'tls'; import tls, { TLSSocket } from 'tls';
import { ConnectorEvents } from '../types/events'; import { ConnectorEvents } from '../types/events';
import { IRCConnector } from '../types/impl.interface'; import { IIRCConnector } from '../types/impl.interface';
import { formatstr } from '../utility/formatstr'; import { formatstr } from '../utility/formatstr';
import { TypedEventEmitter } from '../utility/typed-event-emitter'; import { TypedEventEmitter } from '../utility/typed-event-emitter';
export class IRCSocketConnector export class IRCSocketConnector
extends TypedEventEmitter<ConnectorEvents> extends TypedEventEmitter<ConnectorEvents>
implements IRCConnector implements IIRCConnector
{ {
connected = false; connected = false;
private socket?: Socket | TLSSocket; private socket?: Socket | TLSSocket;

View File

@ -1,11 +1,11 @@
import { ConnectorEvents } from '../types/events'; import { ConnectorEvents } from '../types/events';
import { IRCConnector } from '../types/impl.interface'; import { IIRCConnector } from '../types/impl.interface';
import { formatstr } from '../utility/formatstr'; import { formatstr } from '../utility/formatstr';
import { TypedEventEmitter } from '../utility/typed-event-emitter'; import { TypedEventEmitter } from '../utility/typed-event-emitter';
export class IRCWebSocketConnector export class IRCWebSocketConnector
extends TypedEventEmitter<ConnectorEvents> extends TypedEventEmitter<ConnectorEvents>
implements IRCConnector implements IIRCConnector
{ {
connected = false; connected = false;
private socket?: WebSocket; private socket?: WebSocket;

View File

@ -1,8 +1,9 @@
import { IRCCommunicatorEvents } from './types/events'; import { IRCCommunicatorEvents } from './types/events';
import { import {
IRCCommunicator, IWritableEventEmitter,
IRCConnector, IIRCConnector,
IRCConnectorConstructor, IIRCConnectorConstructor,
IIRCWrapper,
} from './types/impl.interface'; } from './types/impl.interface';
import { import {
IIRCLine, IIRCLine,
@ -22,42 +23,27 @@ import { TypedEventEmitter } from './utility/typed-event-emitter';
import { parseWho, WhoResponse } from './utility/who-parser'; import { parseWho, WhoResponse } from './utility/who-parser';
import { parseWhois, WhoisResponse } from './utility/whois-parser'; import { parseWhois, WhoisResponse } from './utility/whois-parser';
export class IRCConnectionWrapper export class IRCConnection
extends TypedEventEmitter<IRCCommunicatorEvents> extends TypedEventEmitter<IRCCommunicatorEvents>
implements IRCCommunicator implements IIRCWrapper
{ {
/**
* Channels the bot is currently in.
*/
public channels: string[] = []; public channels: string[] = [];
/**
* Current collectors waiting for their reply.
*/
public queue: IQueue[] = []; public queue: IQueue[] = [];
/**
* Login success status.
*/
public authenticated = false; public authenticated = false;
/**
* Information about the IRC server gathered during runtime
*/
public serverData: IIRCServerData = { public serverData: IIRCServerData = {
name: '', name: '',
supportedModes: {}, supportedModes: {},
serverSupports: {}, serverSupports: {},
}; };
private connection?: IRCConnector; private connection?: IIRCConnector;
private _supportsDone = false; private _supportsDone = false;
private _lastLineWasSupports = false; private _lastLineWasSupports = false;
constructor( constructor(
public options: IIRCOptions, public options: IIRCOptions,
public connector: IRCConnectorConstructor, public connector: IIRCConnectorConstructor,
) { ) {
super(); super();
if (!options.username) { if (!options.username) {
@ -66,14 +52,6 @@ export class IRCConnectionWrapper
this.handlers(); this.handlers();
} }
/**
* Send a raw command to the server.
*
* **WARNING:** Line break characters could have an unintended side-effect!
* Filter user-generated content!
* @param format Command
* @param args Command arguments
*/
write(format: string, ...args: any[]): void { write(format: string, ...args: any[]): void {
this.connection?.write(format, ...args); this.connection?.write(format, ...args);
} }
@ -487,9 +465,6 @@ export class IRCConnectionWrapper
} }
} }
/**
* Create a new connection to the configured IRC server.
*/
public async connect(): Promise<void> { public async connect(): Promise<void> {
if (this.connection) { if (this.connection) {
await this.connection.destroy(); await this.connection.destroy();
@ -529,10 +504,6 @@ export class IRCConnectionWrapper
this.authenticate(); this.authenticate();
} }
/**
* Disconnect from the IRC server gracefully, sends `QUIT`.
* @param reason Reason for disconnection
*/
public async disconnect(reason?: string): Promise<void> { public async disconnect(reason?: string): Promise<void> {
if (!this.connected) { if (!this.connected) {
if (this.connection) { if (this.connection) {
@ -554,18 +525,10 @@ export class IRCConnectionWrapper
}); });
} }
/**
* Get connection status. `authenticated` is a more reliable indicator
* of a successful connection.
*/
public get connected() { public get connected() {
return this.connection?.connected ?? false; return this.connection?.connected ?? false;
} }
/**
* Set a new nickname for self.
* @param newNick New nickname
*/
public setNick(newNick: string): void { public setNick(newNick: string): void {
this.write('NICK %s', newNick); this.write('NICK %s', newNick);
this.emit('nick', { this.emit('nick', {
@ -575,10 +538,6 @@ export class IRCConnectionWrapper
this.options.nick = newNick; this.options.nick = newNick;
} }
/**
* Asynchronously ping the server.
* @returns Ping in milliseconds
*/
public async getPing(): Promise<number> { public async getPing(): Promise<number> {
return new Promise<number>((resolve) => { return new Promise<number>((resolve) => {
const sendTime = Date.now(); const sendTime = Date.now();
@ -593,11 +552,6 @@ 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(
@ -608,11 +562,6 @@ 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[]> { public async who(target: string): Promise<WhoResponse[]> {
return new Promise((resolve) => { return new Promise((resolve) => {
this.useCollector( this.useCollector(
@ -623,11 +572,6 @@ export class IRCConnectionWrapper
}); });
} }
/**
* 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[]> { public async names(channel: string): Promise<string[]> {
return new Promise((resolve) => { return new Promise((resolve) => {
this.useCollector( this.useCollector(
@ -646,10 +590,6 @@ export class IRCConnectionWrapper
}); });
} }
/**
* Get a list of channels asynchronously
* @returns Channel list in `[<channel>, <# visible>, <topic>][]` format
*/
public async list(): Promise<string[][]> { public async list(): Promise<string[][]> {
return new Promise((resolve) => { return new Promise((resolve) => {
this.useCollector( this.useCollector(
@ -669,14 +609,6 @@ export class IRCConnectionWrapper
}); });
} }
/**
* Add a collector to the queue.
* This is used to grab lines from the server, wait for them
* and return them in a callback.
* @param collector IRC line collector
* @param line Command to send to the server
* @param args Arguments to the command
*/
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);

View File

@ -1,20 +1,124 @@
export interface IRCCommunicator { import { WhoResponse } from '../utility/who-parser';
import { WhoisResponse } from '../utility/whois-parser';
import { IIRCOptions, IIRCServerData, IQueue } from './irc.interfaces';
export interface IWritableEventEmitter {
emit(event: string, ...args: any[]): void; emit(event: string, ...args: any[]): void;
on(event: string, handler: (...args: any[]) => void): void; on(event: string, handler: (...args: any[]) => void): void;
write(format: string, ...args: any[]): void; write(format: string, ...args: any[]): void;
} }
export interface IRCConnector extends IRCCommunicator { export interface IIRCConnector extends IWritableEventEmitter {
/**
* Current connection status.
*/
connected: boolean; connected: boolean;
/**
* Connect the socket to the server.
*/
connect(): Promise<void>; connect(): Promise<void>;
/**
* Forcefully disconnect the socket from the server.
*/
destroy(): Promise<void>; destroy(): Promise<void>;
} }
export interface IRCConnectorConstructor { export interface IIRCWrapper extends IWritableEventEmitter {
/**
* Connection options for the IRC server.
*/
options: IIRCOptions;
/**
* The connector to use for connecting to the IRC server.
*/
connector: IIRCConnectorConstructor;
/**
* Get connection status. `authenticated` is a more reliable indicator
* of a successful connection.
*/
connected: boolean;
/**
* Channels the bot is currently in.
*/
channels: string[];
/**
* Current collectors waiting for their reply.
*/
queue: IQueue[];
/**
* Login success status.
*/
authenticated: boolean;
/**
* Information about the IRC server gathered during runtime
*/
serverData: IIRCServerData;
/**
* Send a raw command to the server.
*
* **WARNING:** Line break characters could have an unintended side-effect!
* Filter user-generated content!
* @param format Command
* @param args Command arguments
*/
write(format: string, ...args: any[]): void;
/**
* Create a new connection to the configured IRC server.
*/
connect(): Promise<void>;
/**
* Disconnect from the IRC server gracefully, sends `QUIT`.
* @param reason Reason for disconnection
*/
disconnect(reason?: string): Promise<void>;
/**
* Asynchronously ping the server.
* @returns Ping in milliseconds
*/
getPing(): Promise<number>;
/**
* Asynchronous WHOIS query on `nick`
* @param nick Nick to query
* @returns Parsed WHOIS response object
*/
whois(nick: string): Promise<WhoisResponse>;
/**
* Asynchronous WHO query on `target`
* @param target Channel or nick to query
* @returns Parsed WHO response object array
*/
who(target: string): Promise<WhoResponse[]>;
/**
* Get a list of names in a channel asynchronously
* @param channel Channel to query
* @returns String list of nicks (with mode prefixes preserved)
*/
names(channel: string): Promise<string[]>;
/**
* Get a list of channels asynchronously
* @returns Channel list in `[<channel>, <# visible>, <topic>][]` format
*/
list(): Promise<string[][]>;
/**
* Add a collector to the queue.
* This is used to grab lines from the server, wait for them
* and return them in a callback.
* @param collector IRC line collector
* @param line Command to send to the server
* @param args Arguments to the command
*/
useCollector(collector: IQueue, line: string, ...args: any[]): void;
}
export interface IIRCConnectorConstructor {
new ( new (
secure: boolean, secure: boolean,
host: string, host: string,
port?: number, port?: number,
opts?: Record<string, unknown>, opts?: Record<string, unknown>,
): IRCConnector; ): IIRCConnector;
}
export interface IIRCConnetionConstructor {
new (options: IIRCOptions, connector: IIRCConnectorConstructor): IIRCWrapper;
} }

View File

@ -161,5 +161,8 @@ export interface IIRCServerData {
* Supported channel user modes from the server (e.g. `ohv: @%+`) * Supported channel user modes from the server (e.g. `ohv: @%+`)
*/ */
supportedModes: Record<string, string>; supportedModes: Record<string, string>;
/**
* Name of the IRC network. May not be present.
*/
network?: string; network?: string;
} }

View File

@ -1,4 +1,4 @@
import { IRCConnectionWrapper } from '../../irc'; import { IIRCWrapper } from '../../types/impl.interface';
import { modeFromPrefix } from '../mode-from-prefix'; import { modeFromPrefix } from '../mode-from-prefix';
import { TypedEventEmitter } from '../typed-event-emitter'; import { TypedEventEmitter } from '../typed-event-emitter';
import { WhoResponse } from '../who-parser'; import { WhoResponse } from '../who-parser';
@ -11,7 +11,7 @@ import { INicklistChannel } from './nicklist.interfaces';
export class IRCNickList extends TypedEventEmitter<NickListEvents> { export class IRCNickList extends TypedEventEmitter<NickListEvents> {
public channels: INicklistChannel[] = []; public channels: INicklistChannel[] = [];
constructor(public irc: IRCConnectionWrapper) { constructor(public irc: IIRCWrapper) {
super(); super();
this.handlers(); this.handlers();
} }

View File

@ -1,4 +1,4 @@
import { IRCConnectionWrapper } from '../irc'; import { IRCConnection } from '../irc';
import { INickServOptions } from '../types/irc.interfaces'; import { INickServOptions } from '../types/irc.interfaces';
import { Collector } from './collector'; import { Collector } from './collector';
@ -35,7 +35,7 @@ export class NickServCollector extends Collector {
export class NickServValidator { export class NickServValidator {
public nickservStore: { [key: string]: INickStore } = {}; public nickservStore: { [key: string]: INickStore } = {};
constructor(public irc: IRCConnectionWrapper) { constructor(public irc: IRCConnection) {
this.irc.on('leave', ({ nickname }) => { this.irc.on('leave', ({ nickname }) => {
if (this.nickservStore[nickname]) { if (this.nickservStore[nickname]) {
delete this.nickservStore[nickname]; delete this.nickservStore[nickname];