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/** (using node:fs), mirroring Robo’s file-based philosophy.
- Sage-like return semantics: command handlers may simply
return strings/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
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:
-
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
allowDMs is 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
messageCreate events; 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:
!ping works 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
-
Discord Platform
-
discord.js
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-commandsfolder, routesmessageCreateevents 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.Goals
Requirements & Constraints
messageCreate; only process when content begins with a configured prefix or bot mention.Scope & Features
/src/prefix-commands/**(usingnode:fs), mirroring Robo’s file-based philosophy.returnstrings/embeds to reply; throwing yields a friendly error reply.API Design
1) Plugin Configuration (file:
config/plugins/prefix.mjs)2) Imperative API (for programmatic control)
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.
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)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:On throw: reply with
errorStyle.message(and optionally stack when configured). Ensure edits respect rate limits.C) Rate Limits & Reliability
D) Security, Privacy, and Intents
allowDMsis enabled.Usage Examples
1) Simple Ping
2) Long Task with Explicit Thinking
Suggested Project Layout (plugin repository)
Testing (Encouraged, Not Required)
messageCreateevents; assert reply/edit behavior under/over 300 ms.!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.Nice-to-haves:
ping,report,args-demo).References
Robo.js
Discord Platform
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