handling customId params working

This commit is contained in:
Jacob Nguyen
2024-05-22 23:33:37 -05:00
parent 327e56fc1c
commit 1de21b8b37
10 changed files with 256 additions and 148 deletions

View File

@@ -1,10 +1,10 @@
import { ApplicationCommandType, ComponentType, Interaction, InteractionType } from 'discord.js';
import { ApplicationCommandType, ComponentType, type Interaction, InteractionType } from 'discord.js';
import { CommandType, EventType } from './structures/enums';
const parseParams = (event: { customId: string }, id: string, append: string) => {
const hasSlash = event.customId.indexOf('/')
if(hasSlash === -1) {
return { id };
return { id:id+append };
}
const baseid = event.customId.substring(0, hasSlash);
const params = event.customId.substring(hasSlash+1);

View File

@@ -1,4 +1,3 @@
import type { DependencyConfiguration } from '../../types/ioc';
import { Container } from './container';
import * as __Services from '../structures/default-services';
import type { Logging } from '../interfaces';
@@ -11,11 +10,11 @@ export function disposeAll(logger: Logging|undefined) {
.then(() => logger?.info({ message: 'Cleaning container and crashing' }));
}
type Insertable =
| ((container: Dependencies) => object)
| object
const dependencyBuilder = (container: Container, excluded: string[] ) => {
const dependencyBuilder = (container: Container) => {
return {
/**
* Insert a dependency into your container.
@@ -29,14 +28,6 @@ const dependencyBuilder = (container: Container, excluded: string[] ) => {
container.addWiredSingleton(key, (cntr) => v(cntr))
}
},
/**
* Exclude any dependencies from being added.
* Warning: this could lead to bad errors if not used correctly
*/
exclude(...keys: (keyof Dependencies)[]) {
keys.forEach(key => excluded.push(key));
},
/**
* @param key the key of the dependency
* @param v The dependency to swap out.
@@ -49,60 +40,28 @@ const dependencyBuilder = (container: Container, excluded: string[] ) => {
};
};
/**
*
*
*
*/
type ValidDependencyConfig =
| ((c: ReturnType<typeof dependencyBuilder>) => any)
| DependencyConfiguration;
/**
* Given the user's conf, check for any excluded/included dependency keys.
* Then, call conf.build to get the rest of the users' dependencies.
* Finally, update the containerSubject with the new container state
* @param conf
*/
async function composeRoot(
container: Container,
conf: DependencyConfiguration,
) {
//container should have no client or logger yet.
const hasLogger = container.hasKey('@sern/logger');
if (!hasLogger) {
__add_container('@sern/logger', new __Services.DefaultLogging());
}
__add_container('@sern/errors', new __Services.DefaultErrorHandling());
__add_container('@sern/modules', new Map())
__add_container('@sern/emitter', new EventEmitter())
__add_wiredcontainer('@sern/cron', deps => new __Services.Cron(deps))
//Build the container based on the callback provided by the user
conf.build(container as Container);
if (!hasLogger) {
container.get<Logging>('@sern/logger')
?.info({ message: 'All dependencies loaded successfully.' });
}
await container.ready();
}
export async function makeDependencies (conf: ValidDependencyConfig) {
const container = await __init_container({ autowire: false });
if(typeof conf === 'function') {
const excluded: string[] = [];
conf(dependencyBuilder(container, excluded));
//We only include logger if it does not exist
const includeLogger =
!excluded.includes('@sern/logger')
&& !container.hasKey('@sern/logger');
conf(dependencyBuilder(container));
//We only include logger if it does not exist
const includeLogger = !container.hasKey('@sern/logger');
if(includeLogger) {
__add_container('@sern/logger', new __Services.DefaultLogging);
}
__add_container('@sern/errors', new __Services.DefaultErrorHandling());
__add_container('@sern/modules', new Map())
__add_container('@sern/emitter', new EventEmitter())
__add_wiredcontainer('@sern/cron', deps => new __Services.Cron(deps))
await container.ready();
} else {
await composeRoot(container, conf);
if(includeLogger) {
__add_container('@sern/logger', new __Services.DefaultLogging);
}
__add_container('@sern/errors', new __Services.DefaultErrorHandling);
__add_container('@sern/modules', new Map)
__add_container('@sern/emitter', new EventEmitter)
__add_wiredcontainer('@sern/cron', deps => new __Services.Cron(deps))
await container.ready();
}

View File

@@ -28,7 +28,7 @@ export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
get options() {
return this.interaction.options;
}
//TODO
args(type: 'message'|'interaction', parser?: Function) {
switch(type) {
case 'message': {
@@ -42,10 +42,12 @@ export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
}
protected constructor(protected ctx: Result<Message, ChatInputCommandInteraction>,
public prefix?: string) {
private __prefix?: string) {
super(ctx);
}
public get prefix() {
return this.__prefix;
}
public get id(): Snowflake {
return safeUnwrap(this.ctx
.map(m => m.id)

View File

@@ -181,7 +181,7 @@ export function executeModule(
* - if all results are ok, the stream is converted to { config.onNext }
* config.onNext will be returned if everything is okay.
* @param config
* @returns receiver function for flattening a stream of data
* @returns function which calls all plugins and returns onNext or fail
*/
export function createResultResolver<Output>(config: {
onStop?: (module: Module, err?: string) => unknown;
@@ -198,11 +198,11 @@ export function createResultResolver<Output>(config: {
};
};
export async function callInitPlugins(module: Module, deps: Dependencies, sEmitter?: Emitter) {
export async function callInitPlugins(module: Module, deps: Dependencies, emit?: boolean ) {
let _module = module;
for(const plugin of _module.plugins ?? []) {
const res = await plugin.execute({
module, absPath: _module.meta.absPath ,
module, absPath: _module.meta.absPath,
updateModule: (partial: Partial<Module>) => {
_module = { ..._module, ...partial };
return _module;
@@ -210,7 +210,10 @@ export async function callInitPlugins(module: Module, deps: Dependencies, sEmitt
deps
});
if(res.isErr()) {
sEmitter?.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure));
if(emit) {
deps['@sern/emitter']
?.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure));
}
throw Error("Plugin failed with controller.stop()");
}
}

View File

@@ -24,7 +24,7 @@ export default async function(dir: string, deps : UnpackedDependencies) {
if(!validType) {
throw Error(`Found ${module.name} at ${module.meta.absPath}, which has incorrect \`type\``);
}
const resultModule = await callInitPlugins(module, deps, sEmitter);
const resultModule = await callInitPlugins(module, deps, true);
// FREEZE! no more writing!!
commands.set(resultModule.meta.id, Object.freeze(resultModule));
sEmitter.emit('module.register', resultPayload(PayloadType.Success, resultModule));

View File

@@ -33,10 +33,7 @@ export default async function(deps: UnpackedDependencies, eventDir: string) {
}
from(eventModules)
.pipe(map(intoDispatcher(deps)),
/**
* Where all events are turned on
*/
mergeAll(),
mergeAll(), // all eventListeners are turned on
handleCrash(deps))
.subscribe();
}

View File

@@ -1,11 +1,13 @@
//@ts-nocheck
import { afterEach, describe, expect, it, vi } from 'vitest';
import { PluginType, SernOptionsData, controller } from '../../src/index';
import { partitionPlugins, treeSearch } from '../../src/core/functions';
import { faker } from '@faker-js/faker';
import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js';
vi.mock('discord.js', () => {
const Collection = Map;
vi.mock('discord.js', async (importOriginal) => {
const mod = await importOriginal()
const ModalSubmitInteraction = class {
customId;
type = 5;
@@ -36,35 +38,11 @@ vi.mock('discord.js', () => {
};
return {
Collection,
ComponentType: {
Button: 2,
},
InteractionType: {
Ping: 1,
ApplicationCommand: 2,
MessageComponent: 3,
ApplicationCommandAutocomplete: 4,
ModalSubmit: 5,
},
ApplicationCommandOptionType: {
Subcommand: 1,
SubcommandGroup: 2,
String: 3,
Integer: 4,
Boolean: 5,
User: 6,
Channel: 7,
Role: 8,
Mentionable: 9,
Number: 10,
Attachment: 11,
},
ApplicationCommandType: {
ChatInput: 1,
User: 2,
Message: 3,
},
Collection: mod.Collection,
ComponentType: mod.ComponentType,
InteractionType: mod.InteractionType,
ApplicationCommandOptionType: mod.ApplicationCommandOptionType,
ApplicationCommandType: mod.ApplicationCommandType,
ModalSubmitInteraction,
ButtonInteraction,
AutocompleteInteraction,

View File

@@ -1,7 +1,51 @@
//@ts-nocheck
import { expect, test, vi } from 'vitest'
import { CommandType } from '../../src/core/structures/enums';
import * as Id from '../../src/core/id'
import { expect, test } from 'vitest'
import * as Id from '../../src/core/id'
import { ButtonInteraction, ModalSubmitInteraction } from 'discord.js';
vi.mock('discord.js', async (importOriginal) => {
const mod = await importOriginal()
const ModalSubmitInteraction = class {
customId;
type = 5;
isModalSubmit = vi.fn();
constructor(customId) {
this.customId = customId;
}
};
const ButtonInteraction = class {
customId;
type = 3;
componentType = 2;
isButton = vi.fn();
constructor(customId) {
this.customId = customId;
}
};
const AutocompleteInteraction = class {
type = 4;
option: string;
constructor(s: string) {
this.option = s;
}
options = {
getFocused: vi.fn(),
getSubcommand: vi.fn(),
};
};
return {
Collection: mod.Collection,
ComponentType: mod.ComponentType,
InteractionType: mod.InteractionType,
ApplicationCommandOptionType: mod.ApplicationCommandOptionType,
ApplicationCommandType: mod.ApplicationCommandType,
ModalSubmitInteraction,
ButtonInteraction,
AutocompleteInteraction,
};
});
test('id -> Text', () => {
const bothCmdId = Id.create("ping", CommandType.Text)
expect(bothCmdId).toBe("ping_T")
@@ -60,5 +104,37 @@ test('id -> ChannelSelect', () => {
expect(modal).toBe("mychannelselect_C8");
})
test('id reconstruct button', () => {
const idload = Id.reconstruct(new ButtonInteraction("btn"))
expect(idload[0].id).toBe("btn_C2")
})
test('id reconstruct button with params', () => {
const idload = Id.reconstruct(new ButtonInteraction("btn/asdf"))
expect(idload[0].id).toBe("btn_C2")
expect(idload[0].params).toBe("asdf")
})
test('id reconstruct modal with params', () => {
const idload = Id.reconstruct(new ModalSubmitInteraction("btn/asdf"))
expect(idload[0].id).toBe("btn_M")
expect(idload[0].params).toBe("asdf")
})
test('id reconstruct modal', () => {
const idload = Id.reconstruct(new ModalSubmitInteraction("btn"))
expect(idload[0].id).toBe("btn_M")
expect(idload[0].params).toBe(undefined)
})
test('id reconstruct button with empty params', () => {
const idload = Id.reconstruct(new ButtonInteraction("btn/"))
expect(idload[0].id).toBe("btn_C2")
expect(idload[0].params).toBe("")
})
test('id reconstruct button', () => {
const idload = Id.reconstruct(new ButtonInteraction("btn"))
expect(idload[0].id).toBe("btn_C2")
expect(idload[0].params).toBe(undefined)
})

View File

@@ -1,43 +0,0 @@
import { beforeEach, describe, expect, vi, it } from 'vitest';
import { eventDispatcher } from '../../src/handlers/event-utils';
import { faker } from '@faker-js/faker';
import { Module } from '../../src/types/core-modules';
import { Processed } from '../../src/types/core-modules';
import { EventEmitter } from 'events';
import { EventType } from '../../dist/core/structures/enums';
function createRandomModule(): Processed<Module> {
return {
type: EventType.Discord,
meta: { id:"", absPath: "" },
description: faker.string.alpha(),
name: "abc",
onEvent: [],
plugins: [],
execute: vi.fn(),
};
}
function mockDeps() {
return {
'@sern/client': {}
}
}
describe('eventDispatcher standard', () => {
let m: Processed<Module>;
let ee: EventEmitter;
beforeEach(() => {
ee = new EventEmitter();
m = createRandomModule();
});
it('should throw', () => {
expect(() => eventDispatcher(mockDeps(), m, 'not event emitter')).toThrowError();
});
it("Shouldn't throw", () => {
expect(() => eventDispatcher(mockDeps(), m, ee)).not.toThrowError();
});
});

136
test/handlers/index.test.ts Normal file
View File

@@ -0,0 +1,136 @@
//@ts-nocheck
import { beforeEach, describe, expect, vi, it } from 'vitest';
import { callInitPlugins, eventDispatcher } from '../../src/handlers/event-utils';
import { Client } from 'discord.js'
import { faker } from '@faker-js/faker';
import { Module } from '../../src/types/core-modules';
import { Processed } from '../../src/types/core-modules';
import { EventEmitter } from 'events';
import { EventType } from '../../dist/core/structures/enums';
import { CommandInitPlugin, controller } from '../../src';
vi.mock('discord.js', () => {
const Client = vi.fn()
Client.prototype.login= vi.fn()
const Collection = Map;
const ModalSubmitInteraction = class {
customId;
type = 5;
isModalSubmit = vi.fn();
constructor(customId) {
this.customId = customId;
}
};
const ButtonInteraction = class {
customId;
type = 3;
componentType = 2;
isButton = vi.fn();
constructor(customId) {
this.customId = customId;
}
};
const AutocompleteInteraction = class {
type = 4;
option: string;
constructor(s: string) {
this.option = s;
}
options = {
getFocused: vi.fn(),
getSubcommand: vi.fn(),
};
};
return {
Client,
Collection,
ComponentType: {
Button: 2,
},
InteractionType: {
Ping: 1,
ApplicationCommand: 2,
MessageComponent: 3,
ApplicationCommandAutocomplete: 4,
ModalSubmit: 5,
},
ApplicationCommandOptionType: {
Subcommand: 1,
SubcommandGroup: 2,
String: 3,
Integer: 4,
Boolean: 5,
User: 6,
Channel: 7,
Role: 8,
Mentionable: 9,
Number: 10,
Attachment: 11,
},
ApplicationCommandType: {
ChatInput: 1,
User: 2,
Message: 3,
},
ModalSubmitInteraction,
ButtonInteraction,
AutocompleteInteraction,
};
})
function createRandomPlugin (s: 'go', mut?: Partial<Module>) {
return CommandInitPlugin(({ module, updateModule }) => {
if(mut) {
updateModule(mut)
}
return s == 'go'
? controller.next()
: controller.stop()
})
}
function createRandomModule(plugins: any[]): Processed<Module> {
return {
type: EventType.Discord,
meta: { id:"", absPath: "" },
description: faker.string.alpha(),
plugins,
name: "cheese",
onEvent: [],
execute: vi.fn(),
};
}
function mockDeps() {
return {
'@sern/client': new Client(),
'@sern/emitter': new EventEmitter()
}
}
describe('eventDispatcher standard', () => {
let m: Processed<Module>;
let ee: EventEmitter;
beforeEach(() => {
ee = new EventEmitter();
m = createRandomModule();
});
it('should throw', () => {
expect(() => eventDispatcher(mockDeps(), m, 'not event emitter')).toThrowError();
});
it("Shouldn't throw", () => {
expect(() => eventDispatcher(mockDeps(), m, ee)).not.toThrowError();
});
it('mutate with init plugins', async () => {
const deps = mockDeps()
const plugins = createRandomPlugin('go', { name: "abc" })
const mod = createRandomModule([plugins])
const s = await callInitPlugins(mod, deps, false)
expect(s.name).not.equal(mod.name)
})
});