Remove Cooldown for a Subcommand while using an external derived class

I get that there are ways to delete cooldowns in actual Commands, and other ways. Even ways to do the same thing with anything extending Subcommand. But I have a problem here, I'm using an external class to keep my code modular and clean, but this raises a problem for me. I can't seem to delete cooldowns from the WeakMap with this setup, and I don't want to have to make the base class extend Subcommand if I don't need to since it's gonna break a lot of things, mainly with how I'm using them (I'd have to pass some extra stuff into each subcommand, which would be less than good). Is there any other ways of doing what I'm wanting to do here? I haven't found any sort of guide on this particular topic, so... If this is a stupid question, I apologize. Also, if I am missing anything in here, let me know. I will happily try to get as much help as I can here. Information - Bun version: 1.3.1 - Sapphire version: 5.3.7 The class in question (src/subcommands/index.ts)
// ...

class BaseBotSubcommand {
// methods in here...
}

// ...
// ...

class BaseBotSubcommand {
// methods in here...
}

// ...
Solution:
subcommand buckets can be accessed via the following:
this.container.stores.get('preconditions').get('PluginSubcommandCooldown').subcommandBuckets
this.container.stores.get('preconditions').get('PluginSubcommandCooldown').subcommandBuckets
...
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
Jump to solution
43 Replies
Favna
Favna2mo ago
I don't think I really understand what you mean you're doing but for starters you do need MyCustomCommand extends Command and MyCustomSubcommand extends Subcommand so no commands without subcommands should not extend Subcommand. The WeakMap buckets for subcomands and c ommands are separate buckets and if you do the former you should be able to remove each from their respective buckets.
Yoshiboi18303
Yoshiboi18303OP2mo ago
So I may have to make my base class extend Subcommand to be able to handle its cooldowns, right? I looked through the declarations, my cooldowns are declared on a subcommand, and not a upper lever command.
Favna
Favna2mo ago
yes, you always have to extend one of Comand or Subcommand. Without they would not be registered and you would be missing many methods for actually handling them
Yoshiboi18303
Yoshiboi18303OP2mo ago
Because I basically have a class that acts like a group, and then my actual subcommands that run are just based off that class. Like this:
@ApplyOptions<Subcommand.Options>({
description: "View some information about the bot!",
enabled: true,
subcommands: [
{
name: "ping",
chatInputRun: "pingChatInput",
},
],
})
export class InfoGroup extends Subcommand {
public async pingChatInput(
context: Subcommand.ChatInputCommandInteraction,
) {
// This extends that base class I showed.
await new PingSubcommand().run(context);
}
}
@ApplyOptions<Subcommand.Options>({
description: "View some information about the bot!",
enabled: true,
subcommands: [
{
name: "ping",
chatInputRun: "pingChatInput",
},
],
})
export class InfoGroup extends Subcommand {
public async pingChatInput(
context: Subcommand.ChatInputCommandInteraction,
) {
// This extends that base class I showed.
await new PingSubcommand().run(context);
}
}
That's what I'm doing here. Guess I should've been more specific as to what I'm doing here, my bad.
Favna
Favna2mo ago
your folder structure are already groups internally. Every piece has a property called location which has several properties to group your commands
Yoshiboi18303
Yoshiboi18303OP2mo ago
So I should probably pass that in through a constructor or something, right? Or am I reading this wrong?
Favna
Favna2mo ago
s!ev this.command.location
Skyra
Skyra2mo ago
Output: ⏱ 310.02μs
PieceLocation { full: '/usr/src/app/dist/commands/System/Admin/eval.js', root: '/usr/src/app/dist/commands' }
PieceLocation { full: '/usr/src/app/dist/commands/System/Admin/eval.js', root: '/usr/src/app/dist/commands' }
Favna
Favna2mo ago
It's there by default you can use that to group things i.e. in a help command you dont need to do it yourself
Yoshiboi18303
Yoshiboi18303OP2mo ago
Yeah, just saw that. Sorry, just came back to TypeScript after a bit and I'm still kinda new to this framework.
Favna
Favna2mo ago
nothing to apologize for
Yoshiboi18303
Yoshiboi18303OP2mo ago
Hmm... trying to think about this while I try to get some help. Should I try passing through all the subcommand preconditions when the command runs?
Favna
Favna2mo ago
huh?
Yoshiboi18303
Yoshiboi18303OP2mo ago
Just found this.subcommandPreconditions, but that doesn't seem to be right.
Favna
Favna2mo ago
I dont understand what you want to achieve
Yoshiboi18303
Yoshiboi18303OP2mo ago
I might just have to rewrite my commands to just return a boolean and then remove the cooldown based on that.
Favna
Favna2mo ago
I'm still confused. Cooldowns are removed automatically when they expire. Are you saying you want to remove them manually under certain conditions?
Yoshiboi18303
Yoshiboi18303OP2mo ago
Yeah, that's basically what I want to do. Kinda like:
if (someConditionThatIsFalse) {
// Send embed
resetCooldown();
return;
}
if (someConditionThatIsFalse) {
// Send embed
resetCooldown();
return;
}
And I did read the forums on doing that in actual classes that extend Command or Subcommand, but I never found one for my specific use case.
Favna
Favna2mo ago
import { SubcommandPreconditions } from '@sapphire/plugin-subcommand'

if (someConditionThatIsFalse) {
// Send embed
SubcommandPreconditions.PluginSubcommandCooldown.subcommandBuckets.delete(instanceOfYourSubcommandClassToRemove)
}
import { SubcommandPreconditions } from '@sapphire/plugin-subcommand'

if (someConditionThatIsFalse) {
// Send embed
SubcommandPreconditions.PluginSubcommandCooldown.subcommandBuckets.delete(instanceOfYourSubcommandClassToRemove)
}
Yoshiboi18303
Yoshiboi18303OP2mo ago
Let me try that. One moment. Hmm... deprecated and invalid.
Favna
Favna2mo ago
? it shouldnt be
Yoshiboi18303
Yoshiboi18303OP2mo ago
No description
Yoshiboi18303
Yoshiboi18303OP2mo ago
Typed all that out, white line over SubcommandPreconditions, and it didn't see subcommandBuckets.
Favna
Favna2mo ago
No description
Favna
Favna2mo ago
I forgor we scheduled that it's just that the import changes and you dont need the SubcommandPreconditions. part anymore
Yoshiboi18303
Yoshiboi18303OP2mo ago
All right. Let me look through this, one moment.
Favna
Favna2mo ago
as for it not being there uuhhh that is a problem it's typed as the class instead of an instance of the class that's weird
Yoshiboi18303
Yoshiboi18303OP2mo ago
How long has that been deprecated for? Like... since 5.1? And should I try going to the next major version right now?
Favna
Favna2mo ago
I think so but it isnt saying that it will be removed say tomorrow. It's just a general reminder it may happen in the future. the next major version might be a year, 2 years, or never. It happens when it's required for different reasons to release a semver major. we never bump willy nilly anyway im trying to figure this out because I swear it was possible but I cannot find it anymore either
Yoshiboi18303
Yoshiboi18303OP2mo ago
Yeah, I just saw the next major version is listed on NPM, if this is it.
No description
Favna
Favna2mo ago
i can't find how to get the cooldowns anymore either Under which conditions did you exactly want to be able to remove the cooldown from the given context anyway if I may ask?
Yoshiboi18303
Yoshiboi18303OP2mo ago
Should I try going to the next major version then?
Favna
Favna2mo ago
no that wont change anything
Yoshiboi18303
Yoshiboi18303OP2mo ago
Ah, all right. Let's say for example my command requires a user document from the DB, and none was found (this was handled by a different precondition, but this is an example), then I'd like to tell the user the issue and then reset the cooldown so they don't get punished for no reason. Or if they decline something essential to the command or something like that. If that makes sense. Kinda got the tell the user part, just not the resetting part.
Favna
Favna2mo ago
for regular non subcomand preconditions I believe this is the way to get access to the WeakMap buckets
const bb = this.container.stores.get('preconditions')!.get('Cooldown')! as InstanceType<typeof CorePreconditions.Cooldown>;
const bb = this.container.stores.get('preconditions')!.get('Cooldown')! as InstanceType<typeof CorePreconditions.Cooldown>;
(might not be 100% accurate, I havent ran it, just written it out checking TS errors)
Yoshiboi18303
Yoshiboi18303OP2mo ago
All right. Let me see here...
Favna
Favna2mo ago
I believe subcommands should be similar. From the top of my head I forgor where they are stored in stores
Yoshiboi18303
Yoshiboi18303OP2mo ago
All right, all the cooldowns are in subcommands... Wait, one moment... This might be the biggest impasse I've been at ever, and it's nothing to do with this framework. I might have to leave the code as-is and just not reset cooldowns until there's a solid solution to this. Because the only other option is making every command really brittle and repeating myself.
Favna
Favna2mo ago
If you think you can have a design to do it in a good way then feel free to make a PR
Solution
Seren_Modz 21
Seren_Modz 212mo ago
subcommand buckets can be accessed via the following:
this.container.stores.get('preconditions').get('PluginSubcommandCooldown').subcommandBuckets
this.container.stores.get('preconditions').get('PluginSubcommandCooldown').subcommandBuckets
and an example with types: https://tsplay.dev/N94o9w
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
Favna
Favna2mo ago
Thanks :peeposaber: I knew it had to be somewhere
Yoshiboi18303
Yoshiboi18303OP2mo ago
Ooh, yeah. That actually helped! I might be getting somewhere now!
class BaseBotSubcommand {
container: Container = container;
#subcommandInstance: Subcommand;

constructor(subcommandInstance: Subcommand) {
this.#subcommandInstance = subcommandInstance;
}

public resetCooldown() {
// This actually logged something!
this.container.logger.info(
util.inspect(
(
this.container.stores
.get("preconditions")
.get(
"PluginSubcommandCooldown",
) as PluginSubcommandCooldownPrecondition
).subcommandBuckets.get(this.#subcommandInstance),
),
);
}

// ...
}
class BaseBotSubcommand {
container: Container = container;
#subcommandInstance: Subcommand;

constructor(subcommandInstance: Subcommand) {
this.#subcommandInstance = subcommandInstance;
}

public resetCooldown() {
// This actually logged something!
this.container.logger.info(
util.inspect(
(
this.container.stores
.get("preconditions")
.get(
"PluginSubcommandCooldown",
) as PluginSubcommandCooldownPrecondition
).subcommandBuckets.get(this.#subcommandInstance),
),
);
}

// ...
}
It's solved! We're good!
class BaseBotSubcommand {
container: Container = container;

#commandName: string;
readonly subcommandInstance: Subcommand;

constructor(subcommandInstance: Subcommand, commandName: string) {
this.#commandName = commandName;
this.subcommandInstance = subcommandInstance;
}

public resetCooldown(userId: string) {
const ratelimitManager = (
this.container.stores
.get("preconditions")
.get(
"PluginSubcommandCooldown",
) as PluginSubcommandCooldownPrecondition
).subcommandBuckets.get(this.subcommandInstance);

if (!ratelimitManager) return;

const key = `${userId}.${this.#commandName}`;

ratelimitManager.delete(key);
}
class BaseBotSubcommand {
container: Container = container;

#commandName: string;
readonly subcommandInstance: Subcommand;

constructor(subcommandInstance: Subcommand, commandName: string) {
this.#commandName = commandName;
this.subcommandInstance = subcommandInstance;
}

public resetCooldown(userId: string) {
const ratelimitManager = (
this.container.stores
.get("preconditions")
.get(
"PluginSubcommandCooldown",
) as PluginSubcommandCooldownPrecondition
).subcommandBuckets.get(this.subcommandInstance);

if (!ratelimitManager) return;

const key = `${userId}.${this.#commandName}`;

ratelimitManager.delete(key);
}

Did you find this page helpful?