@spatulox/discord-module 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 ADDED
@@ -0,0 +1,64 @@
1
+ # Discord Module System
2
+
3
+ An ultra-simple and fully type-safe TypeScript module system for Discord.js
4
+
5
+ ## ๐Ÿš€ Whatโ€™s it for ?
6
+
7
+ Turn your Discord bot into independent modules that can be enabled or disabled at will.
8
+
9
+ โœจ Fonctionalities
10
+
11
+ ๐Ÿ”— Auto-binding : module.events โ†’ automatic client.on()
12
+
13
+ ๐ŸŽฏ Free name : handleMessage() or myPingHandler() โ†’ you choose !
14
+
15
+ โšก Performance : Only declared event are bind
16
+
17
+ ๐Ÿ”„ Hot reload : Enable/Disable problematic modules without restarting the bot !
18
+
19
+ Discordjs : Always up to date and completely compatible
20
+
21
+ ## ๐ŸŽฎ Usage (2 minutes)
22
+ 1. Module example
23
+ ```ts
24
+ export class PongModule extends Module {
25
+ public name: string = "Pong Module";
26
+ public description: string = "Reply with pong";
27
+ public get events(): ModuleEventsMap {
28
+ return {
29
+ [Events.MessageCreate]: this.handleMessage,
30
+ [Events.MessageUpdate]: [this.handleMessageUpdate1, this.handleMessageUpdate2],
31
+ }
32
+ }
33
+
34
+ async handleMessage(message: Message) {
35
+ if(message.content == "!ping") {
36
+ message.reply("Pong !")
37
+ }
38
+ }
39
+
40
+ async handleMessageUpdate1(message: Message) {
41
+ message.reply("Update 1 !")
42
+ }
43
+
44
+ async handleMessageUpdate2(message: Message) {
45
+ message.reply("Update 2 !")
46
+ }
47
+
48
+ }
49
+ ```
50
+ 2. Bot
51
+ ```ts
52
+ client.once(Events.ClientReady, () => {
53
+ const manager = ModuleManager.createInstance(client); // ModuleManager is a singleton
54
+ manager.register(new PongModule(client)); // You can register a Module or a MultiModule (Menu for Module)
55
+ manager.enableAll(); // By default, a Module is disable
56
+ manager.sendUIToChannel("channelID") // Optionnal, only if you want to dynamically toggle modules
57
+ });
58
+ ```
59
+
60
+ | Functionnalities | Without Modules | With Module |
61
+ |---------------------|------------------|---------------|
62
+ | Hidden client.on | โŒ | โœ… |
63
+ | Live module enabled | โŒ (Need restart) | โœ… (One click) |
64
+ | Organised | โŒ | โœ… |
@@ -0,0 +1,68 @@
1
+ import { ClientEvents, SectionBuilder, InteractionReplyOptions, ContainerBuilder, ButtonInteraction, Client } from 'discord.js';
2
+
3
+ type ModuleEventHandler = ((...args: any[]) => any) | ((...args: any[]) => any)[];
4
+ type ModuleEventsMap = Partial<Record<keyof ClientEvents, ModuleEventHandler>>;
5
+ declare abstract class Module {
6
+ private _parent;
7
+ abstract name: string;
8
+ abstract description: string;
9
+ private _enabled;
10
+ abstract get events(): ModuleEventsMap;
11
+ setParent(parent: string): void;
12
+ get parent(): string | "root";
13
+ createModuleUI(namePrefix?: string): SectionBuilder;
14
+ showModule(): InteractionReplyOptions;
15
+ get enabled(): boolean;
16
+ toggle(): void;
17
+ enable(): void;
18
+ disable(): void;
19
+ }
20
+
21
+ declare abstract class MultiModule extends Module {
22
+ private readonly manager;
23
+ abstract readonly subModules: Module[];
24
+ constructor();
25
+ get events(): ModuleEventsMap;
26
+ protected createSubmoduleUI(): ContainerBuilder;
27
+ showModule(): InteractionReplyOptions;
28
+ enable(interaction?: ButtonInteraction): void;
29
+ enableAll(interaction?: ButtonInteraction): Promise<void>;
30
+ disable(interaction?: ButtonInteraction): void;
31
+ disableAll(interaction?: ButtonInteraction): Promise<void>;
32
+ notifyChange(interaction?: ButtonInteraction): void;
33
+ isAnyEnabled(): boolean;
34
+ }
35
+
36
+ type ModuleMap = Record<string, Module[]>;
37
+ declare class ModuleManager {
38
+ private _modules;
39
+ private client;
40
+ private static instance;
41
+ private static message;
42
+ private constructor();
43
+ static createInstance(client: Client): ModuleManager;
44
+ private static initClient;
45
+ static getInstance(): ModuleManager | null;
46
+ get modules(): ModuleMap;
47
+ register(module: Module | MultiModule): void;
48
+ private registerMod;
49
+ private createManagerUI;
50
+ private bindEvents;
51
+ enableAll(): void;
52
+ disableAll(): void;
53
+ getModule(name: string): Module | undefined;
54
+ get enabledCount(): number;
55
+ sendUIToChannel(channelID: string): Promise<void>;
56
+ /**
57
+ * Global update for the main message
58
+ */
59
+ private updateMainUI;
60
+ /**
61
+ * This update the MultiModule component when a single module is updated
62
+ * @param interaction
63
+ * @param module
64
+ */
65
+ updateMultiModuleUI(interaction: ButtonInteraction, module: Module): void;
66
+ }
67
+
68
+ export { Module, type ModuleEventsMap, ModuleManager, MultiModule };
@@ -0,0 +1,68 @@
1
+ import { ClientEvents, SectionBuilder, InteractionReplyOptions, ContainerBuilder, ButtonInteraction, Client } from 'discord.js';
2
+
3
+ type ModuleEventHandler = ((...args: any[]) => any) | ((...args: any[]) => any)[];
4
+ type ModuleEventsMap = Partial<Record<keyof ClientEvents, ModuleEventHandler>>;
5
+ declare abstract class Module {
6
+ private _parent;
7
+ abstract name: string;
8
+ abstract description: string;
9
+ private _enabled;
10
+ abstract get events(): ModuleEventsMap;
11
+ setParent(parent: string): void;
12
+ get parent(): string | "root";
13
+ createModuleUI(namePrefix?: string): SectionBuilder;
14
+ showModule(): InteractionReplyOptions;
15
+ get enabled(): boolean;
16
+ toggle(): void;
17
+ enable(): void;
18
+ disable(): void;
19
+ }
20
+
21
+ declare abstract class MultiModule extends Module {
22
+ private readonly manager;
23
+ abstract readonly subModules: Module[];
24
+ constructor();
25
+ get events(): ModuleEventsMap;
26
+ protected createSubmoduleUI(): ContainerBuilder;
27
+ showModule(): InteractionReplyOptions;
28
+ enable(interaction?: ButtonInteraction): void;
29
+ enableAll(interaction?: ButtonInteraction): Promise<void>;
30
+ disable(interaction?: ButtonInteraction): void;
31
+ disableAll(interaction?: ButtonInteraction): Promise<void>;
32
+ notifyChange(interaction?: ButtonInteraction): void;
33
+ isAnyEnabled(): boolean;
34
+ }
35
+
36
+ type ModuleMap = Record<string, Module[]>;
37
+ declare class ModuleManager {
38
+ private _modules;
39
+ private client;
40
+ private static instance;
41
+ private static message;
42
+ private constructor();
43
+ static createInstance(client: Client): ModuleManager;
44
+ private static initClient;
45
+ static getInstance(): ModuleManager | null;
46
+ get modules(): ModuleMap;
47
+ register(module: Module | MultiModule): void;
48
+ private registerMod;
49
+ private createManagerUI;
50
+ private bindEvents;
51
+ enableAll(): void;
52
+ disableAll(): void;
53
+ getModule(name: string): Module | undefined;
54
+ get enabledCount(): number;
55
+ sendUIToChannel(channelID: string): Promise<void>;
56
+ /**
57
+ * Global update for the main message
58
+ */
59
+ private updateMainUI;
60
+ /**
61
+ * This update the MultiModule component when a single module is updated
62
+ * @param interaction
63
+ * @param module
64
+ */
65
+ updateMultiModuleUI(interaction: ButtonInteraction, module: Module): void;
66
+ }
67
+
68
+ export { Module, type ModuleEventsMap, ModuleManager, MultiModule };
package/dist/index.js ADDED
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Module: () => Module,
24
+ ModuleManager: () => ModuleManager,
25
+ MultiModule: () => MultiModule
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/code/Module.ts
30
+ var import_discord = require("discord.js");
31
+ var Module = class {
32
+ constructor() {
33
+ this._parent = "root";
34
+ this._enabled = false;
35
+ }
36
+ setParent(parent) {
37
+ this._parent = parent;
38
+ }
39
+ get parent() {
40
+ return this._parent;
41
+ }
42
+ createModuleUI(namePrefix) {
43
+ const namePrefixStr = namePrefix ? `${namePrefix}_` : "";
44
+ const name = `${namePrefixStr}toggle_${this.name.toLowerCase()}`;
45
+ if (name.length > 100) {
46
+ throw new Error(`In order to create the Module UI, buttons customId should not be more than 100 char, please reduce the name of your Module : ${this.name}`);
47
+ }
48
+ return new import_discord.SectionBuilder().addTextDisplayComponents(new import_discord.TextDisplayBuilder().setContent(`## ${this.enabled ? "\u{1F7E2}" : "\u{1F534}"} ${this.name}`)).addTextDisplayComponents(new import_discord.TextDisplayBuilder().setContent(`${this.description}`)).setButtonAccessory(new import_discord.ButtonBuilder().setLabel(this.enabled ? "Disabled" : "Enable").setCustomId(name).setStyle(this.enabled ? import_discord.ButtonStyle.Danger : import_discord.ButtonStyle.Success));
49
+ }
50
+ showModule() {
51
+ return {
52
+ components: [new import_discord.ContainerBuilder().addSectionComponents(this.createModuleUI())],
53
+ flags: import_discord.MessageFlags.IsComponentsV2
54
+ };
55
+ }
56
+ get enabled() {
57
+ return this._enabled;
58
+ }
59
+ toggle() {
60
+ this._enabled = !this._enabled;
61
+ }
62
+ enable() {
63
+ this._enabled = true;
64
+ }
65
+ disable() {
66
+ this._enabled = false;
67
+ }
68
+ };
69
+
70
+ // src/code/ModuleManager.ts
71
+ var import_discord2 = require("discord.js");
72
+ var _ModuleManager = class _ModuleManager {
73
+ constructor(client) {
74
+ this._modules = {};
75
+ this.client = client;
76
+ }
77
+ static createInstance(client) {
78
+ _ModuleManager.instance = new _ModuleManager(client);
79
+ this.initClient(client);
80
+ return _ModuleManager.instance;
81
+ }
82
+ static initClient(client) {
83
+ client.on(import_discord2.Events.InteractionCreate, async (interaction) => {
84
+ if (interaction.isButton()) {
85
+ const custID = interaction.customId;
86
+ console.log(custID);
87
+ const manager = _ModuleManager.getInstance();
88
+ if (custID.startsWith("toggle_")) {
89
+ const module2 = manager?.getModule(custID.split("toggle_")[1]);
90
+ if (module2 instanceof MultiModule) {
91
+ interaction.reply(module2.showModule());
92
+ } else if (module2 instanceof Module) {
93
+ module2.toggle();
94
+ manager?.updateMultiModuleUI(interaction, module2);
95
+ }
96
+ } else if (custID.startsWith("all_")) {
97
+ const module2 = manager?.getModule(custID.split("toggle_")[1]);
98
+ if (module2 instanceof MultiModule) {
99
+ module2.enabled ? await module2.disableAll(interaction) : await module2.enableAll(interaction);
100
+ manager?.updateMultiModuleUI(interaction, module2);
101
+ }
102
+ console.log(module2);
103
+ }
104
+ }
105
+ });
106
+ }
107
+ static getInstance() {
108
+ return _ModuleManager.instance;
109
+ }
110
+ get modules() {
111
+ return this._modules;
112
+ }
113
+ register(module2) {
114
+ if (this.getModule(module2.name)) {
115
+ throw new Error(`Duplicate Module name : '${module2.name}' already exist`);
116
+ }
117
+ if (module2 instanceof MultiModule) {
118
+ this.registerMod(module2);
119
+ for (const mod of module2.subModules) {
120
+ mod.setParent(module2.name);
121
+ if (mod instanceof MultiModule) {
122
+ throw new Error(`The Multi Module "${module2.name}" cannot have a Multi Module as a Module : ${mod.name}`);
123
+ }
124
+ this.registerMod(mod);
125
+ }
126
+ return;
127
+ }
128
+ this.registerMod(module2);
129
+ }
130
+ registerMod(module2) {
131
+ if (!this._modules[module2.parent]) {
132
+ this._modules[module2.parent] = [];
133
+ }
134
+ this._modules[module2.parent].push(module2);
135
+ this.bindEvents(module2);
136
+ }
137
+ createManagerUI() {
138
+ const container = new import_discord2.ContainerBuilder();
139
+ container.addTextDisplayComponents(new import_discord2.TextDisplayBuilder().setContent(`# Module Manager`)).addTextDisplayComponents(new import_discord2.TextDisplayBuilder().setContent(`You can enable/disable any module`)).addSeparatorComponents(new import_discord2.SeparatorBuilder().setDivider(true).setSpacing(import_discord2.SeparatorSpacingSize.Large));
140
+ for (const module2 of this._modules["root"]) {
141
+ container.addSectionComponents(module2.createModuleUI());
142
+ }
143
+ return container;
144
+ }
145
+ bindEvents(module2) {
146
+ const eventsMap = module2.events;
147
+ for (const [eventName, method] of Object.entries(eventsMap)) {
148
+ if (typeof method === "function") {
149
+ this.client?.on(eventName, async (...args) => {
150
+ if (module2.enabled) {
151
+ await method(...args);
152
+ }
153
+ });
154
+ } else if (Array.isArray(method)) {
155
+ for (const handler of method) {
156
+ if (typeof handler === "function") {
157
+ this.client?.on(eventName, async (...args) => {
158
+ if (module2.enabled) {
159
+ await handler(...args);
160
+ }
161
+ });
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ enableAll() {
168
+ Object.values(this._modules).forEach(
169
+ (modules) => modules.forEach((mod) => mod.enable())
170
+ );
171
+ }
172
+ disableAll() {
173
+ Object.values(this._modules).forEach(
174
+ (modules) => modules.forEach((mod) => mod.disable())
175
+ );
176
+ }
177
+ getModule(name) {
178
+ for (const parentModules of Object.values(this._modules)) {
179
+ const found = parentModules.find(
180
+ (m) => m.name.toLowerCase() === name.toLowerCase()
181
+ );
182
+ if (found) return found;
183
+ }
184
+ }
185
+ get enabledCount() {
186
+ return Object.values(this._modules).flat().filter((m) => m.enabled).length;
187
+ }
188
+ async sendUIToChannel(channelID) {
189
+ const channel = this.client?.channels.cache.get(channelID) || await this.client?.channels.fetch(channelID);
190
+ if (!channel) {
191
+ throw new Error(`Channel (${channelID}) does not exist or is unavailable`);
192
+ }
193
+ if (channel.isTextBased() && channel.isSendable()) {
194
+ _ModuleManager.message = await channel.send({
195
+ components: [this.createManagerUI()],
196
+ flags: import_discord2.MessageFlags.IsComponentsV2
197
+ });
198
+ return;
199
+ }
200
+ throw new Error(`Channel (${channelID}) does not exist or is not a valid sendable channel`);
201
+ }
202
+ /**
203
+ * Global update for the main message
204
+ */
205
+ async updateMainUI() {
206
+ const m = {
207
+ components: [this.createManagerUI()]
208
+ };
209
+ await _ModuleManager.message?.edit(m);
210
+ }
211
+ /**
212
+ * This update the MultiModule component when a single module is updated
213
+ * @param interaction
214
+ * @param module
215
+ */
216
+ updateMultiModuleUI(interaction, module2) {
217
+ const manager = _ModuleManager.getInstance();
218
+ if (manager) {
219
+ if (module2.parent == "root") {
220
+ this.updateMainUI();
221
+ }
222
+ const parentMod = manager.getModule(module2.parent);
223
+ if (!parentMod || !(parentMod instanceof MultiModule)) {
224
+ if (!(module2 instanceof MultiModule)) {
225
+ interaction.deferUpdate();
226
+ }
227
+ return;
228
+ }
229
+ parentMod.notifyChange(interaction);
230
+ return;
231
+ }
232
+ console.error("No existing manager");
233
+ }
234
+ };
235
+ _ModuleManager.message = null;
236
+ var ModuleManager = _ModuleManager;
237
+
238
+ // src/code/MultiModule.ts
239
+ var import_discord3 = require("discord.js");
240
+ var MultiModule = class extends Module {
241
+ constructor() {
242
+ super();
243
+ const inst = ModuleManager.getInstance();
244
+ if (inst) {
245
+ this.manager = inst;
246
+ } else {
247
+ throw new Error("Module Manager Instance is null");
248
+ }
249
+ }
250
+ get events() {
251
+ return [];
252
+ }
253
+ createSubmoduleUI() {
254
+ const container = new import_discord3.ContainerBuilder();
255
+ container.addSectionComponents(this.createModuleUI("all"));
256
+ container.addSeparatorComponents(new import_discord3.SeparatorBuilder().setSpacing(import_discord3.SeparatorSpacingSize.Large).setDivider(true));
257
+ for (const module2 of this.subModules) {
258
+ container.addSectionComponents(module2.createModuleUI());
259
+ }
260
+ return container;
261
+ }
262
+ showModule() {
263
+ return {
264
+ components: [this.createSubmoduleUI()],
265
+ flags: [import_discord3.MessageFlags.IsComponentsV2, import_discord3.MessageFlags.Ephemeral]
266
+ };
267
+ }
268
+ enable(interaction) {
269
+ super.enable();
270
+ this.notifyChange(interaction);
271
+ }
272
+ async enableAll(interaction) {
273
+ this.subModules.forEach((module2) => {
274
+ module2.enable();
275
+ });
276
+ this.enable(interaction);
277
+ }
278
+ disable(interaction) {
279
+ super.disable();
280
+ this.notifyChange(interaction);
281
+ }
282
+ async disableAll(interaction) {
283
+ this.subModules.forEach((module2) => {
284
+ module2.disable();
285
+ });
286
+ this.disable(interaction);
287
+ }
288
+ notifyChange(interaction) {
289
+ if (!interaction) return;
290
+ interaction.update({
291
+ components: [this.createSubmoduleUI()]
292
+ });
293
+ }
294
+ isAnyEnabled() {
295
+ return Object.values(this.manager.modules).flat().some((m) => m.enabled);
296
+ }
297
+ };
298
+ // Annotate the CommonJS export names for ESM import in node:
299
+ 0 && (module.exports = {
300
+ Module,
301
+ ModuleManager,
302
+ MultiModule
303
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,293 @@
1
+ // src/code/Module.ts
2
+ import {
3
+ ButtonBuilder,
4
+ ButtonStyle,
5
+ ContainerBuilder,
6
+ MessageFlags,
7
+ SectionBuilder,
8
+ TextDisplayBuilder
9
+ } from "discord.js";
10
+ var Module = class {
11
+ constructor() {
12
+ this._parent = "root";
13
+ this._enabled = false;
14
+ }
15
+ setParent(parent) {
16
+ this._parent = parent;
17
+ }
18
+ get parent() {
19
+ return this._parent;
20
+ }
21
+ createModuleUI(namePrefix) {
22
+ const namePrefixStr = namePrefix ? `${namePrefix}_` : "";
23
+ const name = `${namePrefixStr}toggle_${this.name.toLowerCase()}`;
24
+ if (name.length > 100) {
25
+ throw new Error(`In order to create the Module UI, buttons customId should not be more than 100 char, please reduce the name of your Module : ${this.name}`);
26
+ }
27
+ return new SectionBuilder().addTextDisplayComponents(new TextDisplayBuilder().setContent(`## ${this.enabled ? "\u{1F7E2}" : "\u{1F534}"} ${this.name}`)).addTextDisplayComponents(new TextDisplayBuilder().setContent(`${this.description}`)).setButtonAccessory(new ButtonBuilder().setLabel(this.enabled ? "Disabled" : "Enable").setCustomId(name).setStyle(this.enabled ? ButtonStyle.Danger : ButtonStyle.Success));
28
+ }
29
+ showModule() {
30
+ return {
31
+ components: [new ContainerBuilder().addSectionComponents(this.createModuleUI())],
32
+ flags: MessageFlags.IsComponentsV2
33
+ };
34
+ }
35
+ get enabled() {
36
+ return this._enabled;
37
+ }
38
+ toggle() {
39
+ this._enabled = !this._enabled;
40
+ }
41
+ enable() {
42
+ this._enabled = true;
43
+ }
44
+ disable() {
45
+ this._enabled = false;
46
+ }
47
+ };
48
+
49
+ // src/code/ModuleManager.ts
50
+ import {
51
+ ContainerBuilder as ContainerBuilder2,
52
+ Events,
53
+ MessageFlags as MessageFlags2,
54
+ SeparatorBuilder,
55
+ SeparatorSpacingSize,
56
+ TextDisplayBuilder as TextDisplayBuilder2
57
+ } from "discord.js";
58
+ var _ModuleManager = class _ModuleManager {
59
+ constructor(client) {
60
+ this._modules = {};
61
+ this.client = client;
62
+ }
63
+ static createInstance(client) {
64
+ _ModuleManager.instance = new _ModuleManager(client);
65
+ this.initClient(client);
66
+ return _ModuleManager.instance;
67
+ }
68
+ static initClient(client) {
69
+ client.on(Events.InteractionCreate, async (interaction) => {
70
+ if (interaction.isButton()) {
71
+ const custID = interaction.customId;
72
+ console.log(custID);
73
+ const manager = _ModuleManager.getInstance();
74
+ if (custID.startsWith("toggle_")) {
75
+ const module = manager?.getModule(custID.split("toggle_")[1]);
76
+ if (module instanceof MultiModule) {
77
+ interaction.reply(module.showModule());
78
+ } else if (module instanceof Module) {
79
+ module.toggle();
80
+ manager?.updateMultiModuleUI(interaction, module);
81
+ }
82
+ } else if (custID.startsWith("all_")) {
83
+ const module = manager?.getModule(custID.split("toggle_")[1]);
84
+ if (module instanceof MultiModule) {
85
+ module.enabled ? await module.disableAll(interaction) : await module.enableAll(interaction);
86
+ manager?.updateMultiModuleUI(interaction, module);
87
+ }
88
+ console.log(module);
89
+ }
90
+ }
91
+ });
92
+ }
93
+ static getInstance() {
94
+ return _ModuleManager.instance;
95
+ }
96
+ get modules() {
97
+ return this._modules;
98
+ }
99
+ register(module) {
100
+ if (this.getModule(module.name)) {
101
+ throw new Error(`Duplicate Module name : '${module.name}' already exist`);
102
+ }
103
+ if (module instanceof MultiModule) {
104
+ this.registerMod(module);
105
+ for (const mod of module.subModules) {
106
+ mod.setParent(module.name);
107
+ if (mod instanceof MultiModule) {
108
+ throw new Error(`The Multi Module "${module.name}" cannot have a Multi Module as a Module : ${mod.name}`);
109
+ }
110
+ this.registerMod(mod);
111
+ }
112
+ return;
113
+ }
114
+ this.registerMod(module);
115
+ }
116
+ registerMod(module) {
117
+ if (!this._modules[module.parent]) {
118
+ this._modules[module.parent] = [];
119
+ }
120
+ this._modules[module.parent].push(module);
121
+ this.bindEvents(module);
122
+ }
123
+ createManagerUI() {
124
+ const container = new ContainerBuilder2();
125
+ container.addTextDisplayComponents(new TextDisplayBuilder2().setContent(`# Module Manager`)).addTextDisplayComponents(new TextDisplayBuilder2().setContent(`You can enable/disable any module`)).addSeparatorComponents(new SeparatorBuilder().setDivider(true).setSpacing(SeparatorSpacingSize.Large));
126
+ for (const module of this._modules["root"]) {
127
+ container.addSectionComponents(module.createModuleUI());
128
+ }
129
+ return container;
130
+ }
131
+ bindEvents(module) {
132
+ const eventsMap = module.events;
133
+ for (const [eventName, method] of Object.entries(eventsMap)) {
134
+ if (typeof method === "function") {
135
+ this.client?.on(eventName, async (...args) => {
136
+ if (module.enabled) {
137
+ await method(...args);
138
+ }
139
+ });
140
+ } else if (Array.isArray(method)) {
141
+ for (const handler of method) {
142
+ if (typeof handler === "function") {
143
+ this.client?.on(eventName, async (...args) => {
144
+ if (module.enabled) {
145
+ await handler(...args);
146
+ }
147
+ });
148
+ }
149
+ }
150
+ }
151
+ }
152
+ }
153
+ enableAll() {
154
+ Object.values(this._modules).forEach(
155
+ (modules) => modules.forEach((mod) => mod.enable())
156
+ );
157
+ }
158
+ disableAll() {
159
+ Object.values(this._modules).forEach(
160
+ (modules) => modules.forEach((mod) => mod.disable())
161
+ );
162
+ }
163
+ getModule(name) {
164
+ for (const parentModules of Object.values(this._modules)) {
165
+ const found = parentModules.find(
166
+ (m) => m.name.toLowerCase() === name.toLowerCase()
167
+ );
168
+ if (found) return found;
169
+ }
170
+ }
171
+ get enabledCount() {
172
+ return Object.values(this._modules).flat().filter((m) => m.enabled).length;
173
+ }
174
+ async sendUIToChannel(channelID) {
175
+ const channel = this.client?.channels.cache.get(channelID) || await this.client?.channels.fetch(channelID);
176
+ if (!channel) {
177
+ throw new Error(`Channel (${channelID}) does not exist or is unavailable`);
178
+ }
179
+ if (channel.isTextBased() && channel.isSendable()) {
180
+ _ModuleManager.message = await channel.send({
181
+ components: [this.createManagerUI()],
182
+ flags: MessageFlags2.IsComponentsV2
183
+ });
184
+ return;
185
+ }
186
+ throw new Error(`Channel (${channelID}) does not exist or is not a valid sendable channel`);
187
+ }
188
+ /**
189
+ * Global update for the main message
190
+ */
191
+ async updateMainUI() {
192
+ const m = {
193
+ components: [this.createManagerUI()]
194
+ };
195
+ await _ModuleManager.message?.edit(m);
196
+ }
197
+ /**
198
+ * This update the MultiModule component when a single module is updated
199
+ * @param interaction
200
+ * @param module
201
+ */
202
+ updateMultiModuleUI(interaction, module) {
203
+ const manager = _ModuleManager.getInstance();
204
+ if (manager) {
205
+ if (module.parent == "root") {
206
+ this.updateMainUI();
207
+ }
208
+ const parentMod = manager.getModule(module.parent);
209
+ if (!parentMod || !(parentMod instanceof MultiModule)) {
210
+ if (!(module instanceof MultiModule)) {
211
+ interaction.deferUpdate();
212
+ }
213
+ return;
214
+ }
215
+ parentMod.notifyChange(interaction);
216
+ return;
217
+ }
218
+ console.error("No existing manager");
219
+ }
220
+ };
221
+ _ModuleManager.message = null;
222
+ var ModuleManager = _ModuleManager;
223
+
224
+ // src/code/MultiModule.ts
225
+ import {
226
+ ContainerBuilder as ContainerBuilder3,
227
+ MessageFlags as MessageFlags3,
228
+ SeparatorBuilder as SeparatorBuilder2,
229
+ SeparatorSpacingSize as SeparatorSpacingSize2
230
+ } from "discord.js";
231
+ var MultiModule = class extends Module {
232
+ constructor() {
233
+ super();
234
+ const inst = ModuleManager.getInstance();
235
+ if (inst) {
236
+ this.manager = inst;
237
+ } else {
238
+ throw new Error("Module Manager Instance is null");
239
+ }
240
+ }
241
+ get events() {
242
+ return [];
243
+ }
244
+ createSubmoduleUI() {
245
+ const container = new ContainerBuilder3();
246
+ container.addSectionComponents(this.createModuleUI("all"));
247
+ container.addSeparatorComponents(new SeparatorBuilder2().setSpacing(SeparatorSpacingSize2.Large).setDivider(true));
248
+ for (const module of this.subModules) {
249
+ container.addSectionComponents(module.createModuleUI());
250
+ }
251
+ return container;
252
+ }
253
+ showModule() {
254
+ return {
255
+ components: [this.createSubmoduleUI()],
256
+ flags: [MessageFlags3.IsComponentsV2, MessageFlags3.Ephemeral]
257
+ };
258
+ }
259
+ enable(interaction) {
260
+ super.enable();
261
+ this.notifyChange(interaction);
262
+ }
263
+ async enableAll(interaction) {
264
+ this.subModules.forEach((module) => {
265
+ module.enable();
266
+ });
267
+ this.enable(interaction);
268
+ }
269
+ disable(interaction) {
270
+ super.disable();
271
+ this.notifyChange(interaction);
272
+ }
273
+ async disableAll(interaction) {
274
+ this.subModules.forEach((module) => {
275
+ module.disable();
276
+ });
277
+ this.disable(interaction);
278
+ }
279
+ notifyChange(interaction) {
280
+ if (!interaction) return;
281
+ interaction.update({
282
+ components: [this.createSubmoduleUI()]
283
+ });
284
+ }
285
+ isAnyEnabled() {
286
+ return Object.values(this.manager.modules).flat().some((m) => m.enabled);
287
+ }
288
+ };
289
+ export {
290
+ Module,
291
+ ModuleManager,
292
+ MultiModule
293
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@spatulox/discord-module",
3
+ "version": "0.1.0",
4
+ "description": "Automatic Module Manager for Discordjs",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "type-check": "tsc --noEmit",
8
+ "build": "npm run type-check && rm -rf ./dist/ && tsup",
9
+ "dev": "npm run type-check && tsx watch src/test/index.ts",
10
+ "patch": "npm run build && npm version patch",
11
+ "minor": "npm run build && npm version minor",
12
+ "major": "npm run build && npm version major",
13
+ "pub:patch": "npm run patch && npm publish --access public",
14
+ "pub:minor": "npm run minor && npm publish --access public",
15
+ "pub:major": "npm run major && npm publish --access public"
16
+ },
17
+ "dependencies": {
18
+ "discord.js": "^14.25.1"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^25.3.0",
22
+ "dotenv": "^17.3.1",
23
+ "tsup": "^8.5.1",
24
+ "tsx": "^4.21.0",
25
+ "typescript": "^5.5.3"
26
+ },
27
+ "keywords": [
28
+ "discordjs",
29
+ "discord",
30
+ "framwork",
31
+ "modules"
32
+ ],
33
+ "author": "Spatulox",
34
+ "license": "MIT"
35
+ }