Bejasc
Bejasc
Explore posts from servers
SIASapphire - Imagine a framework
Created by Bejasc on 11/2/2024 in #sapphire-support
Precondition not running
I've taken this from a working template project of mine, but I can't for the life of me figure out where this message is coming from.. it works in the template project, and checking the docs I can't see what I'm missing... I'm missing something stupid right? It's sitting in a preconditions folder, just the same The log message is not my own. 2024-11-02 22:59:01 - [WARN] preconditionUnavailable - The precondition "OwnerOnly" is not available.
import { Command, Precondition, PreconditionResult } from "@sapphire/framework";
import { Message } from "discord.js";
import { DrpgCommandRequest } from "../lib/structures/DrpgCommandRequest";
export class OwnerOnly extends Precondition {
public override async chatInputRun(interaction: Command.ChatInputCommandInteraction, command: Command) {
const request = new DrpgCommandRequest(interaction, command);
return this.checkOwner(request);
}

public override async messageRun(message: Message, command: Command) {
const request = new DrpgCommandRequest(message, command);
return this.checkOwner(request);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public override async contextMenuRun(interaction: Command.ContextMenuCommandInteraction) {
return this.ok();
}

private async checkOwner(request: DrpgCommandRequest): Promise<PreconditionResult> {
const isOwner = request.author.user.id == process.env.OWNER;
if (isOwner) return this.ok();
else return this.error({ message: `${request.author} - This command can only be used by <@${process.env.OWNER}>`, identifier: "Invalid Permission" });
}
}

declare module "@sapphire/framework" {
interface Preconditions {
OwnerOnly: never;
}
}
import { Command, Precondition, PreconditionResult } from "@sapphire/framework";
import { Message } from "discord.js";
import { DrpgCommandRequest } from "../lib/structures/DrpgCommandRequest";
export class OwnerOnly extends Precondition {
public override async chatInputRun(interaction: Command.ChatInputCommandInteraction, command: Command) {
const request = new DrpgCommandRequest(interaction, command);
return this.checkOwner(request);
}

public override async messageRun(message: Message, command: Command) {
const request = new DrpgCommandRequest(message, command);
return this.checkOwner(request);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public override async contextMenuRun(interaction: Command.ContextMenuCommandInteraction) {
return this.ok();
}

private async checkOwner(request: DrpgCommandRequest): Promise<PreconditionResult> {
const isOwner = request.author.user.id == process.env.OWNER;
if (isOwner) return this.ok();
else return this.error({ message: `${request.author} - This command can only be used by <@${process.env.OWNER}>`, identifier: "Invalid Permission" });
}
}

declare module "@sapphire/framework" {
interface Preconditions {
OwnerOnly: never;
}
}
@ApplyOptions<IDrpgCommandOptions>({
name: "forcelink",
shortDesc: "Link your RSI account for verrification",
preconditions: ["OwnerOnly"],
})
export class VerifyCommand extends DrpgCommand {
public override async registerApplicationCommands(registry: ApplicationCommandRegistry) {
registry.registerChatInputCommand((builder) =>
builder //
.setName("forcelink")

...
@ApplyOptions<IDrpgCommandOptions>({
name: "forcelink",
shortDesc: "Link your RSI account for verrification",
preconditions: ["OwnerOnly"],
})
export class VerifyCommand extends DrpgCommand {
public override async registerApplicationCommands(registry: ApplicationCommandRegistry) {
registry.registerChatInputCommand((builder) =>
builder //
.setName("forcelink")

...
5 replies
SIASapphire - Imagine a framework
Created by Bejasc on 7/25/2024 in #sapphire-support
Event before precondition OR access the command via MessageCommandDenied
Hi all, I have extended my Command Options to include a property shouldDeleteOnPost - which should delete the message once it has been posted. I'd like to do this in the most universal way possible - I thought using MessageCommandRun would have fired before any of the preconditions, but it seems it does not. When a precondition returns this.error, the code within MessageCommandRun does not fire. It fires if the precondition is passed, or does not exist. I'm not sure if there is an event that will fire before the precondition is run - pass or fail ? If there is, that's where I would put effectively something like the following
const drpgOptions = request.command.options as IDrpgCommandOptions;
if (drpgOptions.deleteAfterPost) request.message.delete();
const drpgOptions = request.command.options as IDrpgCommandOptions;
if (drpgOptions.deleteAfterPost) request.message.delete();
To get around this, I thought I would try doing effectively the same out of the MessageCommandAccepted and MessageCommandDenied events - which would have worked perfectly, until I realised that MessageCommandDenied doesn't appear to have any context of what the command itself actually was. How can I either... - Run some event before a precondition, pass or fail (preferred) - Access the command inside of MessageCommandDenied (unlikely?) Happy to hear other ideas of how I might achieve the goal of flagging some commands with shouldDeleteOnPost and deleting them, regardless of precondition status.
8 replies
SIASapphire - Imagine a framework
Created by Bejasc on 7/18/2024 in #sapphire-support
Hide Slash/Context menu commands
I have a number of Slash and Context Menu commands that I would like to hide, unless the user holds certain permissions. These permissions, I want to define by role (e.g, the 'Moderator' role). The commands should only be visible for users who have that specific role. I know that the commands can be maintained on a per guild status, in my instance, the bot only exists and only ever will exist in the context of a single guild - and the list of role IDs that need access are limited. I know that changing the commands through the Integration tab in Discord - it is possible to effectively hide the commands from those who should not have access. How can I do this at a code level, and by using a role?
7 replies
SIASapphire - Imagine a framework
Created by Bejasc on 6/2/2024 in #sapphire-support
Decorator issue
Having a weird issue with the decorators.. I've just updated all of my dependencies to the absolute latest version, but the error is still persisting here. It seems to be quite random, but there are steps I can take every time to get the error. Note that I've always had "experimentalDecorators": true, as part of my tsconfig - everything I've seen so far suggests adding that. When I remove "experimentalDecorators": true,, the same error appears across every file, not just the one file with the issue. Notably, when I see the issue, I also have a bunch of other errors (pointing to the discord.js package inside nodemodules, something about ECMAScript 2015, and a whole bunch of errors across other files that aren't normally there. Also, the project compiles fine, the decorator throwing the issue is working as expecred, but the whole project is going mental with errors that it's never reported before. To reproduce this, I; - Copy an existing file - Rename the file/class name Then, I see the error reported on the decorator, Then, several other errors appear
Unable to resolve signature of class decorator when called as an expression.
The runtime will invoke the decorator with 2 arguments, but the decorator expects 1.ts(1238)
Unable to resolve signature of class decorator when called as an expression.
The runtime will invoke the decorator with 2 arguments, but the decorator expects 1.ts(1238)
14 replies
SIASapphire - Imagine a framework
Created by Bejasc on 2/3/2024 in #sapphire-support
Listener run() signature
Feel like I'm missing something obvious.. how do I find out the correct signature for a run method of any given listener - they're different for each event. I'm sure there's an obvious way to get this, without trial and error and debugging to see what's available to it - where am I not looking?
import { Events, Listener, ListenerOptions } from "@sapphire/framework";

import { logger } from "@repo/logger";

export class GuildCreateListener extends Listener {
public constructor(context: Listener.LoaderContext, options?: ListenerOptions) {
super(context, {
...options,
event: Events.GuildCreate,
once: true,
});
}

public run(): void { //??
const guildName = "???"; //??
logger.info(`The bot has been added to a new server! ${guildName}`, "Bot Added");
}
}
import { Events, Listener, ListenerOptions } from "@sapphire/framework";

import { logger } from "@repo/logger";

export class GuildCreateListener extends Listener {
public constructor(context: Listener.LoaderContext, options?: ListenerOptions) {
super(context, {
...options,
event: Events.GuildCreate,
once: true,
});
}

public run(): void { //??
const guildName = "???"; //??
logger.info(`The bot has been added to a new server! ${guildName}`, "Bot Added");
}
}
12 replies
SIASapphire - Imagine a framework
Created by Bejasc on 9/29/2023 in #discordjs-support
Authenticated Attachment URLs
Hi guys, For many areas of my bot, embeds access images that are posted into an #assets channel either by me or by select others in my community. The CDN links for these are stored in mongo and referenced again and again, as the embed is sent. They are only used and accessed within the discord client itself. Does anyone have any more insight, confirmation, or thoughts on what the long term feasibility of this is? Reading the announcement and all I can find, I don't know what to expect for the links hosted in this way. The client behavior is not changing and will refresh posted URLs to be automatically valid, so your app doesn't need to worry about refreshing URLs itself if the link was valid at the time of posting.
7 replies
SIASapphire - Imagine a framework
Created by Bejasc on 8/14/2023 in #sapphire-support
string option, support for strings inside quotes?
Feel a bit silly making a thread out of this as it's a question rather than a support topic I have read that using args.pick("string") will match the next singular word, or the phrase contained "within quotes". I gather that options and flags are generally used for numerical or bool like inputs, but when reading the value of a flag, it does not behave the same way. ie
^test "Hello World" --flag="This is a test"
const input = await args.pick("string"); //Hello World
const flag = args.getOption("flag"); //"This
const input = await args.pick("string"); //Hello World
const flag = args.getOption("flag"); //"This
Is there any support for phrases at a flag level?
17 replies
SIASapphire - Imagine a framework
Created by Bejasc on 8/12/2023 in #sapphire-support
contextMenuRun not firing
No description
8 replies
SIASapphire - Imagine a framework
Created by Bejasc on 7/1/2023 in #sapphire-support
Combine Precondition and object available on the Command
Not sure if I've put that the right way, but I'm sure that the example will make some sense. I have this precondition that works.
export class OnlyAlive extends Precondition {
public override async chatInputRun(interaction: Command.ChatInputCommandInteraction, command: Command) {
const request = new DrpgCommandRequest(interaction, command);
return this.checkAlive(request);
}

public override async messageRun(message: Message, command: Command) {
const request = new DrpgCommandRequest(message, command);
return this.checkAlive(request);
}

private async checkAlive(request: DrpgCommandRequest): Promise<PreconditionResult> {
const character = await CharacterService.getCharacter(request.author);
if (character) {
//Ideally - right here - I would set some property that I can access inside the command
return this.ok();
} else {
const e = Swrpg.warnCharacterNotFound(request.author, request);
return this.error({ message: e.data.description, identifier: e.data.title });
}
}
}
export class OnlyAlive extends Precondition {
public override async chatInputRun(interaction: Command.ChatInputCommandInteraction, command: Command) {
const request = new DrpgCommandRequest(interaction, command);
return this.checkAlive(request);
}

public override async messageRun(message: Message, command: Command) {
const request = new DrpgCommandRequest(message, command);
return this.checkAlive(request);
}

private async checkAlive(request: DrpgCommandRequest): Promise<PreconditionResult> {
const character = await CharacterService.getCharacter(request.author);
if (character) {
//Ideally - right here - I would set some property that I can access inside the command
return this.ok();
} else {
const e = Swrpg.warnCharacterNotFound(request.author, request);
return this.error({ message: e.data.description, identifier: e.data.title });
}
}
}
I've used it to replace a bunch of code that I had in several commands that was similar to
const character = await CharacterService.getCharacter(request.author);
if(!character) return Logger.Error("No Character");

//Do something with the character object
const character = await CharacterService.getCharacter(request.author);
if(!character) return Logger.Error("No Character");

//Do something with the character object
The key to my question is on that last line above.... do something with the character object. To do this when I need to, it means that I need to use CharacterService.getCharacter a second time - which if possible, I'd like to avoid. Almost all of my commands have a context of the character. The reason I have it as a precondition is so that I can selectively apply it to the commands, as not all of them need the character. But enough of them do, that if I can abstract that character up into the command itself, it'll be useful.
Can I do something at the precondition level, to assign something, so that in the command itself, I will have access to the character?
Otherwise, is there some better way to do this, that is not a precondition?
6 replies
SIASapphire - Imagine a framework
Created by Bejasc on 6/25/2023 in #sapphire-support
Get the value that was attempted to be parsed in an Arg
Hiya folks, I have the following code, where I determine if the arg is either of my custom type (working), or if it's a server member. If it is neither, I want to throw out an error that I can handle, but as part of handling that error, I'd like to know the value that was attempted to be parsed.
battleArgs.combatant2 = await args.pick(DrpgNpcArg).catch(
async (e1) =>
await args.pick("member").catch((e2) => {
throw new Error(`${value} did not rsolve to an NPC or a Character.`);
}),
);
battleArgs.combatant2 = await args.pick(DrpgNpcArg).catch(
async (e1) =>
await args.pick("member").catch((e2) => {
throw new Error(`${value} did not rsolve to an NPC or a Character.`);
}),
);
in this example, value (inside the Error) doesn't exist, but I think it's pretty clear what I'm trying to do. I know that I could make an arg type that returns either an Npc or a Guild Member, and use parameter in the Args.error similar to what I do here... But I'd rather not have to make a brand new arg type for trying to do something like this. Any pointers?
17 replies
SIASapphire - Imagine a framework
Created by Bejasc on 12/28/2022 in #sapphire-support
Use commands from src and also from custom package in node_modules (Programatically add commands?)
There's probably a better way to do this, but wrapping my head around how I want to approach it. I'm working on a variety of smaller NPM packages that I intend to use across a bunch of different bots/projects. One of these packages, I want to have a debug command - without having to specify what that command is/does inside of each project - I only want to specify it once at the package level, as all projects will use it the same. For this package, it exports a class, that is instantiated immediately after the project connects to the client. When this class is initialized, it takes in the SapphireClient - so I assume I can do something with this from here. |-- node_modules |-- |-- package |-- |-- |-- Commands |-- src |-- | --Commands If I have the client object - what's the best approach I should take, to be able to run commands from the package and commands from the src?
11 replies
SIASapphire - Imagine a framework
Created by Bejasc on 12/24/2022 in #sapphire-support
Custom Arg - ResultError Unwrap failed
This seemed to be working for me in a previous version of Sapphire - but I've only recently gotten back into the swing of things, so there's a good chance I've missed a change. I have a prefix command that has a single arg. The custom arg works great, for returning a result where a result actually does exist (items from item system) Where that item doesn't exist, it throws an error with the identifier "InputNotanItem". When this was working - the first .catch only returned null (which is also a valid response). I've added this catch to try and catch something earlier than my outer try/catch, in case there's some issue there. I can see that the err does include identifier and parameter, but I cannot read these values - I'm assuming it has something to do with this.. ResultError: UnwrapFailed
ResultError: Unwrap failed
at Err.unwrap (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\result\src\lib\Result\Err.ts:108:2)
at Args.rest (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\lib\parsers\Args.js:67:19)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at MarketInfoCommand.messageRun (F:\Discord Bots\swrpg-bot-v2\src\commands\Market\MarketInfoCommand.ts:42:4)
at async F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\optional-listeners\message-command-listeners\CoreMessageCommandAccepted.js:20:23
at Object.fromAsync (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\result\src\lib\Result.ts:55:2)
at async CoreListener.run (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\optional-listeners\message-command-listeners\CoreMessageCommandAccepted.js:17:22)
at Object.fromAsync (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\result\src\lib\Result.ts:55:2)
at async CoreListener._run (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\lib\structures\Listener.js:48:22) {
value: ArgumentError: `aaa`was not recognized as an Item.
at Function.error (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\lib\parsers\Args.js:174:30)
at Object.run (F:\Discord Bots\swrpg-bot-v2\src\lib\arguments\DrpgItemArg.ts:18:14)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Args.restResult (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\lib\parsers\Args.js:55:20)
at async Args.rest (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\lib\parsers\Args.js:66:20)
at MarketInfoCommand.messageRun (F:\Discord Bots\swrpg-bot-v2\src\commands\Market\MarketInfoCommand.ts:42:4)
at async F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\optional-listeners\message-command-listeners\CoreMessageCommandAccepted.js:20:23
at Object.fromAsync (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\result\src\lib\Result.ts:55:2)
at async CoreListener.run (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\optional-listeners\message-command-listeners\CoreMessageCommandAccepted.js:17:22)
at Object.fromAsync (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\result\src\lib\Result.ts:55:2) {
identifier: 'InputNotAnItem',
context: null,
argument: { run: [AsyncFunction (anonymous)], name: '' },
parameter: 'aaa'
}
}
ResultError: Unwrap failed
at Err.unwrap (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\result\src\lib\Result\Err.ts:108:2)
at Args.rest (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\lib\parsers\Args.js:67:19)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at MarketInfoCommand.messageRun (F:\Discord Bots\swrpg-bot-v2\src\commands\Market\MarketInfoCommand.ts:42:4)
at async F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\optional-listeners\message-command-listeners\CoreMessageCommandAccepted.js:20:23
at Object.fromAsync (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\result\src\lib\Result.ts:55:2)
at async CoreListener.run (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\optional-listeners\message-command-listeners\CoreMessageCommandAccepted.js:17:22)
at Object.fromAsync (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\result\src\lib\Result.ts:55:2)
at async CoreListener._run (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\lib\structures\Listener.js:48:22) {
value: ArgumentError: `aaa`was not recognized as an Item.
at Function.error (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\lib\parsers\Args.js:174:30)
at Object.run (F:\Discord Bots\swrpg-bot-v2\src\lib\arguments\DrpgItemArg.ts:18:14)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Args.restResult (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\lib\parsers\Args.js:55:20)
at async Args.rest (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\lib\parsers\Args.js:66:20)
at MarketInfoCommand.messageRun (F:\Discord Bots\swrpg-bot-v2\src\commands\Market\MarketInfoCommand.ts:42:4)
at async F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\optional-listeners\message-command-listeners\CoreMessageCommandAccepted.js:20:23
at Object.fromAsync (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\result\src\lib\Result.ts:55:2)
at async CoreListener.run (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\framework\dist\optional-listeners\message-command-listeners\CoreMessageCommandAccepted.js:17:22)
at Object.fromAsync (F:\Discord Bots\swrpg-bot-v2\node_modules\@sapphire\result\src\lib\Result.ts:55:2) {
identifier: 'InputNotAnItem',
context: null,
argument: { run: [AsyncFunction (anonymous)], name: '' },
parameter: 'aaa'
}
}
36 replies
SIASapphire - Imagine a framework
Created by Bejasc on 12/14/2022 in #sapphire-support
Read property from custom Command Options
Hey folks, I have a custom command options object, that contains a property called shortDesc. I want to read a property from the options, inside the registerApplicationCommands method, so that I can not have to provide this value (Get a random trivia question) twice. How do I read shortDesc? this.shortDesc didn't work the same as this.name did - I assume because name exists on the default CommandOptions object, but it's having trouble parsing the Options here as IDrpgCommandOptions ?
@ApplyOptions<IDrpgCommandOptions>({
name: "Trivia",
aliases: ["q", "t", "question", "trivia", "quiz"],
shortDesc: "Get a random trivia question",
showInHelpMenu: true,
})
export class QuestionCommand extends DrpgCommand {
public override async registerApplicationCommands(registry: ApplicationCommandRegistry) {
const shortDesc = "Get a random trivia question"; //TODO How to get shortDesc from above?
registry.registerChatInputCommand((builder) => builder.setName(this.name).setDescription(shortDesc), {
idHints: [""],
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
});
}
@ApplyOptions<IDrpgCommandOptions>({
name: "Trivia",
aliases: ["q", "t", "question", "trivia", "quiz"],
shortDesc: "Get a random trivia question",
showInHelpMenu: true,
})
export class QuestionCommand extends DrpgCommand {
public override async registerApplicationCommands(registry: ApplicationCommandRegistry) {
const shortDesc = "Get a random trivia question"; //TODO How to get shortDesc from above?
registry.registerChatInputCommand((builder) => builder.setName(this.name).setDescription(shortDesc), {
idHints: [""],
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
});
}
26 replies