From d32ae78c560432f12532bc117c3ca7e39793c2ac Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sun, 25 Sep 2022 09:22:01 +0300 Subject: [PATCH] rename classes, add irc connection interface --- README.md | 2 +- src/bot.ts | 20 +++-- src/connector/net.connector.ts | 4 +- src/connector/websocket.connector.ts | 4 +- src/irc.ts | 84 ++------------------ src/types/impl.interface.ts | 112 ++++++++++++++++++++++++++- src/types/irc.interfaces.ts | 3 + src/utility/nicklist/nicklist.ts | 4 +- src/utility/nickserv-validator.ts | 4 +- 9 files changed, 142 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 83e3d59..aacf967 100644 --- a/README.md +++ b/README.md @@ -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: 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 diff --git a/src/bot.ts b/src/bot.ts index 5bd14a9..d66e26e 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,22 +1,30 @@ import { IRCSocketConnector } from './connector/net.connector'; -import { IRCConnectionWrapper } from './irc'; +import { IRCConnection } from './irc'; import { IIRCOptions } from './types/irc.interfaces'; import { formatstr } from './utility/formatstr'; -export class IRCBot extends IRCConnectionWrapper { +export class IRCBot extends IRCConnection { constructor(options: IIRCOptions) { 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[]) { 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[]) { this.write('NOTICE %s :%s', to, formatstr(message, ...args)); } - - nick(newNick: string) { - this.write('NICK %s', newNick); - } } diff --git a/src/connector/net.connector.ts b/src/connector/net.connector.ts index cb56e70..4417d88 100644 --- a/src/connector/net.connector.ts +++ b/src/connector/net.connector.ts @@ -1,13 +1,13 @@ import net, { Socket } from 'net'; import tls, { TLSSocket } from 'tls'; import { ConnectorEvents } from '../types/events'; -import { IRCConnector } from '../types/impl.interface'; +import { IIRCConnector } from '../types/impl.interface'; import { formatstr } from '../utility/formatstr'; import { TypedEventEmitter } from '../utility/typed-event-emitter'; export class IRCSocketConnector extends TypedEventEmitter - implements IRCConnector + implements IIRCConnector { connected = false; private socket?: Socket | TLSSocket; diff --git a/src/connector/websocket.connector.ts b/src/connector/websocket.connector.ts index 08fc816..fa8b965 100644 --- a/src/connector/websocket.connector.ts +++ b/src/connector/websocket.connector.ts @@ -1,11 +1,11 @@ import { ConnectorEvents } from '../types/events'; -import { IRCConnector } from '../types/impl.interface'; +import { IIRCConnector } from '../types/impl.interface'; import { formatstr } from '../utility/formatstr'; import { TypedEventEmitter } from '../utility/typed-event-emitter'; export class IRCWebSocketConnector extends TypedEventEmitter - implements IRCConnector + implements IIRCConnector { connected = false; private socket?: WebSocket; diff --git a/src/irc.ts b/src/irc.ts index 2e4cda0..5955db0 100644 --- a/src/irc.ts +++ b/src/irc.ts @@ -1,8 +1,9 @@ import { IRCCommunicatorEvents } from './types/events'; import { - IRCCommunicator, - IRCConnector, - IRCConnectorConstructor, + IWritableEventEmitter, + IIRCConnector, + IIRCConnectorConstructor, + IIRCWrapper, } from './types/impl.interface'; import { IIRCLine, @@ -22,42 +23,27 @@ import { TypedEventEmitter } from './utility/typed-event-emitter'; import { parseWho, WhoResponse } from './utility/who-parser'; import { parseWhois, WhoisResponse } from './utility/whois-parser'; -export class IRCConnectionWrapper +export class IRCConnection extends TypedEventEmitter - implements IRCCommunicator + implements IIRCWrapper { - /** - * Channels the bot is currently in. - */ public channels: string[] = []; - - /** - * Current collectors waiting for their reply. - */ public queue: IQueue[] = []; - - /** - * Login success status. - */ public authenticated = false; - - /** - * Information about the IRC server gathered during runtime - */ public serverData: IIRCServerData = { name: '', supportedModes: {}, serverSupports: {}, }; - private connection?: IRCConnector; + private connection?: IIRCConnector; private _supportsDone = false; private _lastLineWasSupports = false; constructor( public options: IIRCOptions, - public connector: IRCConnectorConstructor, + public connector: IIRCConnectorConstructor, ) { super(); if (!options.username) { @@ -66,14 +52,6 @@ export class IRCConnectionWrapper 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 { 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 { if (this.connection) { await this.connection.destroy(); @@ -529,10 +504,6 @@ export class IRCConnectionWrapper this.authenticate(); } - /** - * Disconnect from the IRC server gracefully, sends `QUIT`. - * @param reason Reason for disconnection - */ public async disconnect(reason?: string): Promise { if (!this.connected) { 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() { return this.connection?.connected ?? false; } - /** - * Set a new nickname for self. - * @param newNick New nickname - */ public setNick(newNick: string): void { this.write('NICK %s', newNick); this.emit('nick', { @@ -575,10 +538,6 @@ export class IRCConnectionWrapper this.options.nick = newNick; } - /** - * Asynchronously ping the server. - * @returns Ping in milliseconds - */ public async getPing(): Promise { return new Promise((resolve) => { 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 { return new Promise((resolve) => { 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 { return new Promise((resolve) => { 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 { return new Promise((resolve) => { this.useCollector( @@ -646,10 +590,6 @@ export class IRCConnectionWrapper }); } - /** - * Get a list of channels asynchronously - * @returns Channel list in `[, <# visible>, ][]` format - */ public async list(): Promise { return new Promise((resolve) => { 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[]) { this.queue.push(collector); this.write(line, ...args); diff --git a/src/types/impl.interface.ts b/src/types/impl.interface.ts index 06a1967..c190c9d 100644 --- a/src/types/impl.interface.ts +++ b/src/types/impl.interface.ts @@ -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; on(event: string, handler: (...args: any[]) => void): void; write(format: string, ...args: any[]): void; } -export interface IRCConnector extends IRCCommunicator { +export interface IIRCConnector extends IWritableEventEmitter { + /** + * Current connection status. + */ connected: boolean; + /** + * Connect the socket to the server. + */ connect(): Promise; + /** + * Forcefully disconnect the socket from the server. + */ destroy(): Promise; } -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; + /** + * Disconnect from the IRC server gracefully, sends `QUIT`. + * @param reason Reason for disconnection + */ + disconnect(reason?: string): Promise; + /** + * Asynchronously ping the server. + * @returns Ping in milliseconds + */ + getPing(): Promise; + /** + * Asynchronous WHOIS query on `nick` + * @param nick Nick to query + * @returns Parsed WHOIS response object + */ + whois(nick: string): Promise; + /** + * Asynchronous WHO query on `target` + * @param target Channel or nick to query + * @returns Parsed WHO response object array + */ + who(target: string): Promise; + /** + * 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; + /** + * Get a list of channels asynchronously + * @returns Channel list in `[, <# visible>, ][]` format + */ + list(): Promise; + /** + * 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 ( secure: boolean, host: string, port?: number, opts?: Record, - ): IRCConnector; + ): IIRCConnector; +} + +export interface IIRCConnetionConstructor { + new (options: IIRCOptions, connector: IIRCConnectorConstructor): IIRCWrapper; } diff --git a/src/types/irc.interfaces.ts b/src/types/irc.interfaces.ts index cfc9456..744139c 100644 --- a/src/types/irc.interfaces.ts +++ b/src/types/irc.interfaces.ts @@ -161,5 +161,8 @@ export interface IIRCServerData { * Supported channel user modes from the server (e.g. `ohv: @%+`) */ supportedModes: Record; + /** + * Name of the IRC network. May not be present. + */ network?: string; } diff --git a/src/utility/nicklist/nicklist.ts b/src/utility/nicklist/nicklist.ts index 2e1250a..e2f85be 100644 --- a/src/utility/nicklist/nicklist.ts +++ b/src/utility/nicklist/nicklist.ts @@ -1,4 +1,4 @@ -import { IRCConnectionWrapper } from '../../irc'; +import { IIRCWrapper } from '../../types/impl.interface'; import { modeFromPrefix } from '../mode-from-prefix'; import { TypedEventEmitter } from '../typed-event-emitter'; import { WhoResponse } from '../who-parser'; @@ -11,7 +11,7 @@ import { INicklistChannel } from './nicklist.interfaces'; export class IRCNickList extends TypedEventEmitter { public channels: INicklistChannel[] = []; - constructor(public irc: IRCConnectionWrapper) { + constructor(public irc: IIRCWrapper) { super(); this.handlers(); } diff --git a/src/utility/nickserv-validator.ts b/src/utility/nickserv-validator.ts index 2a95326..6290979 100644 --- a/src/utility/nickserv-validator.ts +++ b/src/utility/nickserv-validator.ts @@ -1,4 +1,4 @@ -import { IRCConnectionWrapper } from '../irc'; +import { IRCConnection } from '../irc'; import { INickServOptions } from '../types/irc.interfaces'; import { Collector } from './collector'; @@ -35,7 +35,7 @@ export class NickServCollector extends Collector { export class NickServValidator { public nickservStore: { [key: string]: INickStore } = {}; - constructor(public irc: IRCConnectionWrapper) { + constructor(public irc: IRCConnection) { this.irc.on('leave', ({ nickname }) => { if (this.nickservStore[nickname]) { delete this.nickservStore[nickname];