Decorators Message Context?

The decorators don't really say, can I use them on top of an interaction? If not, what's the recommended flow to use the decorators e.g. RequiresGuildContext() on things like a command rather than a subcommand?
45 Replies
ZachHandley
ZachHandley3mo ago
also is there any advantage to using shapeshift over zod? e.g.
export class ConfigCommand extends Command {
guildSupportChannels: Map<string, Channel> = new Map();
guildSettingsEmbeds: Map<string, EmbedBuilder> = new Map();
guildPermittedRoles: Map<string, string[]> = new Map();
guildSupportEnabled: Map<string, boolean> = new Map();
guildCustomUrls: Map<string, string[]> = new Map();

public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) =>
builder //
.setName(this.name)
.setDescription(this.description)
);
}

public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
const curChannel = interaction.channel;
if (curChannel?.type === ChannelType.GuildText && interaction.guild !== null) {
await this.updateSettingsEmbed(interaction);
} else {
await interaction.reply({ content: 'This command can only be used in a text channel in a guild', ephemeral: true });
}
}

@RequiresGuildContext()
@RequiresUserPermissions(['Administrator', 'ManageGuild'])
private async updateSettingsEmbed(interaction: Command.ChatInputCommandInteraction) {
export class ConfigCommand extends Command {
guildSupportChannels: Map<string, Channel> = new Map();
guildSettingsEmbeds: Map<string, EmbedBuilder> = new Map();
guildPermittedRoles: Map<string, string[]> = new Map();
guildSupportEnabled: Map<string, boolean> = new Map();
guildCustomUrls: Map<string, string[]> = new Map();

public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) =>
builder //
.setName(this.name)
.setDescription(this.description)
);
}

public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
const curChannel = interaction.channel;
if (curChannel?.type === ChannelType.GuildText && interaction.guild !== null) {
await this.updateSettingsEmbed(interaction);
} else {
await interaction.reply({ content: 'This command can only be used in a text channel in a guild', ephemeral: true });
}
}

@RequiresGuildContext()
@RequiresUserPermissions(['Administrator', 'ManageGuild'])
private async updateSettingsEmbed(interaction: Command.ChatInputCommandInteraction) {
Favna
Favna3mo ago
RequiresGuildContext requires the decorated method's first parameter to be a Message. Note to self add support for Interaction class. Anyway that's your answer.
ZachHandley
ZachHandley3mo ago
copy that, okay, so
Favna
Favna3mo ago
And subcommands can have preconditions including runIn to limit it to a guild
ZachHandley
ZachHandley3mo ago
if I make the first parameter the message of the interaction, does that count or no? yeah I figured out the subcommands part, but I saw the requirement for message -- just wondering if I can finagle it cause the types for user.permissions is weird string | ReadOnly<PermissionsBitField>
Favna
Favna3mo ago
No. In JavaScript arguments are as provided when the method is called. You can't just type an arg as something else and it magically works.
ZachHandley
ZachHandley3mo ago
no I meant, if I changed my updateSettingsEmbed to private async updateSettingsEmbed(message: Message, interaction: Command.ChatInputCommandInteraction) {
Favna
Favna3mo ago
Oh yeah you could but I would recommend making a PR to support interactions instead
ZachHandley
ZachHandley3mo ago
copy that, will do now
Favna
Favna3mo ago
Much better and helps everyone and saves me work too :kekw: I just forgot to add it ages ago And kept forgetting
ZachHandley
ZachHandley3mo ago
lol you know I love the package too much and will do it oh no can you point me to the right dir can find it, just lazy meh one sec
Favna
Favna3mo ago
GitHub
utilities/packages/decorators/src/djs-decorators.ts at main · sapph...
Common JavaScript utilities for Sapphire Projects. Contribute to sapphiredev/utilities development by creating an account on GitHub.
ZachHandley
ZachHandley3mo ago
found it hehe ty probably 20 minutes or so
Favna
Favna3mo ago
Tip: yarn install in the mono repo then yarn build because decorators depends on packages within the workspace No rush
ZachHandley
ZachHandley3mo ago
I know I'm all about dat efficiency
Favna
Favna3mo ago
Out for dinner anyway so can't really release anyway
ZachHandley
ZachHandley3mo ago
one quick question, you probably know this better than me
Favna
Favna3mo ago
Same :dragonheart:
ZachHandley
ZachHandley3mo ago
anything other than Chat.ChatInputCommandInteraction? that I should extend it to cover
Favna
Favna3mo ago
BaseInteraction from djs catches all
ZachHandley
ZachHandley3mo ago
oh lit thank you
Favna
Favna3mo ago
The others extend that class
ZachHandley
ZachHandley3mo ago
you prefer yarn over bun?
Favna
Favna3mo ago
discord.js
discord.js
discord.js is a powerful Node.js module that allows you to interact with the Discord API very easily. It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend.
Favna
Favna3mo ago
I think bun has potential (unlike deno :kekw: ) but it doesn't have an equivalent of publishing packages yet so it's irrelevant for libraries
ZachHandley
ZachHandley3mo ago
yeah true, I mean it does drop-in-replace NodeJS and it's webserver is like 900 times faster like if you're using Node WebSockets you should switch cause their websockets are like 98 times faster or some stupid number and native
Favna
Favna3mo ago
I tried bun here but I was stuck on publishing https://github.com/favware/graphql-pokemon/tree/refactor/switch-to-bun
ZachHandley
ZachHandley3mo ago
which part? workflow or docker?
Favna
Favna3mo ago
Workflow That project is fully automated Releases are done from GH workflow As well as docker image updates and updating the docker container on my vps
ZachHandley
ZachHandley3mo ago
the automatic data update? cause that's still running yarn in the errored command I see
ZachHandley
ZachHandley3mo ago
No description
Favna
Favna3mo ago
Continuous deployment Also yes I just found that out ffs I made some typo somewhere in the script Cry
ZachHandley
ZachHandley3mo ago
hahahaha all good man the amount of times my bugs are the dumbest thing it looks like your script is stopping for some reason on Generate JavaScript Companion library is there some error throwing? It looks like it's just skipping
Favna
Favna3mo ago
It's been broken for a while because bulbapedia (the website I scrape) added cloudflare captcha for a while and even flaresolverr couldn't flawlessly get by it. Now they disabled the captcha again so the script finally got to the last few lines but now there's a bug in writing the output
ZachHandley
ZachHandley3mo ago
hehehe I have a Browserless up that gets past all of that
Favna
Favna3mo ago
Oh normally it's perfect https://github.com/FlareSolverr/FlareSolverr But query it over a 1000 times in rapid succession....
GitHub
GitHub - FlareSolverr/FlareSolverr: Proxy server to bypass Cloudfla...
Proxy server to bypass Cloudflare protection. Contribute to FlareSolverr/FlareSolverr development by creating an account on GitHub.
ZachHandley
ZachHandley3mo ago
Lol they just released Pingora, their rust based API framework, maybe with that knowledge you can make it even better I just use a Playwright browser
ZachHandley
ZachHandley3mo ago
GitHub
GitHub - Skyvern-AI/skyvern: Automate browser-based workflows with ...
Automate browser-based workflows with LLMs and Computer Vision - Skyvern-AI/skyvern
Favna
Favna3mo ago
Idk. Maybe if I run into the issue again but I hope they'll keep captcha off now It was because they noticed an increased network activity over November December relates to DDOS Who would want to DDOS bulbapedia Despots is2g
ZachHandley
ZachHandley3mo ago
this look good?
export const RequiresClientPermissions = (...permissionsResolvable: PermissionResolvable[]): MethodDecorator => {
const resolved = new PermissionsBitField(permissionsResolvable);
const resolvedIncludesServerPermissions = Boolean(resolved.bitfield & DMAvailablePermissions.bitfield);

return createFunctionPrecondition((context: Message | BaseInteraction) => {
if (context instanceof Message) {
if (resolvedIncludesServerPermissions && isDMChannel(context.channel)) {
throw new UserError({
identifier: DecoratorIdentifiers.RequiresClientPermissionsGuildOnly,
message: 'Sorry, but that command can only be used in a server because I do not have sufficient permissions in DMs'
});
}

if (isGuildBasedChannel(context.channel)) {
const missingPermissions = context.channel.permissionsFor(context.guild!.members.me!).missing(resolved);

if (missingPermissions.length) {
throw new UserError({
identifier: DecoratorIdentifiers.RequiresClientPermissionsMissingPermissions,
message: `Sorry, but I am not allowed to do that. I am missing the permissions: ${missingPermissions}`,
context: {
missing: missingPermissions
}
});
}
}
} else {
if (resolvedIncludesServerPermissions && isDMChannel(context.channel)) {
throw new UserError({
identifier: DecoratorIdentifiers.RequiresClientPermissionsGuildOnly,
message: 'Sorry, but that command can only be used in a server because I do not have sufficient permissions in DMs'
});
}

if (context.channel && isGuildBasedChannel(context.channel)) {
const missingPermissions = context.channel.permissionsFor(context.guild!.members.me!).missing(resolved);

if (missingPermissions.length) {
throw new UserError({
identifier: DecoratorIdentifiers.RequiresClientPermissionsMissingPermissions,
message: `Sorry, but I am not allowed to do that. I am missing the permissions: ${missingPermissions}`,
context: {
missing: missingPermissions
}
});
}
}
}

return true;
});
};
export const RequiresClientPermissions = (...permissionsResolvable: PermissionResolvable[]): MethodDecorator => {
const resolved = new PermissionsBitField(permissionsResolvable);
const resolvedIncludesServerPermissions = Boolean(resolved.bitfield & DMAvailablePermissions.bitfield);

return createFunctionPrecondition((context: Message | BaseInteraction) => {
if (context instanceof Message) {
if (resolvedIncludesServerPermissions && isDMChannel(context.channel)) {
throw new UserError({
identifier: DecoratorIdentifiers.RequiresClientPermissionsGuildOnly,
message: 'Sorry, but that command can only be used in a server because I do not have sufficient permissions in DMs'
});
}

if (isGuildBasedChannel(context.channel)) {
const missingPermissions = context.channel.permissionsFor(context.guild!.members.me!).missing(resolved);

if (missingPermissions.length) {
throw new UserError({
identifier: DecoratorIdentifiers.RequiresClientPermissionsMissingPermissions,
message: `Sorry, but I am not allowed to do that. I am missing the permissions: ${missingPermissions}`,
context: {
missing: missingPermissions
}
});
}
}
} else {
if (resolvedIncludesServerPermissions && isDMChannel(context.channel)) {
throw new UserError({
identifier: DecoratorIdentifiers.RequiresClientPermissionsGuildOnly,
message: 'Sorry, but that command can only be used in a server because I do not have sufficient permissions in DMs'
});
}

if (context.channel && isGuildBasedChannel(context.channel)) {
const missingPermissions = context.channel.permissionsFor(context.guild!.members.me!).missing(resolved);

if (missingPermissions.length) {
throw new UserError({
identifier: DecoratorIdentifiers.RequiresClientPermissionsMissingPermissions,
message: `Sorry, but I am not allowed to do that. I am missing the permissions: ${missingPermissions}`,
context: {
missing: missingPermissions
}
});
}
}
}

return true;
});
};
Favna
Favna3mo ago
Can you dump it on https://hastebin.skyra.pw instead? I'm on mobile so that's a bit hard to read (no syntax highlighting, wrapping)
ZachHandley
ZachHandley3mo ago
just swapped the message for context and checked the class instnace yeh https://hastebin.skyra.pw/okuzipojib.ts that's just client perms just as an example of what i had in mind
Favna
Favna3mo ago
Deduplicate it, no instanceof check like that.
ZachHandley
ZachHandley3mo ago
okaydokey fixed not gonna use that check lol, but that's the format https://hastebin.skyra.pw/zanagaviyi.ts I'll run tests and pull