Context menu commands
Add a right-click command on a user or a message, and read what someone ran it on. Covers the base class each kind has, reaching the member, serving several commands from one handler, and the separate names each kind keeps.
Discord shows a context menu command when someone right-clicks. It takes a name and a kind, and the click itself is its only input.
import {
BuilderComponent,
RegisterCommand
} from '@seedcord/gateway';
import { ApplicationCommandType } from 'discord.js';
@RegisterCommand('global')
export class ViewProfile extends BuilderComponent<'context_menu'> {
constructor() {
super('context_menu');
this.instance
.setName('View Profile')
.setType(ApplicationCommandType.User);
}
}That name is the route you write on the handler, including its spaces and capitals.
@UserContextMenuRoute<"View Profile">(...names: "View Profile"[]): <TCtor>(constructor: AssertContextMenuRoute<ApplicationCommandType.User, "View Profile", TCtor>) => voidUserContextMenuRoute('View Profile')
export class class ViewProfileViewProfile extends class UserContextMenuHandler<
Names extends NamesFor<ApplicationCommandType.User>,
Cache extends CacheType = MenuCacheFor<
ApplicationCommandType.User,
Names
>
>
UserContextMenuHandler<'View Profile'> {
public async ViewProfile.execute(): Promise<void>execute(): interface Promise<T>Promise<void> {
const const user: Useruser = this.UserContextMenuHandler<"View Profile", "cached">.target: Usertarget;
await this.RepliableHandler<UserContextMenuCommandInteraction<"cached">, Core, SentMessage, BufferResolvable | ... 4 more ... | AttachmentPayload, ReplySender>.reply(response: string | ReplyResponse<BufferResolvable | Stream | JSONEncodable<APIAttachment> | Attachment | AttachmentBuilder | AttachmentPayload>, opts?: SendOpts): Promise<SentMessage>reply(`Profile for ${const user: Useruser.User.tag: stringtag}.`);
}
}Each kind has its own base class
ApplicationCommandType.User puts the command on a right-clicked person. ApplicationCommandType.Message puts it on a right-clicked message. Pick the base and the decorator that match the kind you set.
| the command sets | the handler extends | the decorator |
|---|---|---|
ApplicationCommandType.User | UserContextMenuHandler | @UserContextMenuRoute |
ApplicationCommandType.Message | MessageContextMenuHandler | @MessageContextMenuRoute |
Gateway and http differ
The clicked thing arrives as a discord.js object on gateway and as a discord-api-types payload on http.
| gateway | http | |
|---|---|---|
user this.target | User | APIUser |
message this.target | Message | APIMessage |
this.targetMember | GuildMember | null | APIInteractionDataResolvedGuildMember | null |
Both transports give this.target an id. A user target carries username too, and a message target reaches its author through author.
this.targetMember differs further. Gateway gives it id and displayName. Http holds the guild fields alone there, since Discord sends that person's user object once under resolved.users.
A message command reads the message it was run on.
@MessageContextMenuRoute<"Report Message">(...names: "Report Message"[]): <TCtor>(constructor: AssertContextMenuRoute<ApplicationCommandType.Message, "Report Message", TCtor>) => voidMessageContextMenuRoute('Report Message')
export class class ReportMessageReportMessage extends class MessageContextMenuHandler<
Names extends NamesFor<ApplicationCommandType.Message>,
Cache extends CacheType = MenuCacheFor<
ApplicationCommandType.Message,
Names
>
>
MessageContextMenuHandler<'Report Message'> {
public async ReportMessage.execute(): Promise<void>execute(): interface Promise<T>Promise<void> {
const const message: Message<true>message = this.MessageContextMenuHandler<"Report Message", "cached">.target: Message<true>target;
await this.RepliableHandler<MessageContextMenuCommandInteraction<"cached">, Core, SentMessage, BufferResolvable | ... 4 more ... | AttachmentPayload, ReplySender>.reply(response: string | ReplyResponse<BufferResolvable | Stream | JSONEncodable<APIAttachment> | Attachment | AttachmentBuilder | AttachmentPayload>, opts?: SendOpts): Promise<SentMessage>reply(
`Reported ${const message: Message<true>message.Message<true>.id: stringid} from <@${const message: Message<true>message.Message<true>.author: Userauthor.User.id: stringid}>.`
);
}
}Reaching the member
A user command also carries the clicked person's server member, which is null when the command runs outside a server.
const member = this.targetMember;Several commands in one handler
You put both commands on one class. Its decorator takes every name and its generic repeats them. Each arm then receives the clicked user and their server member, narrowed to that one command.
@UserContextMenuRoute('View Profile', 'Warn')
export class Moderation extends UserContextMenuHandler<
'View Profile' | 'Warn'
> {
public async execute(): Promise<void> {
await this.match({
'View Profile': (user) =>
this.reply(`Profile for ${user.tag}.`),
Warn: (user, member) =>
this.reply(
`Warned ${member?.displayName ?? user.tag}.`
)
});
}
}Leaving Warn out of the arms is a compile error, the same as it is on a slash handler.
this.commandName gives you the name that fired, typed to the union, for when you want it without branching.
Message commands work the same way, and each of your arms receives the clicked message.
Each kind keeps its own names
Discord lets a user command and a message command share a name, so the two registries stay separate. A name in one is invisible to the other.
class Wrong extends MessageContextMenuHandler<'View Profile'> { public async execute(): Promise<void> {}
}