npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

strike-discord-framework

v1.5.9

Published

A simple lightweight framework meant to allow rapid development of discord bots

Downloads

60

Readme

strike-discord-framework is a simple, lightweight command and permission framework for Discord.js

Setup

const framework = new Framework(opts);
await framework.init();

This code will start the framework, the opts object has the following stucture

interface FrameworkClientOptions {
	token: string // Discord bot token,
	name: string // Application name, used for logging and database setup,
	commandsPath: string // Path to the commands folder,
	defaultPrefix: string // Prefix
	databaseOpts: {
		databaseName: string, // Name of the mongoDB database
		url: string // URL to connect to mongoDB
	}
	loggerOpts: { // (Optional)
		logChannels: Record<LogLevel, string>; // (Optional) A map between the log level, and where it should go
		logToFile: boolean; // If the framework should log out to a file
		filePath: string; // (Optional) Filepath to store log files, each log file is separated by day
	}
	ownerID: string; // (Optional) Your discord Id for permission override
	dmPrefixOnPing? boolean; // (Optional) If the bot should DM user the prefix whenever its pinged
	permErrorSilently: boolean; // (Optional) If permission errors should be quietly ignored, rather then sending the user an error
	dmErrorSilently: boolean; // (Optional) If the bot should tell users that they cannot run that command in DMs when its set as such
	clientOptions: ClientOptions; // (Optional) Discord client options, passed directly to DJS. Its highly recommend you set this as otherwise the intents are set to everything
}

When you init the framework, you may also pass in an optional object that will be attached to all CommandEvent's

await framework.init(some_object)

In order to load default commands use the loadBotCommands method with a path pointing to the frameworks defaultCommands method The following default commands are loadable:

Default Commands

  • misc
    • help - Uses the help object on commands to show user help
    • ping - A simple ping-pong command to show the bot is running
  • admin
    • eval - Allows the owner of the bot to execute javascript code
    • override - Enables override of all permissions
    • prefix - Allows a server admin to change the prefix value in a guild
    • perm - Allows for the control of command permission flags

This code should point to the default commands path in most nodejs project

framework.loadBotCommands(`${process.cwd()}/node_modules/strike-discord-framework/dist/defaultCommands/`);

Commands

Command folder structure

Within your commands folder, you should have multiple sub directories for each category of command. The above shows the commands and how they are arranged. There is a second type of command called "MultiCommand" that allows for a set of commands that fall under the same parent command, for the above perm is a multi command so the folder structure looks like this

  • misc
    • help.ts
    • ping.ts
  • admin
    • eval.ts
    • override.ts
    • prefix.ts
    • perm
      • grant.ts
      • list.ts
      • perm.ts
      • remove.ts

perm is a multicomamnd as there is a perm.ts file within it. Multi-commands are defined by having a file, with the same name as the folder in it. xxx/xxx.ts = multi command. All other files within the folder will be counted as child commands of the multi-command

Command

To create a command, place a file within the commands directory. commands/category_name/filename.ts The file should have a default export, that is a class that extends the Command The command class has the following structure

abstract class Command {
	abstract name: string; // (Required) The command name, what the user run
	allowDM: boolean = true; // If this command can be ran in DMs
	permissions: string[] = []; // The permission flags this command has
	altNames: string[] = []; // Alternative names for this command
	help: { msg?: string; usage?: string; } = {} // A help object used for the help command
	noPermError(event: CommandEvent, ...args: BotCommandArgument[]): BotCommandReturn; // An optional method that will be called whenever a user without permissions executes the command
	abstract run(event: CommandEvent, ...args: BotCommandArgument[]): BotCommandReturn; // (Required) The method that is run when a user executes a command
}

BotCommandReturn

BotCommandReturn can either be void, or a Sendable. It can also be a promise of either of those.

type Sendable = string | Discord.MessageEmbed | { embeds: Discord.MessageEmbed[] };

BotCommandArgument

The type for automatic argument parsing: number | string | Discord.Role | Discord.User | Discord.GuildMember | UserRole; UserRole is a special class, that will resolve either a user or a role. The following properties exist:

  • id - Role or user Id
  • value - The actual object, either a Discord.User or Discord.Role
  • type - Either "user" or "role"

Multi-Command

The MultiCommand class extends the Command class. All sub commands are refrenced via the primary command, such as when the user runs a command it has the following syntax:

[prefix][multi-command-name] [subcommand] [arguments] A multi-command has one additional property to regular commands, check. Check is optional, and by default will not modify command execution.

check(event: CommandEvent): MultiCommandRet | Promise<MultiCommandRet>

All other commands of a multi-command should be just regular multi-commands. If the base command cannot be executed for whatever reason (DM when DMs are set to false, no permissions, etc) then the child command will not be ran

MultiCommandRet

An object with the following structure

{
	event: CommandEvent; // An updated CommandEvent, very useful if you would like to extend the command event class and add additional properties. This updated command event will be passed to the run method of the child command
	pass: boolean; // If this command should continue execution and run the child command
	failMessage: Sendable; // If pass is set to false, this will be sent to the user
}

CommandEvent

Command event is the primary method to interact with the framework when commands execute. The class has the following structure

class CommandEvent<T = any> {
	command: BotCommand; // The bot command that this event was triggered by, if its a part of a multi-command this will be the child command that gets executed
	app: T; // This is the value that was passed into the framework on initialization
	framework: FrameworkClient; // A reference to the framework instance
	message: Discord.Message; // The discord message that triggered this command
	args: string[]; // A list of arguments the user gave, split by spaces, but where anything in quotations will be grouped. hello world "hello world"
	constructor(event: CommandEvent); // If you extend the command event class then pass in the original command event to assign the values 
}

Argument Parsing

To use automatic argument parsing there are some pre-requisites that must be completed first

  • reflect-metadata is included, and loaded before the framework
  • You are using typescript
  • tsconfig.json has the following:
    • "experimentalDecorators": true
    • "emitDecoratorMetadata": true

The argument types that can be parsed are stated above, to enable argument parsing simply decorate a command with the @CommandRun tag. This would look something like

class MyCommand{
	name = "command"
	@CommandRun
	async run(event: CommandEvent, role: Discord.Role, name: string, user: Discord.string) {}
}

All the arguments will be automatically parsed and given to the run command. If any argument fails to parse it will not run the command

String and number arguments can have specific restrictions applied to them using the Arg decorator

  • Numbers can restrict using min and max properties
  • Strings can restrict using the regex property
  • Both can have the following properties:
    • optional - If this value is strictly required, or can be omitted (this can be applied to the other types as well)
    • options - Strictly sets what values are valid, is an array of possibilities

Example

Get a number greater than 5, and optionally get a string thats either "hello" or "world"

class MyCommand{
	name = "command"
	@CommandRun
	async run(event: CommandEvent, @Arg({min:5}) num: number, @Arg({optional: true, options: ["hello", "world"]}) str: string) {}
}

Utilities

All utilities are accessed via framework.utils, which will contain several utility methods

Display Id

framework.utils.displayId(id: string, guild?: Discord.Guild, opts?: DisplayIdOpts)

Formats a discord Id to be human readable |Argument | type | Description| |- | - | -| |id | string | The Id that will be displayed, can be a user, member, channel, role, or server Id| |guild | Discord.Guild? | The context of the Id for formatting as a ping or raw string| |opts | DisplayIdOpts | an object with the booleans includeTypePrefix and includeID| |returns | Promise<string> | The formatted user-friendly string that describes the Id|

Parse Quotes

framework.utils.parseQuotes(str: string)

Takes in a string, and splits the string into parts based off spaces and grouped by quotes |Argument | type | Description| |- | - | -| |str | string | Input string| |returns | string[] | Array of the split and parsed strings|

React Confirm

framework.utils.reactConfirm(prompt: string, message: Discord.Message, opts?: ConfirmOptions)

Gets a confirm/cancel action from the user |Argument | type | Description| |- | - | -| |promot | string | Thing to ask the user to confirm| |message | Discord.Message | Message from the user, used to target specific user and channel| |opts | ConfirmOptions | Optional set of options to configure how the framework should respond to the input|

ConfirmOptions has the following structure, every property is optional

interface ConfirmOptions {
	visual: boolean; // If the replys should be ephemeral or not
	onCancelMessage: Sendable; // The message sent when the user presses cancel
	onConfirmMessage: Sendable; // Message sent when user presses confirm
	onConfirm: () => Sendable | Promise<Sendable>; // Callback on pressing confirm, return value is sent to user
	onCancel: () => Sendable | Promise<Sendable>; // Callback on pressing cancel, return value is sent to user 
};

Resolve User

framework.utils.resolveUser(resolvable: string, guild?: Discord.Guild)

Attemptes to resolve a Discord.User from a string, by id, name, and nickname |Argument | type | Description| |- | - | -| |resolvable |string| String to try to resolve to a user| |guild | Discord.Guild | Optional to help with the resolotion| |returns | Promise<Discord.User> | Returns the user that was found. WIll be null if none found|

User input helpers

There are four primary methods to get user input, those are

  • getString - Gets a single message from the user and returns its content
  • getSelect - Creates a drop down select and gets N number of options from it
  • getButton - Creates a message with buttons on it, and returns the first the user clicks
  • getButtonSelect - Creates a message with buttons, and allows the user to press multiple buttons

getString

|Argument | type | Description| |- | - | -| |message| Discord.Message| The message from the user to target user and channel| |prompt| Discord.MessageEmbed| The embed to send as the prompt| |returns | Promise<string>| the user entered value|

getSelect

|Argument | type | Description| |- | - | -| |message| Discord.Message| The message from the user to target user and channel| |prompt| Discord.MessageEmbed| The embed to send as the prompt| |options| SelectOption[]| The selection options| |values| number = 1| How many values for the user to enter, defaults to one| |returns | Promise<string[]>| the user selected value(s)|

SelectOption has the structure

interface SelectOption {
	name: string; 
	description?: string;
	emoji?: string;
	value?: string; // If value is defined, the returned value is this, otherwise its the name
}

getButton

|Argument | type | Description| |- | - | -| |message| Discord.Message| The message from the user to target user and channel| |prompt| Discord.MessageEmbed| The embed to send as the prompt| |options| ButtonOption[]| The button options| |returns | Promise<string>| the value of the button the user pressed|

ButtonOption has the structure

interface ButtonOption {
	name: string;
	emoji?: string;
	value?: string; // If value is defined, the returned value is this, otherwise its the name
	disabled?: boolean;
	style: "PRIMARY" | "SECONDARY" | "SUCCESS" | "DANGER";
}

getButtonSelect

|Argument | type | Description| |- | - | -| |message| Discord.Message| The message from the user to target user and channel| |prompt| Discord.MessageEmbed| The embed to send as the prompt| |options| ButtonSelectOption[]| The button options| |returns | Promise<void>|

ButtonSelectOption has the structure

interface ButtonSelectOption {
	button: ButtonOption;
	onSelect: (itr: Discord.ButtonInteraction, updateButtons: (options: ButtonSelectOption[]) => Promise<void>) => void | Promise<void>;
}

onSelect gets two values, the interaction, and an updateButtons callback that can be used to update the selection options after the user clicks a button, it is not required that you update anything

Paged Embeds

There are two types of paged embeds, NamedPageEmbed or NumberedPageEmbed, where named uses string indexs selected by the user, and numbered uses sequential numeric indexs.

There are two methods used to construct these classes, and they have near identical siguatures framework.utils.namedPageEmbed(message: Discord.Message, base: EmbedCallback, init: EmbedCallback, pages: NamedPage[]) |Argument | type | Description| |- | - | -| |message| Discord.Message| The message from the user to target user and channel| |base| EmbedCallback | This is the message first sent, where either the controls for moving backwards and forwards are created, or the selection for the named pages is added| |init| EmbedCallback | Creates the initial emebed for the page after an interaction| |pages| NamedPage[] | Array of pages the user can select| |returns | NamedPageEmbed|

For a numbered page embed simply replace NamedPage with NumberedPage and use framework.utils.numberPageEmbed

EmbedCallback is a simple callback type that resolves an embed

type EmbedCallback = () => Discord.MessageEmbed | Promise<Discord.MessageEmbed>;

NamedPage is an object to describe a single page of the embed

interface NamedPage {
	name: string; // Name shown in the select
	description?: string; // Description in the select
	emoji?: string; // Emoji in the select
	get(existing: Discord.MessageEmbed, name: string): Discord.MessageEmbed | Promise<Discord.MessageEmbed> // Callback that will be executed when this page is selected, should return the updated embed
}

NumberedPage is simular to NamedPage but for numrically indexed pages

type NumberedPage = (existing: Discord.MessageEmbed, index: number) => Discord.MessageEmbed | Promise<Discord.MessageEmbed>;

Object Builder

framework.utils.objectBuilder<Obj>(display: DisplayFunc, message: Discord.Message, questions: Question[]) |Argument | type | Description| |- | - | -| |display| DisplayFunc| A function with the following signature (obj: Object, framework: FrameworkClient, message: Discord.Message) => Discord.MessageEmbed \| Promise<Discord.MessageEmbed>. This is used by the object builder class to show the user the object being edited| |message| Discord.Message | Used to target the specific user and channel| |questions| Question[] | An array of questions that can be used to modify the object| |returns | ObjectBuilder<Obj> | An instance of the OjectBuilder class that can be used to modify or create the object|

ObjectBuilder class

Question

There are three types of questions, each extend from the base question interface

interface BaseQuestion {
	type: QuestionType; // QuestionType is an enum with str, button, and select values
	handle?: (value: string) => { passes: boolean; value: any }| Promise<{ passes: boolean; value: any }>; // Called when the user inputs a value, allows for modification of the value type and for rejecting the value outright
	prop: string; // The property on the object to set
	name: string; // The name of this question, used to select to show the user options in a drop down
	prompt: string; // The prompt to ask the user when asking for input
}
  • QuestionType.str (StringQuestion) - Gets a string from the user, no additional properties over BaseQuestion
  • QuestionType.select (SelectQuestion) - Gets an option from the user via a select, has an options property that should be of type SelectOption[]. Uses the getSelect util
  • QuestionType.button (ButtonQuestion) - Promopts the user to press a button, with the options being defined in an options property that should be of type ButtonOption[]. Uses the getButton util

Ask One Question

ObjectBuilder.askOneQuestion(obj: Obj)

Asks a single question from the user, where they can select the question they would like to answer. If the user presses exit then the method will return null

|Argument | type | Description| |- | - | -| |obj | Obj | The object that will have a modification preformed, and returned once the user makes the change| |returns | Promise<Obj> | The modified object (may be null)|

Ask all questions

ObjectBuilder.askAllQuestions(obj: Partial<Obj> = {})

Asks the users all questions sequentially and builds up the object from that

|Argument | type | Description| |- | - | -| |obj | Partial<Obj> | Optional object to start with, useful for default values| |returns | Promise<Obj> | The created object, may be null if the user exits|