mjx-client 1.0.0-beta.7
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/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/src/client.d.ts +36 -0
- package/dist/src/client.js +149 -0
- package/dist/src/lib/collector.d.ts +11 -0
- package/dist/src/lib/collector.js +54 -0
- package/dist/src/lib/logger.d.ts +6 -0
- package/dist/src/lib/logger.js +15 -0
- package/dist/tests/Commands/ping.d.ts +2 -0
- package/dist/tests/Commands/ping.js +10 -0
- package/dist/tests/Events/message.d.ts +3 -0
- package/dist/tests/Events/message.js +14 -0
- package/dist/tests/Events/ready.d.ts +2 -0
- package/dist/tests/Events/ready.js +9 -0
- package/dist/tests/main.d.ts +1 -0
- package/dist/tests/main.js +12 -0
- package/package.json +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 majcek210
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# MJX CLIENT
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/mjx-client)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
MJX Client is a highly customizable Discord bot framework built on top of Discord.js v14. It allows structured management of commands, events, and bot behavior, providing a flexible framework for creating Discord bots.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- Structured event handling with support for `once` and recurring events.
|
|
13
|
+
- Easy registration of slash commands and automatic handling of interactions.
|
|
14
|
+
- Built-in debug logging
|
|
15
|
+
- Modular architecture with dynamic command and event loading.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install mjx-client
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
MJX Client can be configured during instantiation, allowing you to set the client name(which can be acessed anytime from the client), enable debug mode, and specify Discord intents.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Directory Structure
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
project/
|
|
37
|
+
│
|
|
38
|
+
├─ commands/ # Slash commands
|
|
39
|
+
├─ events/ # Event handlers
|
|
40
|
+
├─ src/
|
|
41
|
+
│ └─ index.ts # Main bot entry
|
|
42
|
+
└─ package.json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
MJX Client is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Client as DiscordClient, IntentsBitField, ChatInputCommandInteraction, SlashCommandBuilder, Collection, ClientEvents } from "discord.js";
|
|
2
|
+
type ClientOptions = {
|
|
3
|
+
name?: string;
|
|
4
|
+
debug?: boolean;
|
|
5
|
+
intents?: IntentsBitField;
|
|
6
|
+
};
|
|
7
|
+
export interface Command {
|
|
8
|
+
data: Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">;
|
|
9
|
+
execute: (interaction: ChatInputCommandInteraction) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export interface Event<K extends keyof ClientEvents = keyof ClientEvents> {
|
|
12
|
+
name: K;
|
|
13
|
+
once?: boolean;
|
|
14
|
+
execute: (...args: ClientEvents[K]) => void | Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export default class Client {
|
|
17
|
+
private _name;
|
|
18
|
+
private _debug;
|
|
19
|
+
private started;
|
|
20
|
+
private _discord;
|
|
21
|
+
commands: Collection<string, Command>;
|
|
22
|
+
events: Collection<string, Event>;
|
|
23
|
+
clientId: string | undefined;
|
|
24
|
+
constructor(options?: ClientOptions);
|
|
25
|
+
setName(name: string): this;
|
|
26
|
+
setDebug(enabled: boolean): this;
|
|
27
|
+
get name(): string;
|
|
28
|
+
get debug(): boolean;
|
|
29
|
+
get discord(): DiscordClient;
|
|
30
|
+
start(token?: string): Promise<this>;
|
|
31
|
+
registerCommandsRoute(dir: string): Promise<void>;
|
|
32
|
+
registerEventsRoute(dir: string): Promise<void>;
|
|
33
|
+
pushCommands(token?: string, guildId?: string): Promise<void>;
|
|
34
|
+
private ensureMutable;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Client as DiscordClient, GatewayIntentBits, Collection, Events, } from "discord.js";
|
|
2
|
+
import { CollectCommands, CollectEvents } from "./lib/collector.js";
|
|
3
|
+
import logger from "./lib/logger.js";
|
|
4
|
+
import { REST } from "@discordjs/rest";
|
|
5
|
+
import { Routes } from "discord.js";
|
|
6
|
+
export default class Client {
|
|
7
|
+
_name;
|
|
8
|
+
_debug;
|
|
9
|
+
started = false;
|
|
10
|
+
_discord;
|
|
11
|
+
commands = new Collection();
|
|
12
|
+
events = new Collection();
|
|
13
|
+
clientId = undefined;
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this._name = options.name ?? "Unnamed Client";
|
|
16
|
+
this._debug = options.debug ?? false;
|
|
17
|
+
this._discord = new DiscordClient({
|
|
18
|
+
intents: options.intents ?? [
|
|
19
|
+
GatewayIntentBits.Guilds,
|
|
20
|
+
GatewayIntentBits.GuildMessages,
|
|
21
|
+
GatewayIntentBits.MessageContent,
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
setName(name) {
|
|
26
|
+
this.ensureMutable();
|
|
27
|
+
if (name.length < 3) {
|
|
28
|
+
throw new Error("Client name must be at least 3 characters");
|
|
29
|
+
}
|
|
30
|
+
this._name = name;
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
setDebug(enabled) {
|
|
34
|
+
this.ensureMutable();
|
|
35
|
+
this._debug = enabled;
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
get name() {
|
|
39
|
+
return this._name;
|
|
40
|
+
}
|
|
41
|
+
get debug() {
|
|
42
|
+
return this._debug;
|
|
43
|
+
}
|
|
44
|
+
get discord() {
|
|
45
|
+
return this._discord;
|
|
46
|
+
}
|
|
47
|
+
async start(token) {
|
|
48
|
+
if (this.started) {
|
|
49
|
+
throw new Error("Client already started");
|
|
50
|
+
}
|
|
51
|
+
const resolvedToken = token ?? process.env.DISCORD_TOKEN;
|
|
52
|
+
if (!resolvedToken) {
|
|
53
|
+
throw new Error("DISCORD_TOKEN is missing");
|
|
54
|
+
}
|
|
55
|
+
this._discord.once(Events.ClientReady, () => {
|
|
56
|
+
logger.output(`${this._name} logged in as ${this._discord.user?.tag}`);
|
|
57
|
+
if (!this.clientId) {
|
|
58
|
+
this.clientId = this._discord?.user?.id;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
this._discord.on("interactionCreate", async (interaction) => {
|
|
62
|
+
if (!interaction.isChatInputCommand())
|
|
63
|
+
return;
|
|
64
|
+
const command = this.commands.get(interaction.commandName);
|
|
65
|
+
if (!command)
|
|
66
|
+
return;
|
|
67
|
+
try {
|
|
68
|
+
await command.execute(interaction);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
logger.error(err);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
await this._discord.login(resolvedToken);
|
|
75
|
+
this.events.forEach((event) => {
|
|
76
|
+
if (event.once) {
|
|
77
|
+
try {
|
|
78
|
+
this._discord.once(event.name, (...args) => event.execute(...args));
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
logger.error(err);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
try {
|
|
86
|
+
this._discord.on(event.name, (...args) => event.execute(...args));
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
logger.error(err);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
this.started = true;
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
async registerCommandsRoute(dir) {
|
|
97
|
+
const { total, loaded, commands } = await CollectCommands(dir);
|
|
98
|
+
commands.forEach((cmd, name) => this.commands.set(name, cmd));
|
|
99
|
+
if (this._debug)
|
|
100
|
+
logger.output(`Loaded ${loaded} out of ${total} commands.`);
|
|
101
|
+
}
|
|
102
|
+
async registerEventsRoute(dir) {
|
|
103
|
+
const { total, loaded, events } = await CollectEvents(dir);
|
|
104
|
+
events.forEach((event, name) => this.events.set(name, event));
|
|
105
|
+
if (this._debug)
|
|
106
|
+
logger.output(`Loaded ${loaded} out of ${total} events.`);
|
|
107
|
+
}
|
|
108
|
+
async pushCommands(token, guildId) {
|
|
109
|
+
const resolvedToken = token ?? process.env.DISCORD_TOKEN;
|
|
110
|
+
if (!resolvedToken) {
|
|
111
|
+
logger.error("DISCORD_TOKEN is missing");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!this._discord.isReady()) {
|
|
115
|
+
logger.warn("Client isn't ready yet. Waiting...");
|
|
116
|
+
await new Promise((resolve) => {
|
|
117
|
+
this._discord.once(Events.ClientReady, () => resolve());
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const commandsData = this.commands.map((cmd) => cmd.data.toJSON());
|
|
121
|
+
const rest = new REST({ version: "10" }).setToken(resolvedToken);
|
|
122
|
+
if (!this.clientId) {
|
|
123
|
+
logger.error("Client ID wasnt intalized correctly, try adding it manualy with setClientId");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
if (guildId) {
|
|
128
|
+
await rest.put(Routes.applicationGuildCommands(this.clientId, guildId), { body: commandsData });
|
|
129
|
+
if (this._debug)
|
|
130
|
+
logger.output(`Registered ${commandsData.length} commands to guild ${guildId}`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
await rest.put(Routes.applicationCommands(this.clientId), {
|
|
134
|
+
body: commandsData,
|
|
135
|
+
});
|
|
136
|
+
if (this._debug)
|
|
137
|
+
logger.output(`Registered ${commandsData.length} global commands`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
logger.error("Failed to register commands:", err);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
ensureMutable() {
|
|
145
|
+
if (this.started) {
|
|
146
|
+
throw new Error("Cannot modify client after start");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Command, Event } from "../client.js";
|
|
2
|
+
export declare function CollectCommands(dir: string): Promise<{
|
|
3
|
+
total: number;
|
|
4
|
+
loaded: number;
|
|
5
|
+
commands: Map<string, Command>;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function CollectEvents(dir: string): Promise<{
|
|
8
|
+
total: number;
|
|
9
|
+
loaded: number;
|
|
10
|
+
events: Map<string, Event>;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import logger from "./logger.js";
|
|
4
|
+
import { pathToFileURL } from "url";
|
|
5
|
+
export async function CollectCommands(dir) {
|
|
6
|
+
try {
|
|
7
|
+
const commands = new Map();
|
|
8
|
+
const files = fs
|
|
9
|
+
.readdirSync(dir)
|
|
10
|
+
.filter((f) => f.endsWith(".js"));
|
|
11
|
+
const total = files.length;
|
|
12
|
+
let loaded = 0;
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
const filePath = path.join(dir, file);
|
|
15
|
+
const module = await import(pathToFileURL(filePath).href);
|
|
16
|
+
const command = module.default ?? module.command;
|
|
17
|
+
if (!command?.data || !command?.execute)
|
|
18
|
+
continue;
|
|
19
|
+
commands.set(command.data.name, command);
|
|
20
|
+
loaded++;
|
|
21
|
+
}
|
|
22
|
+
return { total, loaded, commands };
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
logger.error(err);
|
|
26
|
+
return {
|
|
27
|
+
total: 0,
|
|
28
|
+
loaded: 0,
|
|
29
|
+
commands: new Map(),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function CollectEvents(dir) {
|
|
34
|
+
try {
|
|
35
|
+
const events = new Map();
|
|
36
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith(".js"));
|
|
37
|
+
const total = files.length;
|
|
38
|
+
let loaded = 0;
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
const filePath = path.join(dir, file);
|
|
41
|
+
const module = await import(pathToFileURL(filePath).href);
|
|
42
|
+
const event = module.default ?? module.event;
|
|
43
|
+
if (!event?.name || !event?.execute)
|
|
44
|
+
continue;
|
|
45
|
+
events.set(event.name, event);
|
|
46
|
+
loaded++;
|
|
47
|
+
}
|
|
48
|
+
return { total, loaded, events };
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
logger.error(err);
|
|
52
|
+
return { total: 0, loaded: 0, events: new Map() };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default class logger {
|
|
2
|
+
static format(message) {
|
|
3
|
+
const timestamp = new Date().toISOString();
|
|
4
|
+
return `[${timestamp}] ${message}`;
|
|
5
|
+
}
|
|
6
|
+
static output(...messages) {
|
|
7
|
+
console.log(this.format(messages.map(String).join(" ")));
|
|
8
|
+
}
|
|
9
|
+
static warn(...messages) {
|
|
10
|
+
console.warn(`\x1b[33m${this.format(messages.map(String).join(" "))}\x1b[0m`);
|
|
11
|
+
}
|
|
12
|
+
static error(...messages) {
|
|
13
|
+
console.error(`\x1b[31m${this.format(messages.map(String).join(" "))}\x1b[0m`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SlashCommandBuilder } from "discord.js";
|
|
2
|
+
const pingCommand = {
|
|
3
|
+
data: new SlashCommandBuilder()
|
|
4
|
+
.setName("ping")
|
|
5
|
+
.setDescription("Replies with Pong!"),
|
|
6
|
+
execute: async (interaction) => {
|
|
7
|
+
await interaction.reply("Pong!");
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
export default pingCommand;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Events } from "discord.js";
|
|
2
|
+
const messageEvent = {
|
|
3
|
+
name: Events.MessageCreate,
|
|
4
|
+
once: false,
|
|
5
|
+
async execute(message) {
|
|
6
|
+
if (message.author.bot)
|
|
7
|
+
return;
|
|
8
|
+
const channel = message.client.channels.cache.get(process.env.TEST_CHANNEL);
|
|
9
|
+
if (channel?.isTextBased()) {
|
|
10
|
+
await channel.send(`Hello! Received a message from ${message.author.tag}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export default messageEvent;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import Client from "../src/client.js";
|
|
3
|
+
const client = new Client({ debug: true })
|
|
4
|
+
.setName("tuff bot");
|
|
5
|
+
client.registerEventsRoute("./dist/tests/Events");
|
|
6
|
+
client.registerCommandsRoute("./dist/tests/Commands");
|
|
7
|
+
client.discord.on("messageCreate", (msg) => {
|
|
8
|
+
if (msg.content === "!ping")
|
|
9
|
+
msg.reply("pong");
|
|
10
|
+
});
|
|
11
|
+
client.start(process.env.TOKEN);
|
|
12
|
+
client.pushCommands(process.env.TOKEN);
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mjx-client",
|
|
3
|
+
"version": "1.0.0-beta.7",
|
|
4
|
+
"description": "A discord bot framework built on top of discord.js made by majcek210",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bot",
|
|
7
|
+
"discord",
|
|
8
|
+
"discord.js"
|
|
9
|
+
],
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"author": "majcek210",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "dist/src/client.js",
|
|
14
|
+
"types": "dist/src/client.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"test": "node dist/tests/main.js"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"ts-node": "^10.9.2",
|
|
24
|
+
"typescript": "^5.9.3"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"discord.js": "^14.25.1"
|
|
28
|
+
}
|
|
29
|
+
}
|