Documentation of most methods

This commit is contained in:
Evert Prants 2021-10-02 11:07:01 +03:00
parent 68489c733d
commit 9a2142688b
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
20 changed files with 427 additions and 35 deletions

View File

@ -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;
}

View File

@ -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<any> {
const parsed = url.parse(link);
lback?: number
): Promise<any> {
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<any> {
const parsed = url.parse(link);
let postData = qs.stringify(data);
data: any
): Promise<any> {
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) => {

View File

@ -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(/&amp;/g, '&')

View File

@ -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);

View File

@ -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;

View File

@ -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<IEnvironment> {
if (!await fs.pathExists(enviroFile)) {
throw new Error('Environment file does not exist.');

View File

@ -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 };

View File

@ -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<string, string> = {};
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<void> {
// 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<void> {
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<void> {
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<void> {
if (!await fs.pathExists(this.packageFile)) {
await this.init();

View File

@ -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<string, PluginConfiguration> = 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<PluginConfiguration> {
if (!this.configs.has(mf.name)) {
const conf = new PluginConfiguration(this.env, mf.name);

View File

@ -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<IPluginManifest> {
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<IPluginManifest[]> {
const dirlist = await fs.readdir(this.env.pluginsPath);
const plugins: IPluginManifest[] = [];

View File

@ -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<string, IPlugin> = 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<IPlugin> {
// 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<void> {
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') {

View File

@ -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 {

View File

@ -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<string, IRepository> = 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<IPluginManifest> {
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<void> {
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<IRepository> {
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<IRepository> {
// 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<void> {
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<IPluginManifest[]> {
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<IRepository> {
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<void> {
const repos = await fs.readdir(this.env.repositoryPath);
const loaded = [];

View File

@ -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<void> {
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<void> {
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<void> {
this.config = this.defaults || {};
this.save();
await this.save();
}
}

View File

@ -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'));

View File

@ -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);

View File

@ -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<string, Protocol> = 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<void> {
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) {

View File

@ -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;

View File

@ -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);

View File

@ -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<IProcessData> {
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<IProcessData> {
return new Promise((resolve): void => {
exec(execp, (error: any, stdout: any, stderr: any): void => resolve({