@marshmallow-stoat/mally 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -0
- package/dist/index.d.mts +549 -0
- package/dist/index.d.ts +549 -0
- package/dist/index.js +661 -0
- package/dist/index.mjs +610 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# @marshmallow/mally
|
|
2
|
+
|
|
3
|
+
A high-performance, decorator-based command handler for the [Stoat](https://github.com/valarium/stoat.js) ecosystem. Inspired by [discordx](https://github.com/discordx-ts/discordx).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Decorator-based** - Use `@Stoat()` and `@SimpleCommand()` decorators like discordx
|
|
8
|
+
- **Guards** - Built-in guard system for permissions and checks
|
|
9
|
+
- **Cooldowns** - Per-command cooldown support
|
|
10
|
+
- **Organized** - Group multiple commands in a single class
|
|
11
|
+
- **Type-safe** - Full TypeScript support
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @marshmallow/mally reflect-metadata
|
|
17
|
+
# or
|
|
18
|
+
pnpm add @marshmallow/mally reflect-metadata
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Make sure to enable decorators in your `tsconfig.json`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"compilerOptions": {
|
|
26
|
+
"experimentalDecorators": true,
|
|
27
|
+
"emitDecoratorMetadata": true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### 1. Create your handler
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// index.ts
|
|
38
|
+
import 'reflect-metadata';
|
|
39
|
+
import { Client } from 'stoat.js';
|
|
40
|
+
import { MallyHandler } from '@marshmallow/mally';
|
|
41
|
+
import { join } from 'path';
|
|
42
|
+
|
|
43
|
+
const client = new Client();
|
|
44
|
+
|
|
45
|
+
const handler = new MallyHandler({
|
|
46
|
+
client,
|
|
47
|
+
prefix: '!',
|
|
48
|
+
owners: ['your-user-id'],
|
|
49
|
+
commandsDir: join(__dirname, "commands")
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await handler.init();
|
|
53
|
+
|
|
54
|
+
client.on('messageCreate', (message) => {
|
|
55
|
+
handler.handleMessage(message);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
client.login('your-token');
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2. Create commands
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// commands/general.ts
|
|
65
|
+
import { Stoat, SimpleCommand, Context } from '@marshmallow/mally';
|
|
66
|
+
|
|
67
|
+
@Stoat()
|
|
68
|
+
export class GeneralCommands {
|
|
69
|
+
@SimpleCommand({ name: 'ping', description: 'Check bot latency' })
|
|
70
|
+
async ping(ctx: Context) {
|
|
71
|
+
await ctx.reply(`Pong! 🏓`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@SimpleCommand({ name: 'hello', aliases: ['hi', 'hey'] })
|
|
75
|
+
async hello(ctx: Context) {
|
|
76
|
+
await ctx.reply(`Hello, <@${ctx.authorId}>!`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
That's it! No manual imports needed - commands are auto-discovered.
|
|
82
|
+
|
|
83
|
+
## Decorators
|
|
84
|
+
|
|
85
|
+
### @Stoat()
|
|
86
|
+
|
|
87
|
+
Marks a class as a command container. All `@SimpleCommand()` methods inside will be registered.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
@Stoat()
|
|
91
|
+
export class MyCommands {
|
|
92
|
+
// commands go here
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### @SimpleCommand(options)
|
|
97
|
+
|
|
98
|
+
Marks a method as a command.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
@SimpleCommand({
|
|
102
|
+
name: 'ban', // Command name (defaults to method name)
|
|
103
|
+
description: 'Ban a user',
|
|
104
|
+
aliases: ['b'], // Alternative names
|
|
105
|
+
permissions: ['BanMembers'], // This is currently not implemented, but will be in the future
|
|
106
|
+
cooldown: 5000, // 5 seconds
|
|
107
|
+
ownerOnly: false,
|
|
108
|
+
nsfw: false,
|
|
109
|
+
})
|
|
110
|
+
async ban(ctx: Context) {
|
|
111
|
+
// ...
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### @Guard(GuardClass)
|
|
116
|
+
|
|
117
|
+
Adds a guard check before command execution.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { Stoat, SimpleCommand, Guard, MallyGuard, Context } from '@marshmallow/mally';
|
|
121
|
+
|
|
122
|
+
// Define a guard
|
|
123
|
+
class IsAdmin implements MallyGuard {
|
|
124
|
+
run(ctx: Context): boolean {
|
|
125
|
+
return ctx.message.member?.hasPermission('Administrator') ?? false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
guardFail(ctx: Context): void {
|
|
129
|
+
ctx.reply('You need Administrator permission!');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Stoat()
|
|
134
|
+
@Guard(IsAdmin)
|
|
135
|
+
export class AdminCommands {
|
|
136
|
+
@SimpleCommand({ name: 'shutdown' })
|
|
137
|
+
async shutdown(ctx: Context) {
|
|
138
|
+
await ctx.reply('Shutting down...');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Context
|
|
144
|
+
|
|
145
|
+
The `Context` object provides:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
interface Context {
|
|
149
|
+
client: Client; // Stoat client instance
|
|
150
|
+
message: Message; // Original message
|
|
151
|
+
content: string; // Raw message content
|
|
152
|
+
authorId: string; // Author's user ID
|
|
153
|
+
channelId: string; // Channel ID
|
|
154
|
+
serverId?: string; // Server/Guild ID
|
|
155
|
+
args: string[]; // Parsed arguments
|
|
156
|
+
prefix: string; // Prefix used
|
|
157
|
+
commandName: string; // Command name used
|
|
158
|
+
|
|
159
|
+
reply(content: string): Promise<void>;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Handler Options
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
interface MallyHandlerOptions {
|
|
167
|
+
client: Client;
|
|
168
|
+
commandsDir: string; // Directory to scan for commands
|
|
169
|
+
prefix: string | ((ctx: { serverId?: string }) => string | Promise<string>);
|
|
170
|
+
owners?: string[]; // Owner user IDs
|
|
171
|
+
extensions?: string[]; // File extensions (default: ['.js', '.ts'])
|
|
172
|
+
disableMentionPrefix?: boolean; // Disable @bot prefix
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Dynamic Prefix
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const handler = new MallyHandler({
|
|
180
|
+
client,
|
|
181
|
+
commandsDir: join(__dirname, 'commands'),
|
|
182
|
+
prefix: async ({ serverId }) => {
|
|
183
|
+
// Fetch from database, etc.
|
|
184
|
+
return serverId ? await getServerPrefix(serverId) : '!';
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Legacy Class-Based Commands
|
|
190
|
+
|
|
191
|
+
You can also use the class-based approach:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { Command, BaseCommand, Context } from '@marshmallow/mally';
|
|
195
|
+
|
|
196
|
+
@Command({
|
|
197
|
+
name: 'ping',
|
|
198
|
+
description: 'Ping command',
|
|
199
|
+
})
|
|
200
|
+
export class PingCommand extends BaseCommand {
|
|
201
|
+
async run(ctx: Context) {
|
|
202
|
+
await ctx.reply('Pong!');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
AGPL-3.0-or-later
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|