Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: guild duplicate
Browse files Browse the repository at this point in the history
socram03 committed Oct 22, 2024
1 parent 34c8b09 commit 1e8211c
Showing 2 changed files with 116 additions and 63 deletions.
124 changes: 71 additions & 53 deletions packages/cooldown/src/manager.ts
Original file line number Diff line number Diff line change
@@ -10,15 +10,11 @@ export class CooldownManager {
this.resource = new CooldownResource(client.cache, client);
}

/**
* Get the cooldown data for a command
* @param name - The name of the command
* @returns The cooldown data for the command
*/
getCommandData(name: string): [name: string, data: CooldownProps | undefined] | undefined {
getCommandData(name: string, guildId?: string): [name: string, data: CooldownProps | undefined] | undefined {
if (!this.client.commands?.values?.length) return;
for (const command of this.client.commands.values) {
if (!('cooldown' in command)) continue;
if (guildId && !command.guildId?.includes(guildId)) continue;
if (command.name === name) return [command.name, command.cooldown];
if ('options' in command) {
const option = command.options?.find((x): x is SubCommand => x.name === name);
@@ -30,39 +26,39 @@ export class CooldownManager {
return undefined;
}

/**
* Check if a user has a cooldown
* @param name - The name of the command
* @param target - The target of the cooldown
* @returns Whether the user has a cooldown
*/
has(name: string, target: string, use: keyof UsesProps = 'default', tokens = 1): ReturnCache<boolean> {
const [resolve, data] = this.getCommandData(name) ?? [];
has(options: CooldownHasOptions): ReturnCache<boolean> {
const [resolve, data] = this.getCommandData(options.name, options.guildId) ?? [];
if (!(data && resolve)) return false;

return fakePromise(this.resource.get(`${resolve}:${data.type}:${target}`)).then(cooldown => {
if (tokens > data.uses[use]) return true;
return fakePromise(this.resource.get(`${resolve}:${data.type}:${options.target}`)).then(cooldown => {
if ((options.tokens ?? 1) > data.uses[options.use ?? 'default']) return true;
if (!cooldown) {
return fakePromise(
this.set(resolve, target, { type: data.type, interval: data.interval, remaining: data.uses[use] }),
this.set({
name: resolve,
target: options.target,
type: data.type,
interval: data.interval,
remaining: data.uses[options.use ?? 'default'],
}),
).then(() => false);
}

const remaining = Math.max(cooldown.remaining - tokens, 0);
const remaining = Math.max(cooldown.remaining - (options.tokens ?? 1), 0);

return remaining === 0;
});
}

set(
name: string,
target: string,
{ type, ...data }: MakePartial<CooldownData, 'lastDrip'> & { type: `${CooldownType}` },
) {
return this.resource.set(`${name}:${type}:${target}`, data);
set(options: CooldownSetOptions) {
return this.resource.set(`${options.name}:${options.type}:${options.target}`, {
interval: options.interval,
remaining: options.remaining,
lastDrip: options.lastDrip,
});
}

context(context: AnyContext, use?: keyof UsesProps) {
context(context: AnyContext, use?: keyof UsesProps, guildId?: string) {
if (!('command' in context)) return true;
if (!('name' in context.command)) return true;

@@ -83,69 +79,69 @@ export class CooldownManager {
}

target ??= context.author.id;
return this.use(context.command.name, target, use);
return this.use({ name: context.command.name, target, use, guildId });
}

/**
* Use a cooldown
* @param name - The name of the command
* @param target - The target of the cooldown
* @returns The remaining cooldown in seconds or true if successful
*/
use(name: string, target: string, use: keyof UsesProps = 'default'): ReturnCache<number | true> {
const [resolve, data] = this.getCommandData(name) ?? [];
use(options: CooldownUseOptions): ReturnCache<number | true> {
const [resolve, data] = this.getCommandData(options.name, options.guildId) ?? [];
if (!(data && resolve)) return true;

return fakePromise(this.resource.get(`${resolve}:${data.type}:${target}`)).then(cooldown => {
return fakePromise(this.resource.get(`${resolve}:${data.type}:${options.target}`)).then(cooldown => {
if (!cooldown) {
return fakePromise(
this.set(resolve, target, {
this.set({
name: resolve,
target: options.target,
type: data.type,
interval: data.interval,
remaining: data.uses[use] - 1,
remaining: data.uses[options.use ?? 'default'] - 1,
}),
).then(() => true);
}

return fakePromise(this.drip(resolve, target, data, cooldown, use)).then(drip => {
return fakePromise(
this.drip({
name: resolve,
props: data,
data: cooldown,
target: options.target,
use: options.use,
}),
).then(drip => {
return typeof drip === 'number' ? data.interval - drip : true;
});
});
}

/**
* Drip the cooldown
* @param name - The name of the command
* @param target - The target of the cooldown
* @param props - The cooldown properties
* @param data - The cooldown data
* @returns The cooldown was processed
*/
drip(
name: string,
target: string,
props: CooldownProps,
data: CooldownData,
use: keyof UsesProps = 'default',
): ReturnCache<boolean | number> {
drip(options: CooldownDripOptions): ReturnCache<boolean | number> {
const now = Date.now();
const deltaMS = now - data.lastDrip;
if (deltaMS >= props.interval) {
const deltaMS = now - options.data.lastDrip;
if (deltaMS >= options.props.interval) {
return fakePromise(
this.resource.patch(`${name}:${props.type}:${target}`, {
this.resource.patch(`${options.name}:${options.props.type}:${options.target}`, {
lastDrip: now,
remaining: props.uses[use] - 1,
remaining: options.props.uses[options.use ?? 'default'] - 1,
}),
).then(() => true);
}

if (data.remaining - 1 < 0) {
if (options.data.remaining - 1 < 0) {
return deltaMS;
}

return fakePromise(this.resource.patch(`${name}:${props.type}:${target}`, { remaining: data.remaining - 1 })).then(
() => true,
);
return fakePromise(
this.resource.patch(`${options.name}:${options.props.type}:${options.target}`, {
remaining: options.data.remaining - 1,
}),
).then(() => true);
}

/**
@@ -173,6 +169,28 @@ export interface CooldownProps {
uses: UsesProps;
}

export interface CooldownUseOptions {
name: string;
target: string;
use?: keyof UsesProps;
guildId?: string;
}

export interface CooldownDripOptions extends Omit<CooldownUseOptions, 'guildId'> {
props: CooldownProps;
data: CooldownData;
}

export interface CooldownHasOptions extends CooldownUseOptions {
tokens?: number;
}

export interface CooldownSetOptions extends MakePartial<CooldownData, 'lastDrip'> {
name: string;
target: string;
type: `${CooldownType}`;
}

export interface UsesProps {
default: number;
}
55 changes: 45 additions & 10 deletions packages/cooldown/test/manager.test.mts
Original file line number Diff line number Diff line change
@@ -118,47 +118,82 @@ describe('CooldownManager', async () => {
});

test('has should return false for a new cooldown', () => {
const result = cooldownManager.has('testCommand', 'user1');
const result = cooldownManager.has({
name: 'testCommand',
target: 'user1',
});
assert.equal(result, false);
});

test('has should return true when cooldown is active', () => {
for (let i = 0; i < cooldownData.uses.default; i++) {
cooldownManager.use('testCommand', 'user1');
cooldownManager.use({
name: 'testCommand',
target: 'user1',
});
}
const result = cooldownManager.has('testCommand', 'user1');
const result = cooldownManager.has({
name: 'testCommand',
target: 'user1',
});
assert.equal(result, true);
});

test('use should set cooldown when used for the first time', () => {
const result = cooldownManager.use('testCommand', 'user2');
const result = cooldownManager.use({
name: 'testCommand',
target: 'user2',
});
assert.equal(result, true);
});

test('use should return time left when cooldown is active', () => {
for (let i = 0; i < cooldownData.uses.default; i++) {
cooldownManager.use('testCommand', 'user3');
cooldownManager.use({
name: 'testCommand',
target: 'user3',
});
}
const result = cooldownManager.use('testCommand', 'user3');
const result = cooldownManager.use({
name: 'testCommand',
target: 'user3',
});
assert.ok(typeof result === 'number');
});

test('refill should refill the cooldown', () => {
cooldownManager.use('testCommand', 'user1');
cooldownManager.use({
name: 'testCommand',
target: 'user1',
});
const result = cooldownManager.refill('testCommand', 'user1');
assert.equal(result, true);
assert.equal(cooldownManager.has('testCommand', 'user1'), false);
assert.equal(
cooldownManager.has({
name: 'testCommand',
target: 'user1',
}),
false,
);
});

test('drip should drip the cooldown over time', async () => {
cooldownManager.use('testCommand', 'user1');
cooldownManager.use({
name: 'testCommand',
target: 'user1',
});

// Simulate time passing
await new Promise(resolve => setTimeout(resolve, 1000));

const data = cooldownManager.resource.get('testCommand:user:user1');
const props = cooldownManager.getCommandData('testCommand');
await cooldownManager.drip('testCommand', 'user1', props![1]!, data!);
await cooldownManager.drip({
name: 'testCommand',
target: 'user1',
data: data!,
props: props![1]!,
});
const getter = cooldownManager.resource.get('testCommand:user:user1');
assert.ok(getter?.remaining === 2);
});

0 comments on commit 1e8211c

Please sign in to comment.