Giter VIP home page Giter VIP logo

framework's Introduction

Sapphire Logo

Sapphire

A pleasant Discord Bot framework

GitHub codecov npm

Support Server


Description

Sapphire is a Discord bot framework built on top of discord.js for advanced and amazing bots.

Features

  • Written in TypeScript
  • Command Handler, Arguments, Pre-conditions and Listeners Store
  • Completely Modular and Extendable
  • Advanced Plugins Support
  • Supports many plugins
  • Full TypeScript & JavaScript support

Installation

@sapphire/framework depends on the following packages. Be sure to install these along with this package!

You can use the following command to install this package, or replace npm install with your package manager of choice.

npm install @sapphire/framework [email protected]

Buy us some doughnuts

Sapphire Community is and always will be open source, even if we don't get donations. That being said, we know there are amazing people who may still want to donate just to show their appreciation. Thank you very much in advance!

We accept donations through Open Collective, Ko-fi, Paypal, Patreon and GitHub Sponsorships. You can use the buttons below to donate through your method of choice.

Donate With Address
Open Collective Click Here
Ko-fi Click Here
Patreon Click Here
PayPal Click Here

Contributors

Please make sure to read the Contributing Guide before making a pull request.

Thank you to all the people who already contributed to Sapphire!

framework's People

Contributors

adiologydev avatar alcremie avatar allcontributors[bot] avatar c43721 avatar cfanoulis avatar demonwayne avatar dependabot[bot] avatar depfu[bot] avatar favna avatar feefs avatar feralheart avatar imranbarbhuiya avatar kyranet avatar lavgup avatar leonardssh avatar lioness100 avatar megatank58 avatar nicklvh avatar noftaly avatar nytelife26 avatar porousedsnax avatar quantumlyy avatar r-priyam avatar realshadownova avatar renovate[bot] avatar sawa-ko avatar soumil-07 avatar stitch07 avatar vladfrangu avatar yuansheng1549 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

framework's Issues

RFC: Sapphire directory and automatic TypeScript augmentation support

Preface: this RFC (Request For Comments) is meant to gather feedback and opinions for the feature set and some implementation details for @sapphire/framework! While a lot of the things mentioned in this RFC will be opinionated from my side (like what I’d like to see implemented in the module), not everything might be added in the initial version or some extra things might be added as extras.

What is the Sapphire directory?

Some frameworks, such as Nuxt (.nuxt) and Next.js (.next) define an autogenerated directory, this contains types, metadata, and some other artifacts that greatly enhance the user experience. We can follow their convention and implement .sapphire, but we also may use node_modules/.sapphire instead, which is what Prisma does with node_modules/.prisma/client, which seems more suitable for bridge code, explained later in this proposal.

Since Sapphire Framework is a very strict typed library that aims for type safety to avoid silly mistakes such as typos, we require some of the stores to be augmented into the framework's types, this is remarked for preconditions and arguments, however, for ease of use, commands and listeners are not included, which leads to inconsistent type safety, this is done mostly because manually defining a list of commands (and aliases) can be incredibly bothersome and prone to issues.

An ideal solution to this would be similar to Nuxt v3's auto-imports, which generates a TypeScript Definitions file (.d.ts).

CLI

This utility can be integrated with the official Sapphire CLI, and further enhance it since it won't be limited to a single file.

For simplicity, I believe it may be worth checking whether or not we can bring a part of the CLI inside Framework. And if not, a way to extend the CLI with extra packages, so installing @sapphire/cli-examples gives us sapphire examples. We may need the core built-in because of sapphire generate, which will be required for generating bridge and auto-augmentation code.

Large type presets

Big words! The numbers Mason, what do they mean?

Remember when we said Sapphire v2 would be library agnostic? We're at v3 now, and v4 doesn't look like it'll be. Instead, we're attaching to Discord.js more than ever. This harms our future ability to detach and support raw libraries (@discordjs/ws, @discordjs/rest), other libraries (eris), ecosystems (HTTP), or even runtimes (Deno, CF Workers...). And we're doing this deliberately.

But, what EXACTLY is stopping us from being library agnostic?

Types.

Back in v1, when we were the closest we ever were to library agnosticity, Sapphire was straight incredibly low-level and required a lot of usage gotchas, it wasn't your typical framework. Frameworks are made to simplify things, but as far as v1 went, it felt more like an enterprise library that needed to be extended with one's own micro-framework. This is not what users want to do, and is why v2 is designed the way it was.

How would the Sapphire directory fix this?

Simple, not only it can augment the framework with auto-generated user types, it can also solve the aforementioned issues by automatically injecting compatible code and types as a bridge. Imagine sapphire.config.js, where we define library: 'eris', and it changes the Sapphire bridge to use Eris's types instead of Discord.js's. Pieces such as preconditions and arguments would also be replaced to use Eris-compatible pieces rather than Discord.js-compatible ones, and the bridge's automatic type augmentations simplifies many things even further by ensuring that the user doesn't have to do import '@sapphire/framework/register-eris'; or similar to have the types registered, which also would have the gotcha of requiring the file first before anything else.

Where would the presets be at?

We can make packages such as @sapphire/framework-preset-discord.js that include the Discord.js-compatible pieces, as well as bridge types.

Alternative Sapphire directory

We may use node_modules/.sapphire if the bridge code is injected here (most likely will be), but we can alternatively also use .sapphire at the root directory. This can be configurable too.

Compatibility matrix

Just like we can define library in the sapphire.config.js, we can also define the runtime, accepting 'node' | 'deno' and others (such as 'cloudflare-workers'), but we might also want to add a compatibility matrix so the libraries that can be used per runtime are limited, e.g. library: 'discord.js' may not work with runtime: 'cloudflare-workers'.

Command modes

Sapphire v3 features a very large set of types designed to support both message-based commands and slash commands, however, this bloats IntelliSense and makes components such as preconditions less type-safe since we have to define the handlers as optional rather than abstract. There is AllFlowsPrecondition but it's more of an unsafe workaround, as the methods may be renamed and TypeScript won't warn the user about it, resulting on poorer safety. With the conditional type system (which also works like Rust's #[cfg(feature = "...")] at this point), we can define which methods should be abstract by defining an array (commands: ['messages', 'chat-input', 'context-menu']). This feature would also extend to commands.

Enabling 'messages' in this option will also make message-command-listeners be loaded, without extra code.

Disabling 'chat-input' would make Framework not load the chat-input directory, likewise 'context-menu' with the context-menu one.

Conditional loading

Sapphire v3 defines default error listeners which can be opt-out, however, not all users want to disable all the listeners, just a few.

Configuring this should be with load.optional.listeners.errors: boolean | ('ChatInputCommand' | 'CommandApplicationCommandRegistry')[]..., omitting the Core prefix and Error suffix.

For non-optional, we can define load.arguments, load.listeners, and load.preconditions, both taking either a boolean or an array with the names of the pieces to load.

The config file

Referring to the sapphire.config.js, it may import defineSapphireConfiguration from @sapphire/framework or auto-import it, something that is possible with unjs/jiti used by Nuxt v3 RC10 to auto-import defineNuxtConfig in the configuration file.

Furthermore, we may also support:

  • sapphire.config.mts (ESM TypeScript)
  • sapphire.config.mjs (ESM JavaScript)
  • sapphire.config.cts (CJS TypeScript)
  • sapphire.config.cjs (CJS TypeScript)
  • sapphire.config.ts
  • sapphire.config.js
  • sapphire.config.yaml (should we? Transform plugin?)
  • sapphire.config.json (should we? Transform plugin?)

Defaults

The file's defaults may be the closest as v3's defaults for ease of migration.

bug: missing required args causes an "unwrap failed" error

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

When a command requires arguments to be provided but they aren't when running the command, an error will be thrown stating "unwrap failed".

Error for reference:

2022-09-06 22:39:07 - ERROR - [COMMAND ERROR] test (./dist/commands/System/Bot Owner/test.js)
2022-09-06 22:39:07 - ERROR - ResultError: Unwrap failed
2022-09-06 22:39:07 - ERROR -     at Err.unwrap (./node_modules/@sapphire/result/src/lib/Result/Err.ts:103:9)
2022-09-06 22:39:07 - ERROR -     at Args.rest (./node_modules/@sapphire/framework/src/lib/parsers/Args.ts:248:17)
2022-09-06 22:39:07 - ERROR -     at runMicrotasks (<anonymous>)
2022-09-06 22:39:07 - ERROR -     at processTicksAndRejections (node:internal/process/task_queues:96:5)
2022-09-06 22:39:07 - ERROR -     at UserCommand.messageRun (./src/commands/System/Bot Owner/test.ts:6:17)
2022-09-06 22:39:07 - ERROR -     at <anonymous> (./node_modules/@sapphire/framework/src/optional-listeners/message-command-listeners/CoreMessageCommandAccepted.ts:19:19)
2022-09-06 22:39:07 - ERROR -     at Object.fromAsync (./node_modules/@sapphire/result/src/lib/Result.ts:51:19)
2022-09-06 22:39:07 - ERROR -     at CoreListener.run (./node_modules/@sapphire/framework/src/optional-listeners/message-command-listeners/CoreMessageCommandAccepted.ts:15:18)
2022-09-06 22:39:07 - ERROR -     at Object.fromAsync (./node_modules/@sapphire/result/src/lib/Result.ts:51:19)
2022-09-06 22:39:07 - ERROR -     at CoreListener._run (./node_modules/@sapphire/framework/src/lib/structures/Listener.ts:124:18)

Steps To Reproduce

The error can be reproduced with the following command class:

import { Args, Command } from '@sapphire/framework';
import type { Message } from 'discord.js';

export class UserCommand extends Command {
  public async messageRun(message: Message, args: Args) {
    const arg = await args.rest('string');
    return message.reply(arg);
  }
}

Running the command without providing arguments results in the error.

Expected behavior

From previous experience with the framework prior to v3, I would expect some form of "missing arguments" error to be thrown instead, which appears like it is intended to through the code but I don't understand the args system enough to determine the root cause of this current error.

Screenshots

No response

Additional context

No response

request: use `declare`

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

A "problem" with overridable types. This one is just my personal solution to have accurate types wherever possible.

Command#piece appears to be Store<Piece<PieceOptions>> still when it should be CommandStore instead.

Desired solution

Ever considered using the declare modifier for class properties? Not much of a big deal anyway since the developer could just assert it to a specific store type but IMO it's helpful since it offers a shorter way of accessing the store that holds the piece.

Example:

// Command.ts
export class Command extends Piece {
  public declare store: CommandStore;
}
// help.ts
export default class extends Command {
  public override messageRun(message: Message) {
    const commands = [...this.store.values()]; // Command<Args, CommandOptions>[]
    console.log(commands.map(c => c.name));
  }
} 

Alternatives considered

Use the common way of obtaining a store.

container.stores.get('store');

Additional context

No response

request: e2e testing implementation

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

The idea is to implement maybe as a plugin or jest extension the possibility to test the bots functionality in end to end cases on a dedicated Discord Server.

Desired solution

Its just an idea so i dont have a real solution for that and i might not have the knowledge to sugest something but i love this framework and what you create here :3

Alternatives considered

Maybe a decoupled module for testing the bots commands in an integration testing way.

Additional context

Wanted to thank you for all your work, keep it up, you do and did a great job <3

request: preconditions

Is your feature request related to a problem? Please describe.

We need a built-in way to check whether a command may or may not be run.

Describe the solution you'd like

Since we cannot use Inhibitor nor its design for copyright reasons, I have thought we could use some inspiration from Discord.NET: Preconditions.

The idea of this is that you do not run all conditions on all commands, instead, you make conditions that check only one thing and stick to it.

For example, a NSFW precondition might have this logic:

public run(message: Message) {
  if (!message.channel.nsfw) throw new PreconditionError('You may not run this outside NSFW channels.');
}

The signature is at it follows:

run(message: Message, command: Command): unknown;

Promises will be supported and we may add an option so they're run either in parallel or sequential.

Any error thrown that is not an instance of PreconditionError will result on preconditionError to be called and stopping the command from being run.

Defining them in the commands would be similar to this:

{
  preconditions: ['nsfw', 'moderator']
}

This system allows us to ditch more complex systems that come with more limitations, included but not limited to: permission levels.

This also simplifies checks, resulting on better fragmentation of the code, more options for the developers (they may add systems such as permission levels or permission nodes), and higher performance overall.

Describe alternatives you've considered

Add inhibitors and a copyright banner to comply with MIT license.

Additional context

N/a.

bug: Why ban me? ;c

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

I banned randomly from server

Steps To Reproduce

  1. Chat normally
  2. GET BANNED

Expected behavior

No ban!?

Screenshots

No response

Additional context

No response

request: Can we have a support for discord.js-light

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

Recently, I tried using discord.js-light with sapphire but it's almost impossible without the dev's help.

Desired solution

We might get @sapphire/light as a separate package.

Alternatives considered

using default cache customization that djs provide

Additional context

No response

bug: setting `dm_permission` on guildonly command breaks difference

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

If one would accidentally set dm_permission: false or .setDMPermission(false) on a guild only command, the difference function always detects this command as changed even though it is not the case.

This is caused by Discord returning null or undefined on dm_permission.

Discord converstation reference: https://discord.com/channels/737141877803057244/737142503043498015/1011913466438754355

Steps To Reproduce

  1. Create an application command like so:
@ApplyOptions<ChatInputCommand.Options>({
  name: 'test',
  description: 'test command',
})
export default class extends Command {
  public override registerApplicationCommands(
    registry: ChatInputCommand.Registry
  ) {
    registry.registerChatInputCommand(
      (builder) =>
        builder
          .setName(this.name)
          .setDescription(this.description)
          .setDMPermission(false),

      {
        guildIds: getGuildIds(),
      }
    );
  }

  public override async chatInputRun() {
    this.container.logger.debug('I am a test command');
  }
}
  1. Start bot and let it register
  2. Stop bot
  3. Restart bot

Expected behavior

The difference should take in account someone may set dm_permission in a guildonly command, be it by accident or due to ignorance and handle it appropriately and not always detect the command as changed.

Screenshots

image

Additional context

No response

request: RateLimit Event Listeners

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

A way to RateLimit event listeners will be very Useful. for further Explanation: Whenever An event X is triggered, a specific cooldown will be implemented and the Event will not be triggered until the cooldown is over.

Desired solution

A really simple way will be to add a new option to ListenerOptions Interface, called as CooldownTime, this will simply take an integer, or maybe a float.. but its usage will be as follows it will implement a cooldown on the event after the certain Event is ran..

Alternatives considered

A simple method that I used was literally removing the Listener starting the ratelimit then adding the Listener back after the cooldown is over. We can even make it work by using @sapphire/ratelimits

Additional context

Also we can implement more things within a listener such as it will only be ran when a specific user triggers the event, or in a specific guild or channel

request: update `Args` to support Chat Input commands

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

When we first wrote Sapphire we added support for Arguments for message-based commands, however, they were never updated for Chat Input commands in version 3 as we had to cut it in order to be able to ship v3 sooner. That means it is moved to the scope of v4 and this issue is to track this feature.

Desired solution

Arguments has to be updated to support Chat Input commands

Alternatives considered

  • Only using the resolvers for Chat Input commands. As resolvers already make the backbone of Arguments and Chat Input command arguments are only really relevant for String type Chat Input commands the string content could easily be parsed by resolvers.
    • This is a valid argument, however supporting Arguments will allow more syntactic sugar and allow for less code to achieve the same.,

Additional context

No response

bug: Cannot find namespace "ChatInputCommand" from example code in guide

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

While creating a slash command following the guide. I get the error Cannot find namespace 'ChatInputCommand'.

Steps To Reproduce

Create Typescript project with sapphire

import { Command } from '@sapphire/framework'
import { Message } from 'discord.js'

export class PingCommand extends Command {
	public constructor(context: Command.Context, options: Command.Options) {
		super(context, { ...options });
	}

	public async messageRun (message: Message) {
		const msg = await message.channel.send('Ping?')
		const content = `Pong from JavaScript! Bot Latency ${Math.round(this.container.client.ws.ping)}ms. API Latency ${
			msg.createdTimestamp - message.createdTimestamp
		}ms.`
		return msg.edit(content)
	}

        // Error on next line
	public override registerApplicationCommands(registry: ChatInputCommand.Registry) {
		registry.registerChatInputCommand((builder) =>
			builder.setName('ping').setDescription('Ping bot to see if it is alive')
		);
	}
}

Expected behavior

No error

Screenshots

2022-07-27_18-32-33

Additional context

tsconfig.json

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "es2020",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
     "experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */
     "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */

    /* Modules */
    "module": "commonjs",                                /* Specify what module code is generated. */
    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
    // "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "resolveJsonModule": true,                        /* Enable importing .json files. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

    /* Emit */
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
    // "removeComments": true,                           /* Disable emitting comments. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
    // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */

    /* Type Checking */
    "strict": true,                                      /* Enable all strict type-checking options. */
     "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
     "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
     "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
     "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
     "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
     "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
     "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
     "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
     "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
     "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
     "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
     "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
     "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
     "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
     "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
     "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
     "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
     "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  }
}

bug: login creates multiple instances

Describe the bug
It appears that the client, upon login, creates multiple instances (3 in my case).

To Reproduce

  1. Use latest version of Sapphire (I have verified this was introduced in #84).
  2. Create a logger before login and on the ready event.
  3. The login logger should trigger once, whereas the ready event (and all subsequent) will fire multiple times.

Expected behavior
Login (and all subsequent functions) should only produce one instance

Screenshots
Screenshot from latest build:
image
Screenshot from rollback (confirming that this issue was created in latest build):
image

bug: Not compatible with bun runtime

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

For some reason I a lot of errors like this

Error when loading 'undefined/CorePrefixedMessage.js':
Error when loading 'undefined/CorePreMessageParser.js':
// etc...

this is my index.ts and I have a ping.ts

import { SapphireClient } from "@sapphire/framework";
import { GatewayIntentBits } from "discord.js";

const client = new SapphireClient({
  intents: [
    GatewayIntentBits.MessageContent,
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
  ],
  loadMessageCommandListeners: true,
});

client.login("MY_TOKEN");

Steps To Reproduce

just follow the instructions until here

https://www.sapphirejs.dev/docs/Guide/getting-started/creating-a-basic-command#creating-the-messagerun-method

Expected behavior

The bot should start

Screenshots

image

Additional context

Bun 1.0.3

bug: SapphireClient is missing typed client events

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

SapphireClient is missing intellisense for SapphireClient .on('EVENT-NAME')

Steps To Reproduce

npm init -y
[...]
npm i discord.js
npm i @sapphire/framework

Expected behavior

Get a list of available events via SapphireClient.on like with discord.js

Screenshots

No response

Additional context

node @ v16.14.2
VS Code @ 1.67.2

  "devDependencies": {
    "@tsconfig/recommended": "^1.0.1",
    "@types/node": "^17.0.40",
    "ts-node-dev": "^2.0.0",
    "typescript": "^4.7.3"
  },
  "dependencies": {
    "@sapphire/framework": "^2.5.0",
    "discord.js": "^13.7.0",
    "dotenv": "^16.0.1"
  }

request: Cog system

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

When developing complex bot, it is common to see one feature being fragmented across multiple files. This leads to terrible DX.

Desired solution

A new folder named cogs containing either files or directories that exports a cog class with listeners, application command registers, and all the good stuff.

File system tree example:

cogs/
  - countMessage.ts
    - messageCreate listener
    - /messagecount slash command register
    - etc
  - complexFeature.ts
    - index.ts
    - subFeature1.ts
    - subFeature2.ts
  - etc

Alternatives considered

Writing proper documentation that tracks what goes where helps a lot, but that is not always feasible especially during rapid development.

Additional context

This is inspired by discord.py's Cog system: https://discordpy.readthedocs.io/en/stable/ext/commands/cogs.html

Parse interaction options with sapphire resolvers

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

Maybe we should add a way to parse interaction options with sapphire resolvers?

Because there aren't many Discord's default types, so maybe Sapphire can offer a better api than manually importing resolvers...

Desired solution

const args = this.parseOptions(interaction); // Method in the core SapphireCommand class
args.get('option-name', 'date');
args.get('option-name2', 'hyperlink');
args.get('option-name3'); // Resolve with the discord's type entered in the option array.
// and so on

Alternatives considered

Using resolvers imported manually. It would work, but adding syntactic sugar: that's what a framework is for :)

Additional context

Vladdy told me on discord this was considered, but needs a refactor of the Arguments class to make it more abstract, so I thought I'd put it there as a reminder.

I'm willing to work on this if needed.

request: command handler

Is your feature request related to a problem? Please describe.

Inspired by Klasa because best option, but minimal.

We might also comply to Best Practices and provide built-in prefix reminder.

Describe the solution you'd like

We will need a method and an option in Client:

Client#userID: string | null;
Client#fetchPrefix: (Message) => null | string | string[] | Promise<string | string[]>;

The former is set upon ready using client.user.id as a value if it was not defined already. Otherwise this is handy for stateless bots which usually run WebSocket in another place, making it difficult to find. By adding this, we will provide a more reliable option for checking the bot's user ID in a bot of any kind, including the framework's internals.

The latter is defined by default as a method that returns null (no prefix), and may be overriden by users or by plugins. We cannot know which ORM or database driver the developer is using, so we provide this as a method they can freely override.

The command handler would be a monitor, which is fired in parallel with all others, and does the following steps (pseudo-code):

1: Prefix parsing

if (!channel.canPost) return;

const mentionPrefix = getMentionPrefix(content);

let prefix: string;
if (mentionPrefix) {
  if (content.length === mentionPrefix.length) {
    client.emit('mentionPrefixOnly', message);
    return;
  }

  prefix = mentionPrefix;
} else {
  const prefixes = await client.fetchPrefix(message);
  if (prefixes === null) return;

  const parsed = findPrefix(content, prefixes);
  if (parsed) prefix = parsed;
}

if (prefix) client.emit('parsedCommandPrefix' /* (?) */, message, prefix);

The idea is to have the logic fragmentated so modifying one part is easier, also allows for lowering the need to transfer the command handler.

2: Command parsing

// Retrieve the command name and validate:
const prefixLess = content.slice(prefix.length).trim();
const commandName = /.+\b/.exec(prefixLess)?.[0];
if (!commandName) {
  client.emit('unknownCommandName', message, prefix);
  return;
}

// Retrieve the command and validate:
const command = client.commands.get(commandName);
if (!command) {
  client.emit('unknownCommand', message, commandName, prefix);
  return;
}

client.emit('preCommandRun', message, command, commandName, prefix);

This would be written in the parsedCommandPrefix(?) event.

3: Run preconditions

Up for implementation, defined in #14, we must run them beforehand, emitting an event when the command cannot run.

This would be written in the preCommandRun event, emitting commandRun on success, and commandPreconditionError otherwise.

4: Run the command

Emitting events before and after, emitting events accordinly, included an event when the errors.

This would be written in the commandRun event.

Describe alternatives you've considered

N/a.

Additional context

N/a.

request: Customize built in preconditions failure message

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

I need to be able to customize the built-in preconditions failure message because I need to translate the message into other languages.

For example, by setting requiredUserPermissions: [PermissionFlagsBits.ManageGuild], when the user does not have the ManageGuild permission, the message "You are missing the following permissions to run this command: Manage Server" is returned. I need to translate it to "您缺少以下權限來執行此指令:管理伺服器".

Desired solution

There needs to be a way to set a custom message or a language configuration file.

Alternatives considered

None.

Additional context

No response

request: rawName property for context commands

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

The problem is that the command name is set to lowercase :

export class ValidRoroCommand extends Command {
  constructor(context, options) {
    super(context, {
      ...options,
      name: "Valide",
    });
  }

  registerApplicationCommands(registry) {
    registry.registerContextMenuCommand(builder =>
      builder //
        .setName(this.name)
        .setType(ApplicationCommandType.Message),
    );
}

We can create a rawName property for context commands for don't break actuals commands

Desired solution

Create a rawName property

Alternatives considered

Don't use lowercase() on context commands

Additional context

No response

request: CategoryChannel argument

Is your feature request related to a problem? Please describe.

Describe the solution you'd like

Describe alternatives you've considered

Additional context

request: argument handler

Is your feature request related to a problem? Please describe.

As any command framework, we need a command argument handler.

Describe the solution you'd like

We are looking for argument validation similar to Serenity with the following syntax:

public async run(message: Message, args: Args) {
  const a = args.pick('number');
  const b = args.pick('number');
  return message.send(`Sum is ${a + b}`);
}

This design opens the door to many things:

It's type safe, the method can be defined as the following:

export interface ArgTypes {
  string: string;
  number: number;
  integer: number;
  // ...
}

export class Args {
  pick<K extends keyof ArgTypes>(key: K): Promise<ArgTypes[K]>;
}

For custom arguments (also called per-command arguments), we will provide a top-level function outside of the Command class (so it may be used in class fields), such as the following:

private custom = createCustomArgument((message, argument, options) => {
  // ...
});

Which is typed as:

createCustomResover<T>(cb: (this: Command, message: Message, argument: string, options: ArgumentOptions) => T | Promise<T>): Resolver<T>;

And can be used similarly to regular arguments:

args.pick(this.custom);

What we can do in here is to provide an overload in pick being pick<T>(resolver: Resolver<T>): Promise<T>;.

Let's dive into the other methods, shall we?

  1. Args#setPosition(n: number): Args: sets this.position to n.
  2. Args#start(): Args: calls this.setPosition(0), resets it to the first parameter.
  3. Args#end(): Args: calls this.setPosition(this.length), sets it to the following position as the last parameter, thus rendering the instance as "consumed".
  4. Args#previous(): Args: calls --this.position if higher than 0.
  5. Args#continue(): Args: calls ++this.position if lower than this.length.
  6. Args#peek(): string: gives the current parameter.
  7. Args#peek<T>(r: Resolver<T>, o: ArgumentOptions): Promise<T>: parses the current parameter without modifying the position.
  8. Args#peek<K extends keyof ArgTypes>(key: K, o: ArgumentOptions): Promise<ArgTypes[K]>;: same as the previous.
  9. Args#pick<T>(r: Resolver<T>, o: ArgumentOptions): Promise<T>: parses the current parameter and modifies the position on success.
  10. Args#pick<K extends keyof ArgTypes>(key: K, o: ArgumentOptions): Promise<ArgTypes[K]>;: same as the previous.
  11. Args#rest(): string: returns all the parameters from this.position to end joined by the command's usageDelim.
  12. Args#empty: boolean: getter that checks if there are pending parameters.

In order to provide even more control or the parser, we're also releasing options:

interface ArgumentOptions extends Record<PropertyKey, unknown> {
  /**
   * @defaults false
   */
  required?: boolean; // defaults to false
  /**
   * Handle errors, controls full error flow.
   * @note Throws are bubbled up and will be sent to the user.
   */
  onError?: (m: Message, a: string, e: Error) => unknown;
  /**
   * The i18next key to get the translation key from. This will
   * call `sendLocale` with `{ message, argument, error }` as argument.
   */
  errorKey?: string;
  /**
   * @defaults null
   */
  minimum?: number | null;
  /**
   * @defaults null
   */
  maximum?: number | null;
}

The reason why the interface extends Record is to allow developers to pass extra metadata or for better extensions, e.g. passing an argument name.

Additionally, users may create their own instances of Args in their commands to parse messages from prompts or wherever they desire.

This design allows us to augment the interface and gain type information as we go, something otherwise unavailable by other options. And it is also very easily to extend: because you have full control of the arguments, being able to go to the previous argument, to the first, to the next argument, to the last, etc, developers have the possibility of making their own usage on top of this one, included by not limited to klasa-based (string usage), akairo-based (iterator usage), and commando-based (array of objects).

Describe alternatives you've considered

We have considered many alternatives:

  1. Decorators only: this proposal was denied because it was too focused for TypeScript users and the set-up was harder for those using JavaScript.
  2. Usage string: from Klasa, it needs a ton more things as it limits many features, does not offer a nice UX for TypeScript users, and in general a "black box" where you have no control.
  3. Iterator usage: from Akairo, it involves a complex concept many users are not confident with.
  4. Array of objects usage: from Commando and many others, it involves a lot of clutter and boilerplate lines of code, lacks of many features such as unions.

Additional context

N/a.

request: only fetch guild commands for guilds that have commands associated

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

Currently, to initialise ApplicationCommandRegistries the framework fetches all guild command for all guilds on startup. This has turned out to be unfeasible for bots with a large amount of guilds, easily creating startup times of several minutes if not hours.

Desired solution

The framework should only fetch commands for a guild when its guild id is specified in a command's register options, making sure only guilds relevant to the application code have their guilds commands fetched. This would make the bot only have to fetch a few guilds at worst and none at all in most scenarios.

Alternatives considered

None

Additional context

https://discord.com/channels/737141877803057244/737142774738190377/1014595177232465991

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • chore(deps): update all non-major dependencies (@types/node, @vitest/coverage-v8, vite, vitest)

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/auto-deprecate.yml
  • actions/checkout v4
  • actions/setup-node v4
.github/workflows/codeql-analysis.yml
  • actions/checkout v4
  • github/codeql-action v3
  • github/codeql-action v3
  • github/codeql-action v3
.github/workflows/continuous-delivery.yml
  • actions/checkout v4
  • actions/setup-node v4
.github/workflows/continuous-integration.yml
  • actions/checkout v4
  • actions/setup-node v4
  • actions/checkout v4
  • actions/setup-node v4
  • actions/checkout v4
  • actions/setup-node v4
.github/workflows/deprecate-on-merge.yml
  • actions/checkout v4
  • actions/setup-node v4
.github/workflows/documentation.yml
  • actions/checkout v4
  • actions/setup-node v4
  • actions/upload-artifact v4
  • actions/checkout v4
  • actions/setup-node v4
  • actions/download-artifact v4
  • actions/checkout v4
  • nick-fields/retry v3
.github/workflows/labelsync.yml
  • actions/checkout v4
  • crazy-max/ghaction-github-labeler v5
.github/workflows/publish.yml
  • actions/checkout v4
  • actions/setup-node v4
.github/workflows/release-crosspost.yml
  • kludge-cs/gitcord-release-changelogger v3.0.0@5592170408dc081d7cb6a74ce025911bd1fcb7c3
npm
package.json
  • @discordjs/builders ^1.8.2
  • @sapphire/discord-utilities ^3.3.0
  • @sapphire/discord.js-utilities ^7.3.0
  • @sapphire/lexure ^1.1.7
  • @sapphire/pieces ^4.3.1
  • @sapphire/ratelimits ^2.4.9
  • @sapphire/result ^2.6.6
  • @sapphire/stopwatch ^1.5.2
  • @sapphire/utilities ^3.16.2
  • @commitlint/cli ^19.3.0
  • @commitlint/config-conventional ^19.2.2
  • @favware/cliff-jumper ^4.0.2
  • @favware/npm-deprecate ^1.0.7
  • @favware/rollup-type-bundler ^3.3.0
  • @sapphire/eslint-config ^5.0.5
  • @sapphire/node-utilities ^1.0.2
  • @sapphire/prettier-config ^2.0.0
  • @sapphire/ts-config ^5.0.1
  • @types/node ^20.14.10
  • @types/ws ^8.5.11
  • @vitest/coverage-v8 ^2.0.2
  • concurrently ^8.2.2
  • cz-conventional-changelog ^3.3.0
  • discord.js ^14.15.3
  • esbuild-plugin-file-path-extensions ^2.1.2
  • esbuild-plugin-version-injector ^1.2.1
  • eslint ^8.57.0
  • eslint-config-prettier ^9.1.0
  • eslint-plugin-deprecation ^3.0.0
  • eslint-plugin-prettier ^5.1.3
  • gen-esm-wrapper ^1.1.3
  • lint-staged ^15.2.7
  • prettier ^3.3.3
  • vite ^5.3.3
  • vitest ^2.0.2
  • ansi-regex ^5.0.1
  • minimist ^1.2.8
  • yarn 4.3.1

  • Check this box to trigger a request for Renovate to run again on this repository

discussion: logging

At the moment of writing, Sapphire has no logger. And that isn't such a bad thing since it allows developers to use their own systems.
So I propose a client option that transforms internal console log calls into events or always emit said events but just disable the console logs.

Suggestion for args.pick('member')

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

Currently, even if you only provide one letter of a user's username it will still get the member, there should be an option to prevent that.

Desired solution

Add an option to prevent args.pick('member') from the getting the user if you only provide one part of the user username, for example the first letter, the first two letters, etc.

Alternatives considered

I could make a check to see if the argument provided length is more than a certain amount of characters, but this means the command wouldn't work if the user username is, for example, 2 letters long.

Additional context

No response

request: update for DiscordJS v14

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

DiscordJS v14 has been and we should update @sapphire/framework to match

Desired solution

The framework should be updated

Alternatives considered

N.A.

Additional context

N.A.

request: Slash Commands in Command framework

Is your feature request related to a problem? Please describe.

Easy integration of slash commands

Describe the solution you'd like

An simple option to enable slash commands, e.g:

const client = new SapphireClient({
  defaultPrefix: '!',
  id: '123',
  slashCommands: true, 
});

Probably a little oversimplified but should hopefully explain it 😄

Describe alternatives you've considered

There is an example here, but it doesn't integrate well with non interaction commands imo

Additional context

N/A

request: setPresence.status uses a custom type, but could use readily available PresenceUpdateStatus enum.

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

In the function for setPresence for a client's user the field status requires a custom type that is specified by discord.js as PresenceStatusData, but that field is a union between ClientPresenceStatus and the string 'invisible', ClientPresenceStatus is just a Union of the strings 'online', 'idle' and 'dnd'.

There's a enum for this exact same thing in the DiscordJS library which is PresenceUpdateStatus, that has the same fields plus the field of offline.

The function could take that enum instead, making it really easier to check for validity for example in a command for change the status.``

Desired solution

The following is what I had to do have a check for the string option to fit on the criteria:

	private isPresence(string: string): string is PresenceStatusData {
		if (string == 'online') {
			return true;
		} else if (string == 'invisible') {
			return true;
		} else if (string == 'idle') {
			return true;
		} else if (string == 'dnd') {
			return true;
		} else {
			return false;
		}
	}

But if the function took the enum PresenceUpdateStatus i just needed to do the check of:

   private isPresence(string: string): string is PresenceUpdateStatus {
        return string in PresenceUpdateStatus;
  }

The function could consider offline as invisible for this case as well since it's the only difference between the two values.

Alternatives considered

Alternative is making the function take a Omit<PresenceUpdateStatus, 'offline'> as well. That would keep it only accepting the same values as now.

Additional context

No response

request: easier syntax for combining preconditions

Is your feature request related to a problem? Please describe.

Currently the way to combine preconditions is using nested arrays such as: [['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel'];. This is extremely developer unfriendly because it's extremely chaotic and you have to thoroughly know how preconditions are parsed to start making the least amount of sense of this array.

Describe the solution you'd like

We can take a note out of how other libraries handle cases like this, such as Prisma who has $and, $or, and $not to better build up the preconditions list.

Describe alternatives you've considered

Using what we have no? Nah.

Additional context

https://github.com/sapphiredev/website/pull/91/files/a56d8a9c7f2ac760ea394241092fcdcbc583afa8#diff-7cc7d069f52b3fcc7bf7039c05a4988bd8f895d16731450c21c391e1d0c06565

bug: incorrect conversions in object hashing

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

When you have a chat input command and you have made no changes to the command data
After restarting your bot, it will go detect changes even when there are no changes!
It results in unnecessary api calls for updating and slows down the startup a lot if you got more commands
I tried to debug it and found that it has something to do with this as this is where these are hashed, but I think you will know it better

Steps To Reproduce

  1. Have a basic bot with ping command made with builders not tested without builders
		registry.registerChatInputCommand(
			(builder) =>
				builder //
					.setName(this.name)
					.setDescription(this.description),
			{
				guildIds: ['someID'],
				idHints: ['someID1', 'someID2']
			}
		);
  1. Once your command is registered, stop your bot and restart

  2. Make sure your Register behavior is either LogToConsole or Overwrite (overwrite causes api calls, log causes warn loggings)

  3. see that even when you didn't make any changes it says found differences and updates, more commands more time it takes

Expected behavior

It should detect properly that there are no changes! So no updates

Screenshots

No response

Additional context

Proper versions for your ease

"@sapphire/framework": "3.0.0-next.f47e6f6.0",
"@sapphire/pieces": "3.3.5",
"@sapphire/discord.js-utilities": "4.11.3",
"discord-api-types": "0.33.5",
"discord.js": "13.9.1",

request: language support

Is your feature request related to a problem? Please describe.

As a good framework, we need to implement a built-in way to allow developers customize the strings the framework uses without changing nor transferring its internals, and we can do two features in one: i18n support.

Describe the solution you'd like

We would use i18next for loading, parsing, formatting, and so on. The reasoning behind this is because it's a framework with a very large community that also features a large amount of support (StackOverflow), utilities (online translation services), and plugins (ICU, formatters...), as well as this framework can be used on the web so if a developer does a Sapphire bot with a front-end, they will not have to learn two i18n frameworks.

We would also require an extra method on Client:

Client#fetchLanguage: (message: Message) => string | Promise<string>;

This method is defined by default as a method that returns en-US, may be overriden by users or by plugins. We cannot know which ORM or database driver the developer is using, so we provide this as a method they can freely override.

Additionally we may also provide the following extensions:

Message#sendTranslated(key: string, options?: MessageOptions): Promise<MessageReturn>;
Message#sendTranslated(key: string, values: {}, options?: MessageOptions): Promise<MessageReturn>;
Message#fetchLanguage(): Promise<Language>;
Message#fetchLanguageKey(key: string, values?: {}): Promise<string>;

Describe alternatives you've considered

I considered using a Map and do the entire thing in bare JavaScript, but comes with many issues and everything boils to this.

Additional context

N/a.

request: [CoreReady listener] automatically delete application commands not found in the Command store

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

We often get requests if Sapphire can automatically delete application commands when initialising the bot. One way to achieve that is by diffing the list of container.client.application.commands with what is actually in the Sapphire Commands store on the ready event.

Desired solution

We should add an additional optional listener that diffs the two lists and deletes any commands that are in container.client.application.commands , but are not in container.stores.get('commands'). This optional listener should be toggable on client level with the option automaticallyDeleteUnknownCommands. To keep the comparison simple we should only compare names in both the lists.

This new option and its implications, such as name comparison, should be documented in the guide. While that has to be a separate PR to https://github.com/sapphiredev/website, this issue is not resolved until both PRs are finished.

Alternatives considered

N.A.

Additional context

No response

request: adding commandEditable typing

Adding typing property in SapphireClientOptions calls <TextChannel>.typing() when the command gets triggered. And adding commandEditable phrases a message when it's been edited.

request: flags handler

Is your feature request related to a problem? Please describe.

The besides arguments, the framework will need flags for "exotic" arguments.

Describe the solution you'd like

The Command class defines the flag support as an array, those are the flags we will accept, internally pre-parsed in a RegExp for quick matching. And will have two kinds:

  1. Double dash: -- and , required for flags that are at least 2 characters long. They may accept a custom value (i.e. --foo=bar).
  2. Single dash: - and (?), required for flags that are a single character long. They will not accept custom values (i.e. -i matches, -i=bar will not).

Furthermore we will check word boundaries of those flags, that way --foo=bar and -i match but something--foo=bar and hi-i will not.

The RegExp would be similar to the following:

const allowedFlags = Flag.make(['hello', 'hi', 'i']);
// -> /\B(?:(--|—)(hello|hi)(=|\s|$)|(-|—)(i)(?=\s|$))/ig

And we will ship two ways to parse them:

  1. Iterator: consumes them one-by-one:
    const content = '--hello=world --wow —Hi';
    
    for (const flag of Flag.getPotentialFlags(content, allowedFlags)) {
      console.log(flag);
      // Flag {
      //   dash: '--',
      //   name: 'hello',
      //   value: 'world',
      //   index: 0 }
      //
      // Flag {
      //   dash: '—',
      //   name: 'hi',
      //   value: null,
      //   index: 20 }
    }
  2. As Array: consumes them into an array:
    const content = '--hello=world --wow —Hi';
    const flags = Flag.asArray(content, allowedFlags);
    // [[Flag], [Flag]]
  3. As Map: consumes them into a map:
    const content = '--hello=world --wow —Hi';
    const flags = Flag.asMap(content, allowedFlags);
    console.log(flags);
    // Map(2) {
    //   'hello' => [Flag],
    //   'hi' => [Flag]
    // }

Note that flags are always lower-cased.

The usage of Flag will be done internally and are the foundation for the higher-level API we will provide, the framework will provide Message#parsedFlags being Map<string, Flag> and already parsed.

Note: if flags exist, they must be skipped from Args's parser, this can be done with an helper called Flag.strip(string, RegExp) which returns [content, Map<string, Flag>] (?).

Describe alternatives you've considered

Open for suggestions.

Additional context

N/a.

DMCA: Takedown Notice

Dear Designated Agent:

My name is bdistin.

Your repository is infringing on a copyright-protected source code owned by myself. The code, entitled “Command”/"CommandStore"/"Monitor"/"MonitorStore", to which I own the exclusive copyrights, is located at: https://github.com/dirigeants/klasa/

The unauthorized and infringing copy is located at: https://github.com/skyra-project/framework/

This letter is official notification under Section 512(c) of the Digital Millennium Copyright Act (”DMCA”). I seek the removal of the infringing material referenced above .Please take note as a service provider, the Digital Millennium Copyright Act requires you, to remove or disable access to the infringing materials upon receipt of this notice.

The Online Copyright Infringement Liability Limitation Act grants service providers such as your company immunity from liability so long as investigate and rectify this copyright violation in a timely manner. Should your company fail to do so, it may become liable for the infringement.

Please remove and disable all access to the aforementioned copyrighted work immediately. I have a good faith belief that use of the copyrighted materials described above as allegedly infringing is not authorized by the copyright owner, its agent, or the law.

I swear, under penalty of perjury, that the information in the notification is accurate and that I am the copyright owner or am authorized to act on behalf of the owner of an exclusive right that is allegedly infringed.

Best regards,
bdistin

request: add `RegisterBehavior.BulkOverwrite`

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

One of the features Sapphire currently doesn't have is using the Bulk Overwrite endpoint of the Discord API to register application commands. For those who simply do not care about tracking their commands through idHints and doing comparisons of whether a command has already been registered or not the use of bulk overwriting can be really useful. In particular during early development when you just want to fire and forget.

Desired solution

Add RegisterBehavior.BulkOverwrite to the RegisterBehavior enum then implement it.

For implementation this means:

  • This behavior only applies when setting it globally
    • If possible restrict this through types by removing it from the type for Commands
import { ApplicationCommandRegistries, RegisterBehavior } from '@sapphire/framework';
ApplicationCommandRegistries.setDefaultBehaviorWhenNotIdentical(RegisterBehavior.BulkOverwrite);
  • Any idHints on a per-command basis are completely ignored when this behavior is configured
  • Any provided behaviorWhenNotIdentical on a per-command basis are completely ignored when this behavior is configured

Alternatives considered

N.A.

Additional context

No response

discussion: plugin interface definition

May I inquire on how plugins will work so that api can be redesigned to fit it.
Would it still be all at the end of the constructor or could it also be at the start so that internal structures could be modified before their use?

[SFW-81] bug: application command comparison failing causing command with no actual changes to get re-registered on every launch

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

Given a specific configuration for registering a command, the deep comparison is failing causing the command to get updated on the Discord API at every boot of the bot.

Steps To Reproduce

  1. Copy this code into a command file:
import { Subcommand } from '@sapphire/plugin-subcommands';
import { ChannelType, PermissionFlagsBits } from 'discord.js';

export class UserCommand extends Subcommand {
  public constructor(context: Subcommand.Context) {
    super(context, {
      name: 'sub',
      description: 'Chat Input Command with some subcommand groups',
      requiredUserPermissions: ['ManageGuild'],
      requiredClientPermissions: ['ManageGuild'],
      runIn: 'GUILD_ANY',
      subcommands: [
        { name: 'list', chatInputRun: 'chatInputList' },
        {
          name: 'set',
          type: 'group',
          entries: [
            { name: 'modlog', chatInputRun: 'chatInputSetModlog' },
            { name: 'auditlog', chatInputRun: 'chatInputSetAuditlog' },
            { name: 'welcome', chatInputRun: 'chatInputSetWelcome' }
          ]
        }
      ]
    });
  }

  public override registerApplicationCommands(registry: Subcommand.Registry) {
    registry.registerChatInputCommand(
      (builder) =>
        builder
          .setName(this.name)
          .setDescription(this.description)
          .addSubcommand((command) => command.setName('list').setDescription('list the current settings of the bot for the current server ⚙️'))
          .addSubcommandGroup((group) =>
            group
              .setName('set')
              .setDescription('set the settings of the bot for the current server ⚙️')
              .addSubcommand((command) =>
                command
                  .setName('modlog')
                  .setDescription('change the modlog channel for the current server ⚙️')
                  .addChannelOption((option) =>
                    option
                      .setName('channel')
                      .setDescription("the channel to set the modlog to, don't input a channel to disable")
                      .setRequired(false)
                      .addChannelTypes(ChannelType.GuildText)
                  )
              )
              .addSubcommand((command) =>
                command
                  .setName('auditlog')
                  .setDescription('change the auditlog channel for the current server ⚙️')
                  .addChannelOption((option) =>
                    option
                      .setName('channel')
                      .setDescription("the channel to set the auditlog to, don't input a channel to disable")
                      .setRequired(false)
                      .addChannelTypes(ChannelType.GuildText)
                  )
              )
              .addSubcommand((command) =>
                command
                  .setName('welcome')
                  .setDescription('change the welcome channel for the current server ⚙️')
                  .addChannelOption((option) =>
                    option
                      .setName('channel')
                      .setDescription("the channel to set the welcome to, don't input a channel to disable")
                      .setRequired(false)
                      .addChannelTypes(ChannelType.GuildText)
                  )
              )
          )
          .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild),
      { idHints: undefined }
    );
  }

  public async chatInputList(interaction: Subcommand.ChatInputCommandInteraction) {
    return interaction.reply({ content: 'Not implemented yet', ephemeral: true });
  }

  public async chatInputSetModlog(interaction: Subcommand.ChatInputCommandInteraction) {
    return interaction.reply({ content: 'Not implemented yet', ephemeral: true });
  }

  public async chatInputSetAuditlog(interaction: Subcommand.ChatInputCommandInteraction) {
    return interaction.reply({ content: 'Not implemented yet', ephemeral: true });
  }

  public async chatInputSetWelcome(interaction: Subcommand.ChatInputCommandInteraction) {
    return interaction.reply({ content: 'Not implemented yet', ephemeral: true });
  }
}
  1. Start the bot once to get the command id and replace idHints: undefined with the proper data
  2. Start the bot twice more, you'll see how despite the code going unchanged the following lines are logged:
2023-11-18 23:13:35 - DEBUG - ApplicationCommandRegistry[sub] Preparing to process 1 possible command registrations / updates...
2023-11-18 23:13:35 - DEBUG - ApplicationCommandRegistry[sub] Registering id "1175559217650876477" to internal chat input map
2023-11-18 23:13:35 - DEBUG - ApplicationCommandRegistry[sub] Checking if command "sub" is identical with global chat input command with id "1175559217650876477"
2023-11-18 23:13:35 - DEBUG - ApplicationCommandRegistry[sub] Took 1ms to process differences via fast compute differences
2023-11-18 23:13:35 - DEBUG - ApplicationCommandRegistry[sub] Found differences for command "sub" (1175559217650876477) versus provided api data.
2023-11-18 23:13:36 - DEBUG - ApplicationCommandRegistry[sub] Updated command sub (1175559217650876477) with new api data

Expected behavior

The command doesn't get updated on the API side because the comparison concludes that they are identical.

Screenshots

No response

Additional context

Link to #sapphire-support thread: https://discord.com/channels/737141877803057244/1175555875180658698

SFW-81

request: quoted string support

Is your feature request related to a problem? Please describe.

This is an extension for both #11 (quoted arguments) and #12 (quoted values).

Describe the solution you'd like

We will support 4 sets of quotes:

const enum Quote {
	Single = "''",
	Double = '""',
	SmartSingle = '‘’',
	SmartDouble = '“”'
}

Those must match only if not escaped and with word boundaries, e.g, assuming usage delimiter ' ':

  • Hi "Foo Bar" Baz -> ['Hi', 'Foo Bar', 'Baz'].
  • Hi \"Foo Bar" Baz -> ['Hi', '\"Foo', 'Bar"', 'Baz'].
  • Hi"Foo Bar" Baz -> ['Hi"Foo', 'Bar', 'Baz'].

For escaping, we may support two ways:

  1. Programming-like, using the back-slash character (\), not so obvious for users and bothersome for phone users.
  2. SQL-like (?), using a second quote (i.e. ""), much easier and faster to write, we might ship this form later as the workings of iOS's quotes are unclear to me (does writing the same quote twice produce ‘’Something‘’, or ‘‘Something’’?) while the regular ones are pretty much straight-forward.

The latter may not be supported in flags, but we can revisit this form later in the future.

Describe alternatives you've considered

N/a.

Additional context

N/a.

@sapphire/framework does not declare @discordjs/builders in package.json dependencies

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

While the package uses @discordjs/builders in the code, it is not declared in package.json, not only causing tools like yarn plug n play to break, but it's also just bad practice to use packages you don't actually declare as a dependency.

Steps To Reproduce

  1. Make a @sapphire/framework project using yarn plug n play
  2. Try to run
  3. See error
Error: @sapphire/framework tried to access @discordjs/builders, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound.

Required package: @discordjs/builders
Required by: @sapphire/framework@npm:3.0.0-next.080c438.0 (via [...])

Expected behavior

Framework declares all dependencies it uses in the package.json

Screenshots

No response

Additional context

No response

request: Arguments in command constructor

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

I suggest adding a listener or something similar that will check for arguments that will fit in the command constructor.

Desired solution

Add arguments to the command constructor and either add a listener or extend the error to commandError.
Variant of what the arguments in the command will look like:
arguments: {
member: {
type: 'Member',
required: true,
},
duration: {
type: 'Number',
required: true,
},
},

Alternatives considered

no

Additional context

No response

request: HMR

Is there an existing issue or pull request for this?

  • I have searched the existing issues and pull requests

Feature description

Right now most discord bot frameworks require you to do a full restart to see your new bot changes. This can be slow and annoying when trying to iterate quickly.

HMR is a feature in java/kotlin, and you can use it with things like JDA to update your bot code while its still running.

This is technically possible in sapphire(https://www.sapphirejs.dev/docs/Documentation/api-pieces/classes/Piece#reload), but there is no easy way to actually use this right now, and thats where this feature equest comes in.

Desired solution

There are 2 possible solutions:

  • A command in the CLI
  • A plugin

The CLI command could be something like sapphire dev, which starts your bot and watches for changes, when a change is detected, it will recompile your project and reload the specific commands/listeners/pieces changed, based on what files have been updated.

The plugin will work similarly, you add the plugin and any file change will trigger a recompile and HMR, but will have direct code access so it should be a lot easier to implement

Alternatives considered

the closest thing to a hot reload is ctrl+c,up arrow, and enter

Additional context

Discord message that started this: https://discord.com/channels/737141877803057244/737142774738190377/927948497729257522

bug: Typings of all Args.****Result are incorrect

Describe the bug

The typings of all the Args.***Result methods are incorrect, as they use UserError as their error result. UserError does not include a parameter field, but all of them do.

To Reproduce

Use this, with an invalid role:

const role = await args.pickResult('role');
console.log(role);

You will see this in the console

{
  success: false,
  error: ArgumentError: The argument did not resolve to a role.
      at Function.error
      at CoreArgument.error
      at CoreArgument.run
      at processTicksAndRejections {
    identifier: 'role',
    context: {
      args: [Args],
      argument: [CoreArgument],
      message: [Message],
      command: [MyCommand],
      commandContext: [Object]
    },
    argument: CoreArgument {
      store: [ArgumentStore [Map]],
      path: '.../node_modules/@sapphire/framework/dist/arguments/CoreRole.js',
      name: 'role',
      enabled: true,
      aliases: []
    },
    parameter: 'my-invalid-role' // <---- It contains the parameter field, which is not present in the typings of `UserError`
  }
}

Expected behavior

I expected the typings of the error returned by those methods to contain a parameter?: string field.

Additional context

This can easily be solved by replacing all the UserError in /src/lib/parsers/Args.ts by UserError & { parameter?: string } (or by creating an intermediate type/interface)

I am willing to make a PR if you confirm me that this really is a bug

bug: Calling Args#rest will include leading space

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

When calling Args#rest with the string type, it will return a string that starts with a space, which is not intended

Steps To Reproduce

  1. Call Args#rest
  2. See the first character of the result

Expected behavior

No space is present at the start of the returned argument

Screenshots

No response

Additional context

This is possibly a lexure issue according to kyra 🤷

bug: Command#reload and CommandStore#loadAll do not handle BulkOverwrite

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

Whenever the registry behavior is set to BulkOverwrite, CommandStore#loadAll and Command#reload doesn't handle it, which will cause hard errors to be thrown

Steps To Reproduce

N/A

Expected behavior

CommandStore#loadAll should run the same flow as what is ran on READY
Command#reload is tricky... If we do bulk overwrite for this but then there's several commands reloaded in a row... We might need to use async-queue or similar for this. Or just yolo it. Both are valid options as long as they work

Screenshots

No response

Additional context

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.