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.
- package/core/ChrxCommandBuilder.js +17 -1
- package/core/ChrxComponentsV3.js +652 -0
- package/core/ChrxEmbedBuilder.js +127 -0
- package/core/ChrxModalBuilder.js +151 -0
- package/core/ChrxReply.js +236 -0
- package/core/ChrxRouter.js +233 -0
- package/core/ChrxTimedReply.js +50 -0
- package/index.js +51 -17
- package/package.json +1 -1
|
@@ -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
|
|
3
|
-
* Main entry point
|
|
4
|
-
* Listen to Project4play / SVJ at SoundCloud NOW!
|
|
2
|
+
* index.js
|
|
3
|
+
* Main entry point — exports everything.
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
|
-
const { ChrxClient }
|
|
8
|
-
const Database
|
|
9
|
-
const XPSystem
|
|
10
|
-
const AIWrapper
|
|
11
|
-
const MusicManager
|
|
12
|
-
const 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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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,
|