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 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
+ [![npm version](https://img.shields.io/npm/v/mjx-client)](https://www.npmjs.com/package/mjx-client)
4
+ [![License](https://img.shields.io/npm/l/mjx-client)](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,6 @@
1
+ export default class logger {
2
+ private static format;
3
+ static output(...messages: any[]): void;
4
+ static warn(...messages: any[]): void;
5
+ static error(...messages: any[]): void;
6
+ }
@@ -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,2 @@
1
+ declare const pingCommand: any;
2
+ export default pingCommand;
@@ -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,3 @@
1
+ import type { Event } from "../../src/client.js";
2
+ declare const messageEvent: Event<"messageCreate">;
3
+ export default messageEvent;
@@ -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,2 @@
1
+ declare const readyEvent: any;
2
+ export default readyEvent;
@@ -0,0 +1,9 @@
1
+ import { Events } from "discord.js";
2
+ const readyEvent = {
3
+ name: Events.ClientReady,
4
+ once: true,
5
+ execute(client) {
6
+ console.log(`Ready! Logged in as ${client.user.tag}`);
7
+ }
8
+ };
9
+ export default readyEvent;
@@ -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
+ }