chrxmaticc-framework 1.2.1 → 1.4.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.
@@ -0,0 +1,233 @@
1
+ /**
2
+ * core/ChrxRouter.js
3
+ * Global interaction router — register handlers once, auto-routes everything.
4
+ *
5
+ * Supports:
6
+ * - Buttons
7
+ * - Select menus
8
+ * - Modal submits
9
+ * - Context menus
10
+ *
11
+ * Usage:
12
+ * ChrxRouter.init(client);
13
+ * ChrxRouter.button("ban_confirm", async (i) => i.reply("✅ Banned!"));
14
+ * ChrxRouter.select("role_select", async (i, values) => i.reply(`Picked: ${values[0]}`));
15
+ * ChrxRouter.modal("feedback_modal", async (i, values) => i.reply(`Got: ${values.feedback}`));
16
+ * ChrxRouter.context("Get Avatar", async (i) => i.reply(i.targetUser.displayAvatarURL()));
17
+ */
18
+
19
+ const { ComponentType } = require("discord.js");
20
+
21
+ // ── Route registries ──────────────────────────────────────────────────────
22
+ const buttonRoutes = new Map(); // customId -> handler
23
+ const selectRoutes = new Map(); // customId -> handler
24
+ const modalRoutes = new Map(); // customId -> handler
25
+ const contextRoutes = new Map(); // name -> handler
26
+
27
+ // ── Middleware ────────────────────────────────────────────────────────────
28
+ const middlewares = [];
29
+
30
+ // ── Cooldowns ─────────────────────────────────────────────────────────────
31
+ const cooldowns = new Map(); // `${userId}-${id}` -> timestamp
32
+
33
+ let _initialized = false;
34
+
35
+ const ChrxRouter = {
36
+
37
+ /**
38
+ * Initialize the router — call this once after bot is ready.
39
+ * @param {import("discord.js").Client} client
40
+ */
41
+ init(client) {
42
+ if (_initialized) return;
43
+ _initialized = true;
44
+
45
+ client.on("interactionCreate", async (interaction) => {
46
+ try {
47
+ // ── Run middleware ────────────────────────────────────────────────
48
+ for (const mw of middlewares) {
49
+ const pass = await mw(interaction);
50
+ if (pass === false) return; // middleware blocked it
51
+ }
52
+
53
+ // ── Button ────────────────────────────────────────────────────────
54
+ if (interaction.isButton()) {
55
+ const handler = ChrxRouter._resolve(buttonRoutes, interaction.customId);
56
+ if (handler) await handler(interaction);
57
+ return;
58
+ }
59
+
60
+ // ── Select menu ───────────────────────────────────────────────────
61
+ if (interaction.isStringSelectMenu()) {
62
+ const handler = ChrxRouter._resolve(selectRoutes, interaction.customId);
63
+ if (handler) await handler(interaction, interaction.values);
64
+ return;
65
+ }
66
+
67
+ // ── Modal submit ──────────────────────────────────────────────────
68
+ if (interaction.isModalSubmit()) {
69
+ const handler = ChrxRouter._resolve(modalRoutes, interaction.customId);
70
+ if (handler) {
71
+ // Build values object keyed by field customId
72
+ const values = {};
73
+ for (const [key, field] of interaction.fields.fields) {
74
+ values[key] = field.value;
75
+ }
76
+ await handler(interaction, values);
77
+ }
78
+ return;
79
+ }
80
+
81
+ // ── Context menu ──────────────────────────────────────────────────
82
+ if (interaction.isContextMenuCommand()) {
83
+ const handler = contextRoutes.get(interaction.commandName);
84
+ if (handler) await handler(interaction);
85
+ return;
86
+ }
87
+
88
+ } catch (err) {
89
+ console.error("[ChrxRouter] Unhandled error:", err.message);
90
+ const msg = { content: err.message || "❌ Something went wrong.", ephemeral: true };
91
+ try {
92
+ if (interaction.replied || interaction.deferred) {
93
+ await interaction.followUp(msg);
94
+ } else {
95
+ await interaction.reply(msg);
96
+ }
97
+ } catch {}
98
+ }
99
+ });
100
+
101
+ console.log("[ChrxRouter] Initialized — routing all interactions globally.");
102
+ },
103
+
104
+ // ── Internal resolver — supports wildcards ────────────────────────────
105
+ // e.g. "ban_confirm_*" matches "ban_confirm_123456"
106
+ _resolve(registry, id) {
107
+ // Exact match first
108
+ if (registry.has(id)) return registry.get(id);
109
+
110
+ // Wildcard match
111
+ for (const [pattern, handler] of registry) {
112
+ if (pattern.endsWith("*") && id.startsWith(pattern.slice(0, -1))) {
113
+ return handler;
114
+ }
115
+ }
116
+
117
+ return null;
118
+ },
119
+
120
+ // ═══════════════════════════════════════════════════════════════════════
121
+ // Registration methods
122
+ // ═══════════════════════════════════════════════════════════════════════
123
+
124
+ /**
125
+ * Register a button handler.
126
+ * @param {string} id Button customId (supports wildcard e.g. "confirm_*")
127
+ * @param {Function} handler async (interaction) => void
128
+ * @param {object} [options]
129
+ * @param {number} [options.cooldown] Cooldown in seconds per user
130
+ * @param {boolean} [options.ownerOnly] Restrict to OWNER_ID
131
+ */
132
+ button(id, handler, options = {}) {
133
+ buttonRoutes.set(id, ChrxRouter._wrap(handler, id, options));
134
+ return ChrxRouter; // chainable
135
+ },
136
+
137
+ /**
138
+ * Register a select menu handler.
139
+ * @param {string} id Select menu customId
140
+ * @param {Function} handler async (interaction, values) => void
141
+ * @param {object} [options]
142
+ */
143
+ select(id, handler, options = {}) {
144
+ selectRoutes.set(id, ChrxRouter._wrap(handler, id, options));
145
+ return ChrxRouter;
146
+ },
147
+
148
+ /**
149
+ * Register a modal submit handler.
150
+ * @param {string} id Modal customId
151
+ * @param {Function} handler async (interaction, values) => void
152
+ * values = { inputId: "user typed value", ... }
153
+ * @param {object} [options]
154
+ */
155
+ modal(id, handler, options = {}) {
156
+ modalRoutes.set(id, ChrxRouter._wrap(handler, id, options));
157
+ return ChrxRouter;
158
+ },
159
+
160
+ /**
161
+ * Register a context menu handler.
162
+ * @param {string} name Context menu command name
163
+ * @param {Function} handler async (interaction) => void
164
+ */
165
+ context(name, handler) {
166
+ contextRoutes.set(name, handler);
167
+ return ChrxRouter;
168
+ },
169
+
170
+ /**
171
+ * Remove a registered route.
172
+ * @param {"button"|"select"|"modal"|"context"} type
173
+ * @param {string} id
174
+ */
175
+ remove(type, id) {
176
+ const map = { button: buttonRoutes, select: selectRoutes, modal: modalRoutes, context: contextRoutes }[type];
177
+ if (map) map.delete(id);
178
+ return ChrxRouter;
179
+ },
180
+
181
+ /**
182
+ * Add global middleware — runs before every routed interaction.
183
+ * Return false to block the interaction.
184
+ * @param {Function} fn async (interaction) => boolean | void
185
+ */
186
+ use(fn) {
187
+ middlewares.push(fn);
188
+ return ChrxRouter;
189
+ },
190
+
191
+ /**
192
+ * List all registered routes (useful for debugging).
193
+ */
194
+ list() {
195
+ return {
196
+ buttons: [...buttonRoutes.keys()],
197
+ selects: [...selectRoutes.keys()],
198
+ modals: [...modalRoutes.keys()],
199
+ contexts: [...contextRoutes.keys()],
200
+ };
201
+ },
202
+
203
+ // ── Internal: wrap handler with cooldown + ownerOnly ──────────────────
204
+ _wrap(handler, id, options = {}) {
205
+ return async (interaction, ...args) => {
206
+ // Owner only
207
+ if (options.ownerOnly && interaction.user.id !== process.env.OWNER_ID) {
208
+ return interaction.reply({ content: "❌ This is restricted to the bot owner.", ephemeral: true });
209
+ }
210
+
211
+ // Cooldown
212
+ if (options.cooldown) {
213
+ const key = `${interaction.user.id}-${id}`;
214
+ const now = Date.now();
215
+ if (cooldowns.has(key)) {
216
+ const remaining = (options.cooldown * 1000) - (now - cooldowns.get(key));
217
+ if (remaining > 0) {
218
+ return interaction.reply({
219
+ content: `⏱ Wait **${(remaining / 1000).toFixed(1)}s** before doing that again.`,
220
+ ephemeral: true,
221
+ });
222
+ }
223
+ }
224
+ cooldowns.set(key, now);
225
+ setTimeout(() => cooldowns.delete(key), options.cooldown * 1000);
226
+ }
227
+
228
+ await handler(interaction, ...args);
229
+ };
230
+ },
231
+ };
232
+
233
+ module.exports = ChrxRouter;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * core/ChrxTimedReply.js
3
+ * Extended deferReply — keeps the interaction alive for a custom duration
4
+ * and handles the reply automatically when your function resolves.
5
+ *
6
+ * Usage:
7
+ * await ChrxTimedReply(interaction, async () => "Pong!");
8
+ * await ChrxTimedReply(interaction, async () => ({ content: "Pong!", ephemeral: true }));
9
+ * await ChrxTimedReply(interaction, async () => ({ embeds: [myEmbed] }));
10
+ */
11
+
12
+ /**
13
+ * @param {import("discord.js").CommandInteraction} interaction
14
+ * @param {Function} handler Async function that returns a string, object, or EmbedBuilder
15
+ * @param {object} [options]
16
+ * @param {boolean} [options.ephemeral] Defer as ephemeral (default: false)
17
+ */
18
+ async function ChrxTimedReply(interaction, handler, options = {}) {
19
+ // Defer immediately to keep the interaction token alive
20
+ await interaction.deferReply({ ephemeral: options.ephemeral ?? false });
21
+
22
+ try {
23
+ const result = await handler();
24
+
25
+ if (!result) return;
26
+
27
+ // ── Normalize result into a payload ───────────────────────────────────
28
+ let payload = {};
29
+
30
+ if (typeof result === "string") {
31
+ payload.content = result;
32
+ } else if (result?.setTitle || result?.data?.title) {
33
+ // EmbedBuilder instance
34
+ payload.embeds = [result];
35
+ } else {
36
+ // Plain object — pass through as-is
37
+ payload = result;
38
+ }
39
+
40
+ await interaction.editReply(payload);
41
+
42
+ } catch (err) {
43
+ console.error("[ChrxTimedReply] Handler error:", err);
44
+ await interaction.editReply({
45
+ content: err.message || "❌ Something went wrong.",
46
+ }).catch(() => {});
47
+ }
48
+ }
49
+
50
+ module.exports = ChrxTimedReply;
package/index.js CHANGED
@@ -1,24 +1,30 @@
1
1
  /**
2
- * index.js (not for a discord bot)
3
- * Main entry point, will ALWAYS export everything.
4
- * Listen to Project4play / SVJ at SoundCloud NOW!
2
+ * index.js
3
+ * Main entry point exports everything.
5
4
  */
6
5
 
7
- const { ChrxClient } = require("./core/Client");
8
- const Database = require("./core/Database");
9
- const XPSystem = require("./core/XPSystem");
10
- const AIWrapper = require("./core/AIWrapper");
11
- const MusicManager = require("./core/MusicManager");
12
- const ChrxCommandBuilder = require("./core/ChrxCommandBuilder");
6
+ const { ChrxClient } = require("./core/Client");
7
+ const Database = require("./core/Database");
8
+ const XPSystem = require("./core/XPSystem");
9
+ const AIWrapper = require("./core/AIWrapper");
10
+ const MusicManager = require("./core/MusicManager");
11
+ const ChrxCommandBuilder = require("./core/ChrxCommandBuilder");
12
+ const ChrxTimedReply = require("./core/ChrxTimedReply");
13
+ const ChrxRouter = require("./core/ChrxRouter");
14
+ const { ChrxEmbedBuilder, EmbedTrigger } = require("./core/ChrxEmbedBuilder");
15
+ const { ChrxModalBuilder, ModalTrigger, onModalSubmit } = require("./core/ChrxModalBuilder");
13
16
  const {
14
- parseTime,
15
- formatTime,
16
- setMarker,
17
- getMarkers,
18
- clearMarker,
19
- applyStartMarker,
20
- startEndMarkerWatcher,
21
- stopEndMarkerWatcher,
17
+ ChrxButton, ChrxSelectMenu, ChrxPagination,
18
+ ChrxContextMenu, ChrxMenu, ChrxForm, ChrxConfirm,
19
+ } = require("./core/ChrxComponentsV3");
20
+ const {
21
+ ChrxReply, ChrxDeferReply, ChrxEditReply,
22
+ ChrxFollowUp, ChrxFetchReply, ChrxSilentReply,
23
+ ChrxAutoReply, ChrxThinkReply,
24
+ } = require("./core/ChrxReply");
25
+ const {
26
+ parseTime, formatTime, setMarker, getMarkers,
27
+ clearMarker, applyStartMarker, startEndMarkerWatcher, stopEndMarkerWatcher,
22
28
  } = require("./core/songMarkers");
23
29
 
24
30
  // ── Plugins ───────────────────────────────────────────────────────────────
@@ -36,6 +42,34 @@ module.exports = {
36
42
  // Core
37
43
  ChrxClient,
38
44
  ChrxCommandBuilder,
45
+ ChrxRouter,
46
+
47
+ // Reply system
48
+ ChrxReply,
49
+ ChrxDeferReply,
50
+ ChrxEditReply,
51
+ ChrxFollowUp,
52
+ ChrxFetchReply,
53
+ ChrxSilentReply,
54
+ ChrxAutoReply,
55
+ ChrxThinkReply,
56
+ ChrxTimedReply,
57
+
58
+ // Embed & Modal
59
+ ChrxEmbedBuilder,
60
+ EmbedTrigger,
61
+ ChrxModalBuilder,
62
+ ModalTrigger,
63
+ onModalSubmit,
64
+
65
+ // Components V3
66
+ ChrxButton,
67
+ ChrxSelectMenu,
68
+ ChrxPagination,
69
+ ChrxContextMenu,
70
+ ChrxMenu,
71
+ ChrxForm,
72
+ ChrxConfirm,
39
73
 
40
74
  // Modules
41
75
  Database,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrxmaticc-framework",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "A batteries-included Discord bot framework with music, AI, XP and database support.",
5
5
  "main": "index.js",
6
6
  "files": [