From 9a2142688bdc4b691c955dd85279c52571055232 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sat, 2 Oct 2021 11:07:01 +0300 Subject: [PATCH] Documentation of most methods --- src/channel/index.ts | 45 ++++++++++++++++++++++ src/common/http.ts | 63 ++++++++++++++++++++++--------- src/common/sanitize.ts | 5 +++ src/common/time.ts | 30 ++++++++++++++- src/core/coreref.ts | 4 ++ src/core/environment.ts | 6 +++ src/core/logger.ts | 49 ++++++++++++++++++++++-- src/npm/executor.ts | 24 +++++++++++- src/plugin/config.ts | 8 ++++ src/plugin/loader.ts | 13 +++++++ src/plugin/manager.ts | 45 +++++++++++++++++++++- src/plugin/plugin.ts | 7 ++++ src/plugin/repository/manager.ts | 64 +++++++++++++++++++++++++++++++- src/types/config.ts | 39 +++++++++++++++++-- src/types/plugin-config.ts | 3 ++ src/types/protocol.ts | 3 ++ src/types/service.ts | 33 ++++++++++++++-- src/util/events.ts | 3 ++ src/util/index.ts | 5 +++ src/util/run.ts | 13 +++++++ 20 files changed, 427 insertions(+), 35 deletions(-) diff --git a/src/channel/index.ts b/src/channel/index.ts index 31064fa..e9c3391 100644 --- a/src/channel/index.ts +++ b/src/channel/index.ts @@ -8,11 +8,24 @@ export interface IChannel { enabled: boolean; } +/** + * This class is used to direct messages and events from one plugin to many others + * using a pre-set list of plugins that are allowed to talk to one another. + * + * Generally when creating a channel, the first plugin should be the source of messages + * or events, such as a protocol or other service, and the rest of the plugins in the + * list are the handlers. + */ export class ChannelManager { private channels: IChannel[] = []; constructor(private stream: ScopedEventEmitter) {} + /** + * Ensure that the message or event source is a plugin + * @param source Event source + * @returns Plugin or null + */ public static determinePlugin(source: any): IPlugin | null { if (source != null) { if (source.manifest) { @@ -27,6 +40,10 @@ export class ChannelManager { return null; } + /** + * Initialize the event handlers for channels + * @param configured Initial configuration of channels + */ public initialize(configured: IChannel[]): void { this.addPreconfiguredChannels(configured); @@ -61,6 +78,12 @@ export class ChannelManager { } } + /** + * Get all the channels a plugin is in + * @param plugin Plugin name + * @param source Source protocol of the event + * @returns List of channels to send to + */ private getChannelsByPluginName(plugin: string, source: Protocol): IChannel[] { const list = []; for (const chan of this.channels) { @@ -81,6 +104,10 @@ export class ChannelManager { return list; } + /** + * Validate a preconfigured channel list and add them to the list + * @param channels Preconfigured channel list + */ private addPreconfiguredChannels(channels: IChannel[]): void { for (const chan of channels) { if (!chan.name) { @@ -95,10 +122,20 @@ export class ChannelManager { } } + /** + * Get a channel by name + * @param name Channel name + * @returns Channel or undefined + */ public getChannelByName(name: string): IChannel | undefined { return this.channels.find(c => c.name === name); } + /** + * Add a new channel to the channels list + * @param chan Channel configuration + * @returns Channel + */ public addChannel(chan: IChannel): IChannel { const exists = this.getChannelByName(chan.name); if (exists) { @@ -108,6 +145,10 @@ export class ChannelManager { return chan; } + /** + * Remove a channel by name or the channel itself + * @param chan Name of channel or channel + */ public removeChannel(chan: string | IChannel): void { if (typeof chan === 'string') { const getchan = this.getChannelByName(chan); @@ -119,6 +160,10 @@ export class ChannelManager { this.channels.splice(this.channels.indexOf(chan), 1); } + /** + * Get all channels + * @returns All channels + */ public getAll(): IChannel[] { return this.channels; } diff --git a/src/common/http.ts b/src/common/http.ts index e07375f..adaac59 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -1,17 +1,24 @@ -import http from 'http'; +import http, { RequestOptions } from 'http'; import https from 'https'; -import qs from 'querystring'; import fs from 'fs-extra'; -import url from 'url'; +import { URL } from 'url'; +/** + * Create an HTTP GET request. + * @param link Request URL + * @param headers Request headers + * @param restrictToText Only allow textual responses + * @param saveTo Save to a file + * @returns Response data + */ export function httpGET( link: string, headers: any = {}, restrictToText = true, saveTo?: string, - lback?: number): Promise { - - const parsed = url.parse(link); + lback?: number +): Promise { + const parsed = new URL(link); const opts = { headers: { Accept: '*/*', @@ -19,8 +26,8 @@ export function httpGET( 'User-Agent': 'Squeebot/Commons-3.0.0', }, host: parsed.hostname, - path: parsed.path, - port: parsed.port, + path: `${parsed.pathname}${parsed.search}`, + port: parsed.port || null, }; if (headers) { @@ -63,13 +70,13 @@ export function httpGET( if (restrictToText) { const cType = res.headers['content-type']; if (cType && cType.indexOf('text') === -1 && cType.indexOf('json') === -1) { - req.abort(); + req.destroy(); return reject(new Error('Response type is not supported by httpGET!')); } } reqTimeOut = setTimeout(() => { - req.abort(); + req.destroy(); data = null; reject(new Error('Request took too long!')); }, 5000); @@ -91,33 +98,53 @@ export function httpGET( }); } +/** + * Create an HTTP POST request. + * + * Note: Content-Type defaults to `application/x-www-form-urlencoded`. + * Set the `Content-Type` to `application/json` to post and parse JSON. + * @param link Request URL + * @param headers Request headers + * @param data Submit data + * @returns Response data + */ export function httpPOST( link: string, headers: any = {}, - data: any): Promise { - const parsed = url.parse(link); - let postData = qs.stringify(data); + data: any +): Promise { + const parsed = new URL(link); + let postData: string | URLSearchParams = new URLSearchParams(data); - const opts = { + const opts: RequestOptions = { headers: { - 'Content-Length': Buffer.byteLength(postData), 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Squeebot/Commons-3.0.0', }, host: parsed.host, method: 'POST', - path: parsed.path, - port: parsed.port, + path: `${parsed.pathname}${parsed.search}`, + port: parsed.port || null, }; + // Assign provided headers if (headers) { - opts.headers = Object.assign(opts.headers, headers); + opts.headers = Object.assign({}, opts.headers, headers); } + // Ensure headers list exists + if (!opts.headers) { + opts.headers = {}; + } + + // If content type is JSON, add it to body if (opts.headers['Content-Type'] === 'application/json') { postData = JSON.stringify(data); } + // Set content length accordingly + opts.headers['Content-Length'] = Buffer.byteLength(postData.toString()); + return new Promise((resolve, reject) => { const httpModule = parsed.protocol === 'https:' ? https : http; const req = httpModule.request(opts, (res) => { diff --git a/src/common/sanitize.ts b/src/common/sanitize.ts index 19f23a9..99bc37c 100644 --- a/src/common/sanitize.ts +++ b/src/common/sanitize.ts @@ -1,4 +1,9 @@ +/** + * Remove escaped HTML entities from string. + * @param text HTML escaped string + * @returns Un-escaped string + */ export function sanitizeEscapedText(text: string): string { return text.replace(/\n/g, ' ') .replace(/&/g, '&') diff --git a/src/common/time.ts b/src/common/time.ts index c20bf03..de9218a 100644 --- a/src/common/time.ts +++ b/src/common/time.ts @@ -1,4 +1,9 @@ +/** + * Convert seconds to HH:MM:SS format + * @param input seconds + * @returns string in HH:MM:SS format + */ export function toHHMMSS(input: string | number): string { const secNum = parseInt(input.toString(), 10); let hours: string | number = Math.floor(secNum / 3600); @@ -27,12 +32,18 @@ export function toHHMMSS(input: string | number): string { return time; } -// Add a zero in front of single-digit numbers +/** + * Add a zero in front of single-digit numbers + */ function zf(v: number): string { return v < 9 ? '0' + v : '' + v; } -// Convert seconds into years days hours minutes seconds(.milliseconds) +/** + * Convert seconds into years days hours minutes seconds + * @param timems time in seconds + * @returns y d h m s string + */ export function readableTime(timems: number): string { const time = Math.floor(timems); @@ -59,6 +70,11 @@ export function readableTime(timems: number): string { } } +/** + * Convert y d h m s string to seconds + * @param input y d h m s string + * @returns seconds + */ export function parseTimeToSeconds(input: string): number { let seconds = 0; let match; @@ -101,6 +117,11 @@ export function parseTimeToSeconds(input: string): number { return seconds; } +/** + * Add a comma to separate thousands in number + * @param input number (example: 1000000) + * @returns string (example: 1,000,000) + */ export function thousandsSeparator(input: number | string): string { const nStr = input.toString(); const x = nStr.split('.'); @@ -115,6 +136,11 @@ export function thousandsSeparator(input: number | string): string { return x1 + x2; } +/** + * Time since a unix timestamp + * @param date unix timestamp + * @returns x years, months, days, hours, minutes, seconds + */ export function timeSince(date: number): string { const seconds = Math.floor((Date.now() - date) / 1000); let interval = Math.floor(seconds / 31536000); diff --git a/src/core/coreref.ts b/src/core/coreref.ts index 68f9380..67e53fc 100644 --- a/src/core/coreref.ts +++ b/src/core/coreref.ts @@ -5,6 +5,10 @@ import { RepositoryManager } from '../plugin/repository'; import { Configuration, IEnvironment } from '../types'; import { ScopedEventEmitter } from '../util'; +/** + * Reference of a fully featured Squeebot core. + * Recommended implementation of a squeebot runner implements this interface. + */ export interface ISqueebotCore { environment: IEnvironment; npm: NPMExecutor; diff --git a/src/core/environment.ts b/src/core/environment.ts index a6657e9..212b1b9 100644 --- a/src/core/environment.ts +++ b/src/core/environment.ts @@ -9,6 +9,12 @@ const dirs: {[key: string]: string} = { repositoryPath: 'repos', }; +/** + * Load a Squeebot environment from a file + * @param enviroFile Environment JSON file + * @param chroot Change bot root to this instead of the path in the environment + * @returns Squeebot environment + */ export async function loadEnvironment(enviroFile: string = 'squeebot.env.json', chroot?: string): Promise { if (!await fs.pathExists(enviroFile)) { throw new Error('Environment file does not exist.'); diff --git a/src/core/logger.ts b/src/core/logger.ts index 346ae32..c669043 100644 --- a/src/core/logger.ts +++ b/src/core/logger.ts @@ -1,23 +1,39 @@ import dateFmt from 'dateformat'; import util from 'util'; +type LogType = 'info' | 'debug' | 'warn' | 'error'; + +/** + * Logger for all of Squeebot. Use this instead of console.log/warn/error! + */ export class Logger { - public timestamp = 'dd/mm/yy HH:MM:ss'; private console = [console.log, console.warn, console.error]; - constructor() {} + constructor( + public timestamp = 'dd/mm/yy HH:MM:ss' + ) {} + /** + * Set node.js readline consideration + * @param rl Readline instance + */ public setReadline(rl: any): void { for (const index in this.console) { const old = this.console[index]; this.console[index] = (...data: any[]): void => { rl.output.write('\x1b[2K\r'); old.apply(null, data); + rl.prompt(true); }; } } - private write(ltype: string, ...data: any[]): void { + /** + * Write out to log + * @param ltype Logger level + * @param data Data to log + */ + private write(ltype: LogType, ...data: any[]): void { const message = []; let cfunc = this.console[0]; @@ -52,27 +68,52 @@ export class Logger { cfunc.apply(null, message); } + /** + * Logger level: `INFO` + * + * See `console.log` for more information. + */ public log(...data: any[]): void { this.write('info', ...data); } + /** + * Logger level: `WARN` + * + * See `console.warn` for more information. + */ public warn(...data: any[]): void { this.write('warn', ...data); } + /** + * Logger level: `INFO` + * + * See `console.log` for more information. + */ public info(...data: any[]): void { this.write('info', ...data); } + /** + * Logger level: `ERROR` + * + * See `console.error` for more information. + */ public error(...data: any[]): void { this.write('error', ...data); } + /** + * Logger level: `DEBUG` + * + * See `console.log` for more information. + */ public debug(...data: any[]): void { this.write('debug', ...data); } } +// Create singleton for Logger to be used anywhere const logger = new Logger(); - export { logger }; diff --git a/src/npm/executor.ts b/src/npm/executor.ts index 02e25f9..9b753d8 100644 --- a/src/npm/executor.ts +++ b/src/npm/executor.ts @@ -4,12 +4,21 @@ import semver from 'semver'; import { IEnvironment } from '../types/environment'; import { spawnProcess } from '../util/run'; +/** + * Execute NPM commands + */ export class NPMExecutor { private installed: Record = {}; private packageFile: string = path.join(this.environment.path, 'package.json'); - constructor(private environment: IEnvironment, private coreModule: string) {} + constructor( + private environment: IEnvironment, + private coreModule: string + ) {} + /** + * Create a package.json file and install the core. + */ public async init(): Promise { // Initialize npm environment const c1 = await spawnProcess('npm', ['init', '-y'], this.environment); @@ -24,6 +33,9 @@ export class NPMExecutor { } } + /** + * Load a package.json file into memory + */ public async loadPackageFile(): Promise { if (!await fs.pathExists(this.packageFile)) { await this.init(); @@ -37,6 +49,10 @@ export class NPMExecutor { this.installed = jsonData.dependencies; } + /** + * Install a npm package (examples: `@squeebot/core`, `@squeebot/core@3.3.3`, `node-ical`) + * @param pkg Package name + */ public async installPackage(pkg: string): Promise { if (!await fs.pathExists(this.packageFile)) { await this.init(); @@ -66,6 +82,12 @@ export class NPMExecutor { await this.loadPackageFile(); } + /** + * Uninstall a npm package. + * + * See `installPackage` for more info. + * @param pkg Package name + */ public async uninstallPackage(pkg: string): Promise { if (!await fs.pathExists(this.packageFile)) { await this.init(); diff --git a/src/plugin/config.ts b/src/plugin/config.ts index 03833e2..5cf8c60 100644 --- a/src/plugin/config.ts +++ b/src/plugin/config.ts @@ -2,11 +2,19 @@ import { IEnvironment } from '../types/environment'; import { PluginConfiguration } from '../types/plugin-config'; import { IPluginManifest } from './plugin'; +/** + * Plugin configuration memory storage + */ export class PluginConfigurator { private configs: Map = new Map(); constructor(private env: IEnvironment) {} + /** + * Load a plugin configuration file by plugin manifest. + * @param mf Plugin manifest + * @returns Plugin's configuration object + */ public async loadConfig(mf: IPluginManifest): Promise { if (!this.configs.has(mf.name)) { const conf = new PluginConfiguration(this.env, mf.name); diff --git a/src/plugin/loader.ts b/src/plugin/loader.ts index fb7c3f6..a445626 100644 --- a/src/plugin/loader.ts +++ b/src/plugin/loader.ts @@ -6,9 +6,17 @@ import { IPluginManifest } from './plugin'; import { logger } from '../core'; +/** + * Plugin manifest loader + */ export class PluginMetaLoader { constructor(private env: IEnvironment) {} + /** + * Load a plugin manifest by plugin name. + * @param name Plugin name + * @returns Plugin manifest + */ public async load(name: string): Promise { if (name === 'squeebot') { throw new Error('Illegal name.'); @@ -60,6 +68,11 @@ export class PluginMetaLoader { return json; } + /** + * Load all plugin manifests from files. + * @param ignoreErrors Ignore loading errors instead of throwing them + * @returns List of plugin manifests ready to be loaded + */ public async loadAll(ignoreErrors = false): Promise { const dirlist = await fs.readdir(this.env.pluginsPath); const plugins: IPluginManifest[] = []; diff --git a/src/plugin/manager.ts b/src/plugin/manager.ts index 1f7d912..7ea2771 100644 --- a/src/plugin/manager.ts +++ b/src/plugin/manager.ts @@ -11,6 +11,9 @@ import { NPMExecutor } from '../npm/executor'; import { logger } from '../core/logger'; +/** + * Plugin management and execution system + */ export class PluginManager { private plugins: Map = new Map(); private configs: PluginConfigurator = new PluginConfigurator(this.environment); @@ -21,14 +24,25 @@ export class PluginManager { public availablePlugins: IPluginManifest[], private stream: ScopedEventEmitter, private environment: IEnvironment, - private npm: NPMExecutor) { + private npm: NPMExecutor + ) { this.addEvents(); } + /** + * Get a plugin manifest by plugin name, if it is available. + * @param name Plugin name + * @returns Plugin manifest + */ public getAvailableByName(name: string): IPluginManifest | undefined { return this.availablePlugins.find(p => p.name === name); } + /** + * Get a loaded plugin by name. + * @param name Plugin name + * @returns Plugin instance + */ public getLoadedByName(name: string): IPlugin | undefined { if (this.plugins.has(name)) { return this.plugins.get(name) as IPlugin; @@ -36,10 +50,21 @@ export class PluginManager { return; } + /** + * Get a list of all loaded plugins. + * @returns List of loaded plugins + */ public getLoaded(): IPlugin[] { return Array.from(this.plugins.values()); } + /** + * Add plugin manifests as available (ready to load). + * + * Also used to replace existing manifest after installing a plugin update. + * @param manifest Plugin manifest or list of + * @returns true on success + */ public addAvailable(manifest: IPluginManifest | IPluginManifest[]): boolean { // Automatically add arrays of manifests if (Array.isArray(manifest)) { @@ -67,6 +92,12 @@ export class PluginManager { return true; } + /** + * Remove a plugin from available list, usually called after uninstalling a plugin. + * @param plugin Plugin name, plugin manifest or list of + * @param unload If they're loaded, unload them + * @returns true on success + */ public removeAvailable( plugin: IPluginManifest | IPluginManifest[] | string | string[], unload = true @@ -107,6 +138,11 @@ export class PluginManager { return returnValue; } + /** + * Load a plugin into memory and start executing it. + * @param plugin Plugin manifest + * @returns Plugin instance + */ public async load(plugin: IPluginManifest): Promise { // Ignore loading when we're shutting down if (this.stopping) { @@ -245,6 +281,10 @@ export class PluginManager { return loaded; } + /** + * Restart a loaded plugin. + * @param mf Plugin instance, plugin manifest or plugin name + */ public async restart(mf: IPluginManifest | IPlugin | string): Promise { let manifest; if (typeof mf === 'string') { @@ -268,6 +308,9 @@ export class PluginManager { this.stream.emitTo(manifest.name, 'pluginUnload', manifest.name); } + /** + * Listen for plugin status events. + */ private addEvents(): void { this.stream.on('core', 'pluginLoad', (mf: IPluginManifest | string) => { if (typeof mf === 'string') { diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index 2c794be..d8cd0a4 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -8,6 +8,9 @@ export interface IPlugin { service: Service | null; } +/** + * Base class for all plugins + */ export class Plugin implements IPlugin { public service: Service | null = null; protected on = this.addEventListener; @@ -17,6 +20,10 @@ export class Plugin implements IPlugin { public stream: ScopedEventEmitter, public config: PluginConfiguration) {} + /** + * Called when plugin first starts. + * Please use this instead of the constructor. + */ public initialize(): void {} public get name(): string { diff --git a/src/plugin/repository/manager.ts b/src/plugin/repository/manager.ts index 2aac0ab..28eb264 100644 --- a/src/plugin/repository/manager.ts +++ b/src/plugin/repository/manager.ts @@ -12,29 +12,60 @@ import { PluginManager } from '../manager'; import { IPlugin, IPluginManifest } from '../plugin'; import { IRepoPluginDef, IRepository } from './repository'; +/** + * Plugin repository manager + */ export class RepositoryManager { private repositories: Map = new Map(); constructor(private env: IEnvironment, private plugins: PluginManager) {} - public repoProvidesPlugin(repo: IRepository, mf: IPluginManifest): IRepoPluginDef | undefined { + /** + * Determine if this repository provides a specific plugin + * @param repo Repository manifest + * @param mf Plugin manifest + * @returns Repository plugin definition + */ + public repoProvidesPlugin( + repo: IRepository, + mf: IPluginManifest + ): IRepoPluginDef | undefined { return repo.plugins.find(plugin => plugin.name === mf.name && repo.name === mf.repository); } + /** + * Find repository providing plugin + * @param pname Plugin name + * @returns Repository manifest + */ public findRepoForPlugin(pname: string): IRepository | undefined { return Array.from(this.repositories.values()).find(repo => repo.plugins.find(plugin => plugin.name === pname) !== undefined ); } + /** + * Find a repository by name + * @param name Repository name + * @returns Repository manifest + */ public getRepoByName(name: string): IRepository | undefined { return this.repositories.get(name); } + /** + * Get the list of all installed repositories + * @returns All repositories + */ public getAll(): IRepository[] { return Array.from(this.repositories.values()); } + /** + * Install a plugin by name + * @param name Plugin name + * @returns Plugin manifest + */ public async installPlugin(name: string): Promise { let repo; if (name.indexOf('/') !== -1) { @@ -94,6 +125,10 @@ export class RepositoryManager { return manifest; } + /** + * Uninstall a plugin + * @param plugin Plugin name, manifest or object + */ public async uninstallPlugin(plugin: string | IPluginManifest | IPlugin): Promise { let realName: string; if (typeof plugin === 'string') { @@ -115,6 +150,11 @@ export class RepositoryManager { this.plugins.removeAvailable(pluginMeta); } + /** + * Get an up-to-date version of a plugin manifest from remote server. + * @param url Repository manifest URL + * @returns Repository manifest + */ public async getRemote(url: string): Promise { const indexFileGet = await httpGET(url); const meta = JSON.parse(indexFileGet); @@ -134,6 +174,11 @@ export class RepositoryManager { return meta; } + /** + * Install a repository by remote URL + * @param url Remote repository manifest URL + * @returns Repository manifest + */ public async installRepository(url: string): Promise { // Add standard repository file path, if missing if (!url.endsWith('/repository.json')) { @@ -158,6 +203,10 @@ export class RepositoryManager { return remote; } + /** + * Uninstall a repository + * @param repo Repository name or manifest + */ public async uninstallRepository(repo: string | IRepository): Promise { if (typeof repo === 'string') { repo = this.getRepoByName(repo) as IRepository; @@ -180,6 +229,11 @@ export class RepositoryManager { } } + /** + * Check for plugin updates from this repository. + * @param repo Repository manifest + * @returns Plugin manifests of plugins that can be updated + */ public async checkForUpdates(repo: IRepository): Promise { const oprep = await this.updateRepository(repo); @@ -194,6 +248,11 @@ export class RepositoryManager { }); } + /** + * Update a repository manifest by going to the remote. + * @param repo Repository manifest + * @returns Up-to-date repository manifest + */ public async updateRepository(repo: IRepository): Promise { const remote = await this.getRemote(repo.url + '/repository.json'); if (remote.created <= repo.created) { @@ -213,6 +272,9 @@ export class RepositoryManager { return remote; } + /** + * Load installed repositories from manifest files. + */ public async loadFromFiles(): Promise { const repos = await fs.readdir(this.env.repositoryPath); const loaded = []; diff --git a/src/types/config.ts b/src/types/config.ts index a2ed24f..5c924e1 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -2,12 +2,22 @@ import * as fs from 'fs-extra'; import { IEnvironment } from './environment'; +/** + * Configuration object + */ export class Configuration { public config: any = {}; - private loaded = false; + public loaded = false; - constructor(private env: IEnvironment, private file: string, private defaults: any = {}) {} + constructor( + private env: IEnvironment, + private file: string, + private defaults: any = {} + ) {} + /** + * Load the configuration from its file. + */ public async load(): Promise { this.loaded = true; if (!await fs.pathExists(this.file)) { @@ -23,10 +33,20 @@ export class Configuration { } } + /** + * Save configuration to its file. + */ public async save(): Promise { return fs.writeJson(this.file, this.config); } + /** + * Get a configuration value by key. + * @param key JSON traverse key (foo.bar.one) + * @param defval Default value + * @param from Internal use only + * @returns Configuration value or default or null + */ public get(key: string, defval?: any, from?: any): any { if (!from) { from = this.config; @@ -62,6 +82,10 @@ export class Configuration { return from[key]; } + /** + * Set a configuration value by key. + * @returns true on success + */ public set(key: string, value?: any, from?: any): boolean { if (!from) { from = this.config; @@ -94,12 +118,19 @@ export class Configuration { return true; } + /** + * Set the default configuration before loading. + * @param defconf Default configuration + */ public setDefaults(defconf: any): void { this.defaults = defconf; } - public saveDefaults(): void { + /** + * Save default values to configuration file + */ + public async saveDefaults(): Promise { this.config = this.defaults || {}; - this.save(); + await this.save(); } } diff --git a/src/types/plugin-config.ts b/src/types/plugin-config.ts index 1174344..a5bbcd1 100644 --- a/src/types/plugin-config.ts +++ b/src/types/plugin-config.ts @@ -3,6 +3,9 @@ import * as path from 'path'; import { Configuration } from './config'; import { IEnvironment } from './environment'; +/** + * Class that acts as a Configuration for plugins. + */ export class PluginConfiguration extends Configuration { constructor(env: IEnvironment, name: string) { super(env, path.join(env.configurationPath, name + '.json')); diff --git a/src/types/protocol.ts b/src/types/protocol.ts index 2dffc9a..9a51e2f 100644 --- a/src/types/protocol.ts +++ b/src/types/protocol.ts @@ -5,6 +5,9 @@ import { IPlugin } from '../plugin'; import { IMessage, IMessageTarget } from './message'; import { Formatter } from './message-format'; +/** + * The base class for a protocol handler. + */ export class Protocol extends EventEmitter { public format: Formatter = new Formatter(false, false); diff --git a/src/types/service.ts b/src/types/service.ts index 2fe72d4..559250b 100644 --- a/src/types/service.ts +++ b/src/types/service.ts @@ -1,6 +1,9 @@ import { EMessageType, IMessage } from './message'; import { Protocol } from './protocol'; +/** + * Service is used to run, keep track of and kill Protocols. + */ export class Service { private protocols: Map = new Map(); private stopped = false; @@ -35,7 +38,12 @@ export class Service { }); } - // Add a new protocol to this service + /** + * Add a new protocol to this service. + * @param pto Protocol instance + * @param autostart Automatically start the protocol + * @returns Protocol instance + */ public use(pto: Protocol, autostart = true): Protocol { // This service is no longer accepting new protocols if (this.stopped) { @@ -61,7 +69,11 @@ export class Service { return pto; } - // Stop a protocol running in this service + /** + * Stop a protocol running in this service + * @param pto Protocol instance or name + * @param force Force stop + */ public stop(pto: string | Protocol, force = false): void { let proto: Protocol; if (typeof pto === 'string') { @@ -80,15 +92,26 @@ export class Service { this.protocols.delete(proto.name); } + /** + * Find a protocol by name. + * @param name Protocol name + * @returns Protocol instance or undefined + */ public getProtocolByName(name: string): Protocol | undefined { return this.protocols.get(name); } + /** + * Get all protocol instances running in this service. + * @returns List of all protocol instances running in this service + */ public getAll(): Protocol[] { return Array.from(this.protocols.values()); } - // Gracefully stops everything running in this service + /** + * Gracefully stops everything running in this service + */ public stopAll(): Promise { return new Promise((resolve, reject) => { if (this.stopped) { @@ -133,7 +156,9 @@ export class Service { }); } - // Kills everything running in this service + /** + * Forcefully kills everything running in this service, + */ public die(): void { this.stopped = true; for (const [name, proto] of this.protocols) { diff --git a/src/util/events.ts b/src/util/events.ts index 2868370..07f82d7 100644 --- a/src/util/events.ts +++ b/src/util/events.ts @@ -1,5 +1,8 @@ import { logger } from '../core'; +/** + * Event emitter that can be scoped by plugin name or core. + */ export class ScopedEventEmitter { private listeners: {[key: string]: any[]}; public addEventListener = this.on; diff --git a/src/util/index.ts b/src/util/index.ts index d53258a..1f0e3e9 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -3,6 +3,11 @@ import path from 'path'; export { ScopedEventEmitter } from './events'; export { IProcessData, spawnProcess, execProcess } from './run'; +/** + * Load a Node.js module without caching it. + * @param file JavaScript file + * @returns Loaded module + */ export function requireNoCache(file: string): object | undefined { const fullPath = path.resolve(file); const mod = require(fullPath); diff --git a/src/util/run.ts b/src/util/run.ts index a8ea896..c65fa33 100644 --- a/src/util/run.ts +++ b/src/util/run.ts @@ -7,6 +7,13 @@ export interface IProcessData { stdout: string[]; } +/** + * Spawn a process. + * @param execp Executable + * @param args Executable arguments + * @param env Squeebot environment + * @returns Process output + */ export async function spawnProcess(execp: string, args: any[], env: IEnvironment): Promise { return new Promise((resolve): void => { const process = spawn(execp, args, { cwd: env.path }); @@ -28,6 +35,12 @@ export async function spawnProcess(execp: string, args: any[], env: IEnvironment }); } +/** + * Execute an executable file while buffering its output. + * @param execp Executable + * @param env Squeebot environment + * @returns Process output + */ export async function execProcess(execp: string, env: IEnvironment): Promise { return new Promise((resolve): void => { exec(execp, (error: any, stdout: any, stderr: any): void => resolve({