diff --git a/content/recipes/necord.md b/content/recipes/necord.md new file mode 100644 index 0000000000..8427a16af1 --- /dev/null +++ b/content/recipes/necord.md @@ -0,0 +1,347 @@ +### Necord + +This module provides fast and easy way for creating [Discord](https://discord.com) bots and deep integration with your NestJS application. + +> info **info** `Necord` is a third party package and is not managed by the NestJS core team. Please, report any issues found with the library in the [appropriate repository](https://github.com/necordjs/necord). + +#### Installation + +Just like any other package, you've got to install necord and its dependency, [`Discord.js`](https://discord.js.org) before you can use it. + +```bash +$ npm i necord discord.js +``` + +> info **Hint** You need to install [Node.js](https://nodejs.org/) v16.0.0 or newer to use `Necord` and `Discord.js`. + +#### Usage + +To use `Necord` you need to import the `NecordModule` and pass it the configuration options. + +```typescript +@@filename(app.module) +import { Module } from '@nestjs/common'; +import { AppService } from './app.service'; +import { IntentsBitField } from 'discord.js'; + +@Module({ + imports: [ + NecordModule.forRoot({ + token: process.env.DISCORD_TOKEN, + intents: [IntentsBitField.Guilds], + development: [process.env.DISCORD_DEVELOPMENT_GUILD_ID] + }) + ], + providers: [AppService] +}) +export class AppModule {} +``` + +> info **Hint** You can find the list of intents [here](https://discord.com/developers/docs/topics/gateway#gateway-intents). + +Now you can inject the `AppService` into your providers and use it to register your commands, events, etc. + +```typescript +@@filename(app.service) +import { Injectable, Logger } from '@nestjs/common'; +import { Once, On, Context, ContextOf } from 'necord'; + +@Injectable() +export class AppService { + private readonly logger = new Logger(AppService.name); + + @Once('ready') + public onReady(@Context() [client]: ContextOf<'ready'>) { + this.logger.log(`Bot logged in as ${client.user.username}`); + } + + @On('warn') + public onWarn(@Context() [message]: ContextOf<'warn'>) { + this.logger.warn(message); + } +} +``` + +##### Context + +You might have noticed the `@Context` decorator in the last snippet: This is used to inject the event context within the method. As there are many type of events, its type must be inferred from the `ContextOf` type. + +You can access the context variables by using the `@Context()` decorator within your function, which will populate the variable with an array of arguments. + + +#### Text Commands + +> warning **caution** A text command is dependent on the content of the message but unfortunately, Discord plans to remove message content for verified bots and apps, those with 100 or more servers. Hence, You cannot use text commands if your bot cannot access message content.
[Read discord message here](https://support-dev.discord.com/hc/en-us/articles/4404772028055-Message-Content-Access-Deprecation-for-Verified-Bots) + +Create a simple command handler for messages using `@TextCommand`. + +```typescript +@@filename(app.commands) +import { Injectable } from '@nestjs/common'; +import { Context, TextCommand, TextCommandContext, Arguments } from 'necord'; + +@Injectable() +export class AppCommands { + @TextCommand({ + name: 'ping', + description: 'Ping command!', + }) + public onPing(@Context() [message]: TextCommandContext, @Arguments() args: string[]) { + return message.reply('pong!'); + } +} +``` + +#### Application Commands + +Application commands are native ways to interact with apps in the Discord client. There are 3 types of commands accessible in different interfaces: the chat input, a message's context menu (top-right menu or right-clicking in a message), and a user's context menu (right-clicking on a user). + +
+ +#### Slash Commands + +The best way to interact with your users is to use [Slash commands](https://support.discord.com/hc/en-us/articles/1500000368501-Slash-Commands-FAQ)! +Slash commands allow you to create commands with precise arguments and choices, giving users the best experience. + +To create a command with Necord, you can use the `SlashCommand` decorator. + +```typescript +@@filename(app.commands) +import { Injectable } from '@nestjs/common'; +import { Context, SlashCommand, SlashCommandContext } from 'necord'; + +@Injectable() +export class AppCommands { + @SlashCommand({ + name: 'ping', + description: 'Ping command!' + }) + public async onPing(@Context() [interaction]: SlashCommandContext) { + return interaction.reply({ content: 'Pong!' }); + } +} +``` + +> info **Hint** When the client logs in, it will automatically register all of the commands. +Global commands are cached for up to an hour, therefore to avoid the global commands cache, you should use the `development` argument on the Necord module. This will restrict the command to a single guild, preventing it from getting caught by the cache. + +##### Options + +Use the option decorator to define a parameter in a slash command, let's create the `TextDto` class: + +```typescript +@@filename(text.dto) +import { StringOption } from 'necord'; + +export class TextDto { + @StringOption({ + name: 'text', + description: 'Your text', + required: true + }) + text: string; +} +``` + +It has only one basic properties. Thereafter we can use the newly created DTO inside the `AppCommands`: + +```typescript +@@filename(app.commands) +import { Injectable } from '@nestjs/common'; +import { Context, SlashCommand, Options, SlashCommandContext } from 'necord'; +import { TextDto } from './text.dto'; + +@Injectable() +export class AppCommands { + @SlashCommand({ + name: 'length', + description: 'Get length of text' + }) + public async onLength(@Context() [interaction]: SlashCommandContext, @Options() { text }: TextDto) { + return interaction.reply({content: `Length of your text ${text.length}`}); + } +} +``` + +[List of all built-in option decorators](https://necord.org/interactions/slash-commands#options) + +##### Autocomplete + +To add autocomplete to your Slashcommand you will need a interceptor first. This class will intercept all requests from the user after typing in the autocomplete option field. + +```typescript +@@filename(cats-autocomplete.interceptor) +import { Injectable, UseInterceptors } from '@nestjs/common'; +import { AutocompleteInteraction, CommandInteraction } from 'discord.js'; +import { AutocompleteInterceptor } from 'necord'; + +@Injectable() +class CatsAutocompleteInterceptor extends AutocompleteInterceptor { + public transformOptions(interaction: AutocompleteInteraction) { + const focused = interaction.options.getFocused(true); + let choices: string[]; + + if (focused.name === 'cat') { + choices = ['Siamese', 'Persian', 'Maine Coon']; + } + + return interaction.respond( + choices + .filter(choice => choice.startsWith(focused.value.toString())) + .map(choice => ({ name: choice, value: choice })) + ); + } +} +``` + +You'll then have to add `autocomplete: true` to your options class: + +```typescript +@@filename(cat.dto) +import { StringOption } from 'necord'; + +export class CatDto { + @StringOption({ + name: 'cat', + description: 'Breed of cat', + autocomplete: true, + required: true + }) + cat: string; +} +``` + +And last but not least, apply the interceptor to your slash command: + +```typescript +@@filename(cats.commands) +import { Injectable, UseInterceptors } from '@nestjs/common'; +import { Context, SlashCommand, Options, SlashCommandContext } from 'necord'; +import { CatDto } from './cat.dto'; +import { CatsAutocompleteInterceptor } from './cats-autocomplete.interceptor'; + +@Injectable() +export class CatsCommands { + @UseInterceptors(CatsAutocompleteInterceptor) + @SlashCommand({ + name: 'cat', + description: 'Lookup information about the cat breed' + }) + public async onSearch(@Context() [interaction]: SlashCommandContext, @Options() { cat }: CatDto) { + return interaction.reply({content: `I found the breed of ${cat} cat`}); + } +} +``` + +#### User Context Menu + +**User commands** are application commands that appear on the context menu (right click or tap) of users. They're a great way to surface quick actions for your app that target users. + +```typescript +@@filename(app.commands) +import { Injectable } from '@nestjs/common'; +import { Context, UserCommand, UserCommandContext, TargetUser } from 'necord'; +import { User } from 'discord.js'; + +@Injectable() +export class AppCommands { + @UserCommand({ name: 'Get avatar' }) + public async getUserAvatar( + @Context() [interaction]: UserCommandContext, + @TargetUser() user: User + ) { + return interaction.reply({ + embeds: [ + new MessageEmbed() + .setTitle(`Avatar ${user.username}`) + .setImage(user.displayAvatarURL({ size: 4096, dynamic: true })) + ] + }); + } +} +``` + +#### Message Context Menu + +**Message commands** are application commands that appear on the context menu (right click or tap) of messages. They're a great way to surface quick actions for your app that target messages. + +```typescript +@@filename(app.commands) +import { Injectable } from '@nestjs/common'; +import { Context, MessageCommand, MessageCommandContext, TargetMessage } from 'necord'; +import { Message } from 'discord.js'; + +@Injectable() +export class AppCommands { + @MessageCommand({ name: 'Copy Message' }) + public async copyMessage( + @Context() [interaction]: MessageCommandContext, + @TargetMessage() message: Message + ) { + return interaction.reply({ content: message.content }); + } +} +``` + +#### Buttons + +[Buttons](https://discord.com/developers/docs/interactions/message-components#buttons) are interactive components that render on messages. They can be clicked by users, and send an [interaction](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object) to your app when clicked. + + +```typescript +@@filename(app.components) +import { Injectable } from '@nestjs/common'; +import { Context, Button, ButtonContext } from 'necord'; + +@Injectable() +export class AppComponents { + @Button('BUTTON') + public onButton(@Context() [interaction]: ButtonContext) { + return interaction.reply({ content: 'Button clicked!' }); + } +} +``` + +#### Select Menus + +[Select menus](https://discord.com/developers/docs/interactions/message-components#select-menus) are another interactive component that renders on messages. On desktop, clicking on a select menu opens a dropdown-style UI; on mobile, tapping a select menu opens up a half-sheet with the options. + +```typescript +@@filename(app.components) +import { Injectable } from '@nestjs/common'; +import { Context, StringSelect, StringSelectContext, Values } from 'necord'; + +@Injectable() +export class AppComponents { + @StringSelect('SELECT_MENU') + public onSelectMenu(@Context() [interaction]: StringSelectContext, @Values() values: string[]) { + return interaction.reply({ content: `Your selected color - ${values.join(' ')}` }); + } +} +``` + +[All of built-in select menu components](https://necord.org/interactions/message-components#select-menu) + +#### Modals + +With [modals](https://discord.com/developers/docs/interactions/message-components#text-inputs) you can create pop-up forms that allow users to provide you with formatted inputs through submissions. We'll cover how to create, show, and receive modal forms using necord + +```typescript +@@filename(app.modals) +import { Injectable } from '@nestjs/common'; +import { Context, Modal, ModalContext } from 'necord'; + +@Injectable() +export class AppModals { + @Modal('pizza') + public onModal(@Context() [interaction]: ModalContext) { + return interaction.reply({ + content: `Your fav pizza : ${interaction.fields.getTextInputValue('pizza')}` + }); + } +} +``` + +#### More Information + +Visit the [Necord](https://necord.org) website for more information, examples. diff --git a/src/app/homepage/menu/menu.component.ts b/src/app/homepage/menu/menu.component.ts index f5f8936cbf..d60cd7fbf9 100644 --- a/src/app/homepage/menu/menu.component.ts +++ b/src/app/homepage/menu/menu.component.ts @@ -245,6 +245,7 @@ export class MenuComponent implements OnInit { { title: 'Commander', path: '/recipes/nest-commander' }, { title: 'Async local storage', path: '/recipes/async-local-storage' }, { title: 'Automock', path: '/recipes/automock' }, + { title: 'Necord', path: '/recipes/necord' }, ], }, { diff --git a/src/app/homepage/pages/recipes/necord/necord.component.ts b/src/app/homepage/pages/recipes/necord/necord.component.ts new file mode 100644 index 0000000000..424d33e48e --- /dev/null +++ b/src/app/homepage/pages/recipes/necord/necord.component.ts @@ -0,0 +1,9 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { BasePageComponent } from '../../page/page.component'; + +@Component({ + selector: 'app-necord', + templateUrl: './necord.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class NecordComponent extends BasePageComponent {} diff --git a/src/app/homepage/pages/recipes/recipes.module.ts b/src/app/homepage/pages/recipes/recipes.module.ts index f75889c524..ab216b789a 100644 --- a/src/app/homepage/pages/recipes/recipes.module.ts +++ b/src/app/homepage/pages/recipes/recipes.module.ts @@ -19,6 +19,7 @@ import { NestCommanderComponent } from './nest-commander/nest-commander.componen import { AsyncLocalStorageComponent } from './async-local-storage/async-local-storage.component'; import { AutomockComponent } from './automock/automock.component'; import { SwcComponent } from './swc/swc.component'; +import { NecordComponent } from './necord/necord.component'; import { PassportComponent } from './passport/passport.component'; const routes: Routes = [ @@ -119,6 +120,11 @@ const routes: Routes = [ component: AutomockComponent, data: { title: 'Automock' }, }, + { + path: 'necord', + component: NecordComponent, + data: { title: 'Necord' } + }, { path: 'passport', component: PassportComponent, @@ -146,6 +152,7 @@ const routes: Routes = [ AutomockComponent, ReplComponent, SwcComponent, + NecordComponent, PassportComponent, ], })