Skip to content

Commit

Permalink
Add ListenerUtil
Browse files Browse the repository at this point in the history
It's helpful I swear
  • Loading branch information
zajrik committed May 11, 2017
1 parent 2b14ab9 commit c951fee
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 17 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"glob": "7.1.1",
"node-json-db": "0.7.3",
"performance-now": "^2.0.0",
"postinstall-build": "^3.0.0"
"postinstall-build": "^3.0.0",
"reflect-metadata": "^0.1.10"
},
"devDependencies": {
"@types/chalk": "^0.4.31",
Expand Down
23 changes: 22 additions & 1 deletion src/client/Client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import * as Discord from 'discord.js';
import * as path from 'path';
import { Channel, ClientOptions, Collection, Emoji, Guild, GuildMember, Message, MessageReaction, Role, User, UserResolvable, ClientUserSettings, Snowflake } from 'discord.js';

import {
Channel,
ClientOptions,
Collection,
Emoji,
Guild,
GuildMember,
Message,
MessageReaction,
Role,
User,
UserResolvable,
ClientUserSettings,
Snowflake
} from 'discord.js';

import { Command } from '../command/Command';
import { CommandDispatcher } from '../command/CommandDispatcher';
import { CommandLoader } from '../command/CommandLoader';
Expand All @@ -16,6 +32,9 @@ import { MiddlewareFunction } from '../types/MiddlewareFunction';
import { StorageProviderConstructor } from '../types/StorageProviderConstructor';
import { BaseCommandName } from '../types/BaseCommandName';
import { Logger, logger } from '../util/logger/Logger';
import { ListenerUtil } from '../util/ListenerUtil';

const { registerListeners } = ListenerUtil;

/**
* The YAMDBF Client through which you can access [storage]{@link Client#storage}
Expand Down Expand Up @@ -173,6 +192,8 @@ export class Client extends Discord.Client

// Load commands
if (!this.passive) this.loadCommand('all');

registerListeners(this);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export { LogLevel } from './types/LogLevel';

export { deprecated } from './util/DeprecatedDecorator';

export { ListenerUtil } from './util/ListenerUtil';

export { ArgOpts } from './types/ArgOpts';
export { BaseCommandName } from './types/BaseCommandName';
export { ClientStorage } from './types/ClientStorage';
Expand Down
98 changes: 98 additions & 0 deletions src/util/ListenerUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import 'reflect-metadata';
import { EventEmitter } from 'events';

/**
* Contains static methods for declaring class methods (within a class extending `EventEmitter`)
* as listeners for events that will be emitted by the class or parent classes
* @module ListenerUtil
*/
export class ListenerUtil
{
/**
* Attaches any listeners registered via the `on` or `once` decorators.
* Must be called ***after*** `super()`, and only in classes extending `EventEmitter`
* (which includes the Discord.js Client class and thus the YAMDBF Client class);
* @static
* @method registerListeners
* @param {EventEmitter} emitter EventEmitter to register listeners for
* @returns {void}
*/
public static registerListeners(emitter: EventEmitter): void
{
if (!(emitter instanceof EventEmitter))
throw new TypeError('Listeners can only be registered on classes extending EventEmitter');
if (typeof Reflect.getMetadata('listeners', emitter.constructor.prototype) === 'undefined') return;

for (const listener of <ListenerMetadata[]> Reflect.getMetadata('listeners', emitter.constructor.prototype))
{
if (!(<any> emitter)[listener.method]) continue;
if (listener.attached) continue;
listener.attached = true;
emitter[listener.once ? 'once' : 'on'](listener.event,
(...eventArgs: any[]) => (<any> emitter)[listener.method](...eventArgs));
}
}

/**
* Declares the decorated method as an event handler for the specified event
* Must be registered by calling {@link ListenerUtil.registerListeners()}
*
* > **Note:** `registerListeners()` is already called in the YAMDBF
* {@link Client} constructor and does not need to be called in classes
* extending it
* @static
* @method on
* @param {string} event The name of the event to handle
* @returns {MethodDecorator}
*/
public static on(event: string): MethodDecorator
{
return ListenerUtil._setListenerMetadata(event);
}

/**
* Declares the decorated method as a single-use event handler for the
* specified event. Must be registered by calling
* {@link ListenerUtil.registerListeners()}
*
* > **Note:** `registerListeners()` is already called in the YAMDBF
* {@link Client} constructor and does not need to be called in classes
* extending it
* @static
* @method once
* @param {string} event The name of the event to handle
* @returns {MethodDecorator}
*/
public static once(event: string): MethodDecorator
{
return ListenerUtil._setListenerMetadata(event, true);
}

/**
* Returns a MethodDecorator that handles setting the appropriate listener
* metadata for a class method
* @private
*/
private static _setListenerMetadata(event: string, once: boolean = false): MethodDecorator
{
return function<T extends EventEmitter>(target: T, key: string, descriptor: PropertyDescriptor): PropertyDescriptor
{
const listeners: ListenerMetadata[] = Reflect.getMetadata('listeners', target) || [];
listeners.push({ event: event, method: key, once: once });
Reflect.defineMetadata('listeners', listeners, target);
return descriptor;
};
}
}

/**
* Represents metadata used to build an event listener
* and assign it to a class method at runtime
*/
type ListenerMetadata =
{
event: string;
method: string;
once: boolean;
attached?: boolean;
};
65 changes: 50 additions & 15 deletions test/test_client.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
import { Client, LogLevel, Logger } from '../bin/';
import { Client, LogLevel, Logger, ListenerUtil } from '../bin/';
const config: any = require('./config.json');
const logger: Logger = Logger.instance();
const { once } = ListenerUtil;

const client: Client = new Client({
name: 'test',
token: config.token,
config: config,
commandsDir: './commands',
logLevel: LogLevel.DEBUG
}).start();
// const client: Client = new Client({
// name: 'test',
// token: config.token,
// config: config,
// commandsDir: './commands',
// logLevel: LogLevel.DEBUG
// }).start();

client.on('waiting', async () =>
// client.on('waiting', async () =>
// {
// await client.setDefaultSetting('prefix', '.');
// client.emit('finished');
// });
// logger.warn('Test', 'Testing Logger#warn()');
// logger.error('Test', 'Testing Logger#error()');
// logger.debug('Test', 'Testing Logger#debug()');

class Test extends Client
{
await client.setDefaultSetting('prefix', '.');
client.emit('finished');
});
logger.warn('Test', 'Testing Logger#warn()');
logger.error('Test', 'Testing Logger#error()');
logger.debug('Test', 'Testing Logger#debug()');
public constructor()
{
super({
name: 'test',
token: config.token,
config: config,
commandsDir: './commands',
logLevel: LogLevel.DEBUG
});
}

@once('waiting')
private async _onWaiting(): Promise<void>
{
logger.debug('Test', 'Waiting...');
await this.setDefaultSetting('prefix', '?');
this.emit('finished');
}

@once('finished')
private _onFinished(): void
{
logger.debug('Test', 'Finished');

logger.warn('Test', 'Testing Logger#warn()');
logger.debug('Test', 'Testing Logger#debug()');
logger.error('Test', 'Testing Logger#error()');
}
}
const test: Test = new Test();
test.start();

0 comments on commit c951fee

Please sign in to comment.