chrxmaticc-framework 1.3.0 → 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.
@@ -52,7 +52,6 @@ class ChrxCommandBuilder {
52
52
  });
53
53
  }
54
54
 
55
- // ── Critical: expose data and execute so CommandLoader + deploy-commands picks it up ──
56
55
  this.data = builder;
57
56
  this.execute = this._execute.bind(this);
58
57
  }
@@ -61,20 +60,24 @@ class ChrxCommandBuilder {
61
60
  const client = interaction.client;
62
61
  const opts = this._options;
63
62
 
63
+ // ── Guild only ────────────────────────────────────────────────────────
64
64
  if (opts.guildOnly !== false && !interaction.guild) {
65
65
  return interaction.reply({ content: "❌ This command can only be used in a server.", ephemeral: true });
66
66
  }
67
67
 
68
+ // ── Owner only ────────────────────────────────────────────────────────
68
69
  if (opts.ownerOnly && interaction.user.id !== process.env.OWNER_ID) {
69
70
  return interaction.reply({ content: "❌ This command is restricted to the bot owner.", ephemeral: true });
70
71
  }
71
72
 
73
+ // ── Permission check ──────────────────────────────────────────────────
72
74
  if (opts.permission) {
73
75
  if (!interaction.member?.permissions.has(PermissionFlagsBits[opts.permission])) {
74
76
  return interaction.reply({ content: `❌ You need the **${opts.permission}** permission to use this.`, ephemeral: true });
75
77
  }
76
78
  }
77
79
 
80
+ // ── Cooldown ──────────────────────────────────────────────────────────
78
81
  if (opts.cooldown) {
79
82
  const key = `${interaction.user.id}-${opts.name}`;
80
83
  const now = Date.now();
@@ -91,6 +94,18 @@ class ChrxCommandBuilder {
91
94
  setTimeout(() => this._cooldowns.delete(key), opts.cooldown * 1000);
92
95
  }
93
96
 
97
+ // ── TimedReply — auto defer if timedReply option is set ───────────────
98
+ if (opts.timedReply) {
99
+ const minutes = opts.timedReply.minutes ?? 1;
100
+ const ms = Math.min(minutes * 60 * 1000, 15 * 60 * 1000); // Discord hard cap is 15min
101
+ if (ms > 2500) {
102
+ // Only defer if we expect the command to take longer than ~2.5s
103
+ await interaction.deferReply({ ephemeral: opts.timedReply.ephemeral ?? false });
104
+ }
105
+ console.log(`[ChrxCommand] /${opts.name} timedReply active — ${minutes}min window`);
106
+ }
107
+
108
+ // ── Plugin injection ──────────────────────────────────────────────────
94
109
  const plugins = {
95
110
  economy: client.chrx?.economy,
96
111
  moderation: client.chrx?.moderation,
@@ -106,6 +121,7 @@ class ChrxCommandBuilder {
106
121
  db: client.db,
107
122
  };
108
123
 
124
+ // ── Run ───────────────────────────────────────────────────────────────
109
125
  try {
110
126
  await opts.run(interaction, plugins);
111
127
  } catch (err) {
@@ -0,0 +1,236 @@
1
+ /**
2
+ * core/ChrxReply.js
3
+ * Unified reply system — every Discord reply type simplified into clean triggers.
4
+ *
5
+ * Includes:
6
+ * - ChrxReply → interaction.reply()
7
+ * - ChrxDeferReply → interaction.deferReply()
8
+ * - ChrxEditReply → interaction.editReply()
9
+ * - ChrxFollowUp → interaction.followUp()
10
+ * - ChrxFetchReply → interaction.fetchReply()
11
+ * - ChrxSilentReply → ephemeral reply shortcut
12
+ * - ChrxAutoReply → smart reply (detects state automatically)
13
+ * - ChrxThinkReply → defer + slow handler + editReply
14
+ */
15
+
16
+ // ── Helpers ───────────────────────────────────────────────────────────────
17
+
18
+ /**
19
+ * Normalize input into a Discord reply payload.
20
+ * Accepts: string, EmbedBuilder, or plain payload object.
21
+ */
22
+ function normalize(input, extra = {}) {
23
+ if (!input) return extra;
24
+
25
+ if (typeof input === "string") {
26
+ return { content: input, ...extra };
27
+ }
28
+
29
+ // EmbedBuilder instance
30
+ if (input?.data || input?.setTitle) {
31
+ return { embeds: [input], ...extra };
32
+ }
33
+
34
+ // Array of embeds
35
+ if (Array.isArray(input)) {
36
+ return { embeds: input, ...extra };
37
+ }
38
+
39
+ // Plain object payload — merge with extra
40
+ return { ...input, ...extra };
41
+ }
42
+
43
+ // ═════════════════════════════════════════════════════════════════════════════
44
+ // ChrxReply — normal reply
45
+ // ═════════════════════════════════════════════════════════════════════════════
46
+
47
+ /**
48
+ * @param {import("discord.js").CommandInteraction} interaction
49
+ * @param {string|object|import("discord.js").EmbedBuilder} content
50
+ * @param {object} [options]
51
+ * @param {boolean} [options.ephemeral]
52
+ * @param {object[]} [options.components]
53
+ * @param {object[]} [options.files]
54
+ */
55
+ async function ChrxReply(interaction, content, options = {}) {
56
+ try {
57
+ return await interaction.reply(normalize(content, options));
58
+ } catch (err) {
59
+ console.error("[ChrxReply] Error:", err.message);
60
+ }
61
+ }
62
+
63
+ // ═════════════════════════════════════════════════════════════════════════════
64
+ // ChrxDeferReply — defer the interaction
65
+ // ═════════════════════════════════════════════════════════════════════════════
66
+
67
+ /**
68
+ * @param {import("discord.js").CommandInteraction} interaction
69
+ * @param {object} [options]
70
+ * @param {boolean} [options.ephemeral]
71
+ */
72
+ async function ChrxDeferReply(interaction, options = {}) {
73
+ try {
74
+ if (interaction.deferred || interaction.replied) return;
75
+ return await interaction.deferReply({ ephemeral: options.ephemeral ?? false });
76
+ } catch (err) {
77
+ console.error("[ChrxDeferReply] Error:", err.message);
78
+ }
79
+ }
80
+
81
+ // ═════════════════════════════════════════════════════════════════════════════
82
+ // ChrxEditReply — edit an existing reply
83
+ // ═════════════════════════════════════════════════════════════════════════════
84
+
85
+ /**
86
+ * @param {import("discord.js").CommandInteraction} interaction
87
+ * @param {string|object|import("discord.js").EmbedBuilder} content
88
+ * @param {object} [options]
89
+ */
90
+ async function ChrxEditReply(interaction, content, options = {}) {
91
+ try {
92
+ // Auto defer if not already deferred or replied
93
+ if (!interaction.deferred && !interaction.replied) {
94
+ await interaction.deferReply({ ephemeral: options.ephemeral ?? false });
95
+ }
96
+ return await interaction.editReply(normalize(content, options));
97
+ } catch (err) {
98
+ console.error("[ChrxEditReply] Error:", err.message);
99
+ }
100
+ }
101
+
102
+ // ═════════════════════════════════════════════════════════════════════════════
103
+ // ChrxFollowUp — follow up message
104
+ // ═════════════════════════════════════════════════════════════════════════════
105
+
106
+ /**
107
+ * @param {import("discord.js").CommandInteraction} interaction
108
+ * @param {string|object|import("discord.js").EmbedBuilder} content
109
+ * @param {object} [options]
110
+ * @param {boolean} [options.ephemeral]
111
+ */
112
+ async function ChrxFollowUp(interaction, content, options = {}) {
113
+ try {
114
+ return await interaction.followUp(normalize(content, options));
115
+ } catch (err) {
116
+ console.error("[ChrxFollowUp] Error:", err.message);
117
+ }
118
+ }
119
+
120
+ // ═════════════════════════════════════════════════════════════════════════════
121
+ // ChrxFetchReply — fetch the reply message object
122
+ // ═════════════════════════════════════════════════════════════════════════════
123
+
124
+ /**
125
+ * @param {import("discord.js").CommandInteraction} interaction
126
+ * @returns {Promise<import("discord.js").Message>}
127
+ */
128
+ async function ChrxFetchReply(interaction) {
129
+ try {
130
+ return await interaction.fetchReply();
131
+ } catch (err) {
132
+ console.error("[ChrxFetchReply] Error:", err.message);
133
+ return null;
134
+ }
135
+ }
136
+
137
+ // ═════════════════════════════════════════════════════════════════════════════
138
+ // ChrxSilentReply — ephemeral reply shortcut
139
+ // ═════════════════════════════════════════════════════════════════════════════
140
+
141
+ /**
142
+ * Always sends as ephemeral. No options needed.
143
+ * @param {import("discord.js").CommandInteraction} interaction
144
+ * @param {string|object|import("discord.js").EmbedBuilder} content
145
+ */
146
+ async function ChrxSilentReply(interaction, content) {
147
+ try {
148
+ if (interaction.deferred || interaction.replied) {
149
+ return await interaction.followUp({ ...normalize(content), ephemeral: true });
150
+ }
151
+ return await interaction.reply({ ...normalize(content), ephemeral: true });
152
+ } catch (err) {
153
+ console.error("[ChrxSilentReply] Error:", err.message);
154
+ }
155
+ }
156
+
157
+ // ═════════════════════════════════════════════════════════════════════════════
158
+ // ChrxAutoReply — smart reply, detects state automatically
159
+ // Never throws "interaction already replied" — picks the right method every time
160
+ // ═════════════════════════════════════════════════════════════════════════════
161
+
162
+ /**
163
+ * @param {import("discord.js").CommandInteraction} interaction
164
+ * @param {string|object|import("discord.js").EmbedBuilder} content
165
+ * @param {object} [options]
166
+ * @param {boolean} [options.ephemeral]
167
+ */
168
+ async function ChrxAutoReply(interaction, content, options = {}) {
169
+ try {
170
+ const payload = normalize(content, options);
171
+
172
+ if (interaction.replied) {
173
+ return await interaction.followUp(payload);
174
+ }
175
+
176
+ if (interaction.deferred) {
177
+ return await interaction.editReply(payload);
178
+ }
179
+
180
+ return await interaction.reply(payload);
181
+ } catch (err) {
182
+ console.error("[ChrxAutoReply] Error:", err.message);
183
+ }
184
+ }
185
+
186
+ // ═════════════════════════════════════════════════════════════════════════════
187
+ // ChrxThinkReply — defer + slow async handler + auto editReply
188
+ // Perfect for AI commands, database queries, anything slow
189
+ // ═════════════════════════════════════════════════════════════════════════════
190
+
191
+ /**
192
+ * @param {import("discord.js").CommandInteraction} interaction
193
+ * @param {Function} handler Async function that returns the reply content
194
+ * @param {object} [options]
195
+ * @param {boolean} [options.ephemeral]
196
+ * @param {string} [options.errorMessage] Custom error message on failure
197
+ *
198
+ * @example
199
+ * await ChrxThinkReply(interaction, async () => {
200
+ * const result = await someSlowDatabaseCall();
201
+ * return `Result: ${result}`;
202
+ * });
203
+ */
204
+ async function ChrxThinkReply(interaction, handler, options = {}) {
205
+ try {
206
+ if (!interaction.deferred && !interaction.replied) {
207
+ await interaction.deferReply({ ephemeral: options.ephemeral ?? false });
208
+ }
209
+
210
+ const result = await handler();
211
+
212
+ if (!result) return;
213
+
214
+ return await interaction.editReply(normalize(result));
215
+ } catch (err) {
216
+ console.error("[ChrxThinkReply] Error:", err.message);
217
+ await interaction.editReply({
218
+ content: options.errorMessage || err.message || "❌ Something went wrong.",
219
+ }).catch(() => {});
220
+ }
221
+ }
222
+
223
+ // ═════════════════════════════════════════════════════════════════════════════
224
+ // Exports
225
+ // ═════════════════════════════════════════════════════════════════════════════
226
+
227
+ module.exports = {
228
+ ChrxReply,
229
+ ChrxDeferReply,
230
+ ChrxEditReply,
231
+ ChrxFollowUp,
232
+ ChrxFetchReply,
233
+ ChrxSilentReply,
234
+ ChrxAutoReply,
235
+ ChrxThinkReply,
236
+ };
@@ -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
@@ -9,26 +9,22 @@ const XPSystem = require("./core/XPSystem");
9
9
  const AIWrapper = require("./core/AIWrapper");
10
10
  const MusicManager = require("./core/MusicManager");
11
11
  const ChrxCommandBuilder = require("./core/ChrxCommandBuilder");
12
- const { ChrxEmbedBuilder, EmbedTrigger } = require("./core/ChrxEmbedBuilder");
12
+ const ChrxTimedReply = require("./core/ChrxTimedReply");
13
+ const ChrxRouter = require("./core/ChrxRouter");
14
+ const { ChrxEmbedBuilder, EmbedTrigger } = require("./core/ChrxEmbedBuilder");
13
15
  const { ChrxModalBuilder, ModalTrigger, onModalSubmit } = require("./core/ChrxModalBuilder");
14
16
  const {
15
- ChrxButton,
16
- ChrxSelectMenu,
17
- ChrxPagination,
18
- ChrxContextMenu,
19
- ChrxMenu,
20
- ChrxForm,
21
- ChrxConfirm,
17
+ ChrxButton, ChrxSelectMenu, ChrxPagination,
18
+ ChrxContextMenu, ChrxMenu, ChrxForm, ChrxConfirm,
22
19
  } = require("./core/ChrxComponentsV3");
23
20
  const {
24
- parseTime,
25
- formatTime,
26
- setMarker,
27
- getMarkers,
28
- clearMarker,
29
- applyStartMarker,
30
- startEndMarkerWatcher,
31
- stopEndMarkerWatcher,
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,
32
28
  } = require("./core/songMarkers");
33
29
 
34
30
  // ── Plugins ───────────────────────────────────────────────────────────────
@@ -46,6 +42,18 @@ module.exports = {
46
42
  // Core
47
43
  ChrxClient,
48
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,
49
57
 
50
58
  // Embed & Modal
51
59
  ChrxEmbedBuilder,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrxmaticc-framework",
3
- "version": "1.3.0",
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": [