-
-
Notifications
You must be signed in to change notification settings - Fork 37
Description
Summary
A Robo.js plugin that lets communities keep using prefix commands (e.g., !ping) while they migrate to slash commands. It scans a project’s /src/prefix-commands folder, routes messageCreate events to handlers, supports Sage-like return semantics (return a value → bot replies), and shows a “thinking…” placeholder if execution exceeds 300 ms, later editing it with the final result or error.
⚠️ Note: Prefix commands require the Message Content privileged intent. See the References section.
Goals
- Provide a simple, pragmatic bridge for legacy prefix commands without blocking migration to slash commands.
- Mirror Robo ergonomics: return a value to reply (Sage-like), minimal boilerplate, and clear file structure.
- Encourage slash commands in the README and examples.
Requirements & Constraints
- Discord Intents: Message Content is required to parse prefixes from message text. Apps at scale must apply for privileged intents via the Developer Portal. Smaller bots can enable them directly (see References).
- Event Source: Subscribe to
messageCreate; only process when content begins with a configured prefix or bot mention. - Rate Limits: Respect Discord rate limits when sending and editing messages.
- Compatibility: Designed for the current discord.js API and Robo.js plugin model.
Scope & Features
- Prefix parsing with support for multiple prefixes (global config) and case-insensitive matching.
- Folder-based command loading from
/src/prefix-commands/**(usingnode:fs), mirroring Robo’s file-based philosophy. - Sage-like return semantics: command handlers may simply
returnstrings/embeds to reply; throwing yields a friendly error reply. - Thinking/deferral UX: If a handler takes >300 ms, send a placeholder (or typing indicator) and edit with the final result.
API Design
1) Plugin Configuration (file: config/plugins/prefix.mjs)
// config/plugins/prefix.mjs
export default {
// Global defaults
prefixes: ['!'], // supports multiple, e.g., ['!', '?']
directory: 'src/prefix-commands',
mentionPrefix: true, // allow "@Bot ping"
caseInsensitive: true,
allowDMs: false,
// Thinking behavior
thinking: {
strategy: 'message', // 'off' | 'typing' | 'message'
delayMs: 300,
placeholder: '…thinking…'
},
deleteTrigger: false, // optionally delete the user’s trigger message
errorStyle: {
showStack: false,
message: 'Something went wrong running that command.'
},
parsing: {
quotes: true, // keep "quoted strings" intact
greedyLastArg: true // last arg captures the rest of the line
}
}2) Imperative API (for programmatic control)
// @robojs/prefix — minimal public API
export interface PrefixPluginConfig {
prefixes: string[];
directory: string; // default: 'src/prefix-commands'
mentionPrefix: boolean;
caseInsensitive: boolean;
allowDMs: boolean;
thinking: { strategy: 'off' | 'typing' | 'message'; delayMs: number; placeholder: string };
deleteTrigger: boolean;
errorStyle: { showStack: boolean; message: string };
parsing: { quotes: boolean; greedyLastArg: boolean };
}
export const Prefix = {
getGlobalConfig(): {} as PrefixPluginConfig,
reload: async () => { /* re-scan handlers from disk */ }
} as const;3) Handler Authoring API (in /src/prefix-commands) (in /src/prefix-commands)
Handlers mirror the ergonomics of Robo slash commands: return a value to reply. When you need full control, use provided helpers.
// src/prefix-commands/ping.ts
import type { LegacyContext, LegacyResult } from '@robojs/prefix';
export default async function handler(ctx: LegacyContext): Promise<LegacyResult> {
// Sage-like: return a value → plugin replies (or edits the thinking message)
return `Pong! (latency ~${Date.now() - ctx.message.createdTimestamp}ms)`;
}// Types exposed to handler authors
export interface LegacyContext {
message: import('discord.js').Message;
args: string[];
userId: string;
// helpers
reply: (payload: ReplyPayload) => Promise<void>; // manual reply
think: () => Promise<void>; // emit placeholder now
edit: (payload: ReplyPayload) => Promise<void>; // edit placeholder
}
export type ReplyPayload =
| string
| { content?: string; embeds?: any[]; components?: any[]; files?: any[] };
export type LegacyResult = void | ReplyPayload | Promise<ReplyPayload | void> = void | ReplyPayload | Promise<ReplyPayload | void>;Architecture & Implementation Details
A) File Scanning
-
Use
node:fsto recursively scandirectory(src/prefix-commands) and build a registry:commandName -> module path. -
Resolution rules:
src/prefix-commands/ping.ts→!pingsrc/prefix-commands/mod/kick.ts→!mod kick(nested groups)- Case sensitivity controlled by
caseInsensitive.
B) Event Flow (messageCreate)
-
Ignore bots and messages with no content.
-
Check for prefix or mention match. Extract
cmd+args(with quotes and greedy last arg options). -
Resolve handler; check guild-level blocks and
allowDMs. -
Start a 300 ms timer:
-
If still pending after
delayMs:typing: callchannel.sendTyping()(renew as needed).message: send a placeholder message and keep its reference.
-
-
Execute handler with
LegacyContext. On return:- If placeholder exists → edit it with final payload.
- Else → reply to the trigger message.
-
On throw: reply with
errorStyle.message(and optionally stack when configured). Ensure edits respect rate limits.
C) Rate Limits & Reliability
- Coalesce progress into one edit when possible; avoid spamming edits.
- Honor Discord rate-limit guidance for sends/edits.
- Provide minimal retries with jitter only on transient failures.
D) Security, Privacy, and Intents
- Clearly document Message Content requirements and obtain only necessary intents.
- Don’t process DMs unless
allowDMsis enabled. - Avoid logging raw message content in production unless you use logger.debug().
Usage Examples
1) Simple Ping
// src/prefix-commands/ping.ts
export default () => 'Pong!';2) Long Task with Explicit Thinking
// src/prefix-commands/report.ts
export default async ({ think }: LegacyContext) => {
await think(); // force placeholder immediately
const data = await makeHeavyCall();
return { content: `Report ready:\n${format(data)}` };
}Suggested Project Layout (plugin repository)
@robojs/prefix/
src/
index.ts # plugin entry (register event + config load)
registry.ts # fs-based scan of /src/prefix-commands → map name->module
router.ts # prefix/mention parsing, arg tokenizer
executor.ts # 300ms thinking, run handler, auto-reply/edit
types.ts # LegacyContext, ReplyPayload, public API types
README.md
package.json
Testing (Encouraged, Not Required)
- Unit: parser, registry resolution.
- Integration: fake
messageCreateevents; assert reply/edit behavior under/over 300 ms. - E2E (optional): spin up a test guild with a bot token; verify
!ping→ “Pong!” and the thinking/edit flow.
Contributing / Acceptance Criteria
We’ll accept the PR when:
!pingworks end-to-end with Sage-like return semantics.- 300 ms thinking/edit flow functions as configured.
- README includes Message Content instructions and migration guidance.
Nice-to-haves:
- Example commands (
ping,report,args-demo). - Basic unit tests and a short demo GIF in the PR.
References
-
Robo.js
- Sage Mode: https://robojs.dev/discord-bots/sage
- Commands (file-based): https://robojs.dev/discord-bots/commands
- CLI overview: https://robojs.dev/cli/robo
- CLI overview: https://robojs.dev/cli/robo
-
Discord Platform
- Privileged Gateway Intents (overview): https://discord.com/developers/docs/events/gateway
- Message Content Privileged Intent FAQ: https://support-dev.discord.com/hc/en-us/articles/4404772028055-Message-Content-Privileged-Intent-FAQ
- What are Privileged Intents?: https://support-dev.discord.com/hc/en-us/articles/6207308062871-What-are-Privileged-Intents
- Rate Limits: https://discord.com/developers/docs/topics/rate-limits
- Gateway Events (Message Create): https://discord.com/developers/docs/events/gateway-events
- Messages resource: https://discord.com/developers/docs/resources/message
-
discord.js
Client&messageCreateevent: https://discord.js.org/docs/packages/discord.js/main/Client%3AclassMessage.edit: https://discord.js.org/docs/packages/discord.js/main/Message%3AClasschannel.sendTyping(): https://discord.js.org/docs/packages/discord.js/main/DMChannel%3AClass