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,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/ChrxEmbedBuilder.js
|
|
3
|
+
* Simple and Advanced embed builder.
|
|
4
|
+
*
|
|
5
|
+
* Simple mode → key/value syntax (title, desc, color)
|
|
6
|
+
* AdvancedEmbed → parses raw JSON or HTML-like string into a full embed
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { EmbedBuilder } = require("discord.js");
|
|
10
|
+
|
|
11
|
+
class ChrxEmbedBuilder {
|
|
12
|
+
/**
|
|
13
|
+
* Simple mode — just pass title, desc, color etc.
|
|
14
|
+
* @param {object} options
|
|
15
|
+
* @param {string} [options.title]
|
|
16
|
+
* @param {string} [options.desc]
|
|
17
|
+
* @param {string} [options.color]
|
|
18
|
+
* @param {string} [options.thumbnail]
|
|
19
|
+
* @param {string} [options.image]
|
|
20
|
+
* @param {string} [options.footer]
|
|
21
|
+
* @param {boolean} [options.timestamp]
|
|
22
|
+
* @param {object[]} [options.fields] [{ name, value, inline? }]
|
|
23
|
+
*/
|
|
24
|
+
static build(options = {}) {
|
|
25
|
+
const embed = new EmbedBuilder();
|
|
26
|
+
|
|
27
|
+
if (options.title) embed.setTitle(options.title);
|
|
28
|
+
if (options.desc) embed.setDescription(options.desc);
|
|
29
|
+
if (options.color) embed.setColor(options.color);
|
|
30
|
+
if (options.thumbnail) embed.setThumbnail(options.thumbnail);
|
|
31
|
+
if (options.image) embed.setImage(options.image);
|
|
32
|
+
if (options.footer) embed.setFooter({ text: options.footer });
|
|
33
|
+
if (options.timestamp) embed.setTimestamp();
|
|
34
|
+
if (options.fields) embed.addFields(options.fields);
|
|
35
|
+
|
|
36
|
+
return embed;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* AdvancedEmbed — reads raw JSON string or HTML-like string and parses it.
|
|
41
|
+
* Supports both:
|
|
42
|
+
* - JSON: { "title": "Hi", "desc": "ok bro", "color": "#5865F2", ... }
|
|
43
|
+
* - HTML-like: <title>Hi</title><desc>ok bro</desc><color>#5865F2</color>
|
|
44
|
+
*
|
|
45
|
+
* @param {string|object} input Raw JSON string, HTML-like string, or plain object
|
|
46
|
+
* @returns {EmbedBuilder}
|
|
47
|
+
*/
|
|
48
|
+
static advanced(input) {
|
|
49
|
+
let data = {};
|
|
50
|
+
|
|
51
|
+
if (typeof input === "object") {
|
|
52
|
+
// Already a plain object
|
|
53
|
+
data = input;
|
|
54
|
+
|
|
55
|
+
} else if (typeof input === "string") {
|
|
56
|
+
// Try JSON first
|
|
57
|
+
try {
|
|
58
|
+
data = JSON.parse(input);
|
|
59
|
+
} catch {
|
|
60
|
+
// Fall back to HTML-like tag parser
|
|
61
|
+
data = ChrxEmbedBuilder._parseHTML(input);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return ChrxEmbedBuilder.build(data);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parse HTML-like tag syntax into a plain object.
|
|
70
|
+
* Supports: <title> <desc> <color> <thumbnail> <image> <footer> <timestamp> <field>
|
|
71
|
+
*
|
|
72
|
+
* Example input:
|
|
73
|
+
* <title>Hello!</title>
|
|
74
|
+
* <desc>This is my embed</desc>
|
|
75
|
+
* <color>#5865F2</color>
|
|
76
|
+
* <field name="Field 1" value="Value 1" inline="true"/>
|
|
77
|
+
*/
|
|
78
|
+
static _parseHTML(html) {
|
|
79
|
+
const data = {};
|
|
80
|
+
|
|
81
|
+
const extract = (tag) => {
|
|
82
|
+
const match = html.match(new RegExp(`<${tag}[^>]*>(.*?)<\\/${tag}>`, "si"));
|
|
83
|
+
return match ? match[1].trim() : null;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
data.title = extract("title") || undefined;
|
|
87
|
+
data.desc = extract("desc") || extract("description") || undefined;
|
|
88
|
+
data.color = extract("color") || undefined;
|
|
89
|
+
data.thumbnail = extract("thumbnail") || undefined;
|
|
90
|
+
data.image = extract("image") || undefined;
|
|
91
|
+
data.footer = extract("footer") || undefined;
|
|
92
|
+
data.timestamp = html.includes("<timestamp") ? true : undefined;
|
|
93
|
+
|
|
94
|
+
// Parse <field name="x" value="y" inline="true"/> tags
|
|
95
|
+
const fieldRegex = /<field\s+name="([^"]+)"\s+value="([^"]+)"(?:\s+inline="(true|false)")?[^/]*\/>/gi;
|
|
96
|
+
const fields = [];
|
|
97
|
+
let fieldMatch;
|
|
98
|
+
while ((fieldMatch = fieldRegex.exec(html)) !== null) {
|
|
99
|
+
fields.push({
|
|
100
|
+
name: fieldMatch[1],
|
|
101
|
+
value: fieldMatch[2],
|
|
102
|
+
inline: fieldMatch[3] === "true",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (fields.length > 0) data.fields = fields;
|
|
106
|
+
|
|
107
|
+
return data;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* EmbedTrigger — fires a ChrxEmbedBuilder embed in a reply/send.
|
|
113
|
+
* Drop this anywhere in a ChrxCommandBuilder run() function.
|
|
114
|
+
*
|
|
115
|
+
* @param {import("discord.js").CommandInteraction} interaction
|
|
116
|
+
* @param {EmbedBuilder} embed
|
|
117
|
+
* @param {object} [options]
|
|
118
|
+
* @param {boolean} [options.ephemeral]
|
|
119
|
+
*/
|
|
120
|
+
async function EmbedTrigger(interaction, embed, options = {}) {
|
|
121
|
+
if (interaction.replied || interaction.deferred) {
|
|
122
|
+
return interaction.followUp({ embeds: [embed], ephemeral: options.ephemeral ?? false });
|
|
123
|
+
}
|
|
124
|
+
return interaction.reply({ embeds: [embed], ephemeral: options.ephemeral ?? false });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = { ChrxEmbedBuilder, EmbedTrigger };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/ChrxModalBuilder.js
|
|
3
|
+
* Simple modal builder with built in validation and rate limiting.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
ModalBuilder,
|
|
8
|
+
TextInputBuilder,
|
|
9
|
+
TextInputStyle,
|
|
10
|
+
ActionRowBuilder,
|
|
11
|
+
} = require("discord.js");
|
|
12
|
+
|
|
13
|
+
// ── Rate limiter ──────────────────────────────────────────────────────────
|
|
14
|
+
// Tracks how many modals a user has triggered in the last minute
|
|
15
|
+
const modalRateLimit = new Map(); // userId -> { count, resetAt }
|
|
16
|
+
const RATE_LIMIT = 50; // max modals per minute per user
|
|
17
|
+
|
|
18
|
+
function checkRateLimit(userId) {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const data = modalRateLimit.get(userId);
|
|
21
|
+
|
|
22
|
+
if (!data || now > data.resetAt) {
|
|
23
|
+
modalRateLimit.set(userId, { count: 1, resetAt: now + 60000 });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (data.count >= RATE_LIMIT) return false;
|
|
28
|
+
|
|
29
|
+
data.count++;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Type map ──────────────────────────────────────────────────────────────
|
|
34
|
+
const STYLE_MAP = {
|
|
35
|
+
short: TextInputStyle.Short,
|
|
36
|
+
paragraph: TextInputStyle.Paragraph,
|
|
37
|
+
long: TextInputStyle.Paragraph, // alias
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
class ChrxModalBuilder {
|
|
41
|
+
/**
|
|
42
|
+
* @param {object} options
|
|
43
|
+
* @param {string} options.name Internal name (for reference)
|
|
44
|
+
* @param {string} options.id Modal custom ID
|
|
45
|
+
* @param {string} options.title Modal title (max 45 chars)
|
|
46
|
+
* @param {object[]} options.inputs Input fields (max 5)
|
|
47
|
+
* @param {string} options.inputs[].id Input custom ID
|
|
48
|
+
* @param {string} options.inputs[].label Input label (max 100 chars)
|
|
49
|
+
* @param {string} [options.inputs[].type] "short" | "paragraph" (default: short)
|
|
50
|
+
* @param {boolean} [options.inputs[].required] Required? (default: false)
|
|
51
|
+
* @param {string} [options.inputs[].placeholder] Placeholder text
|
|
52
|
+
* @param {string} [options.inputs[].value] Pre-filled value
|
|
53
|
+
* @param {number} [options.inputs[].minLength]
|
|
54
|
+
* @param {number} [options.inputs[].maxLength]
|
|
55
|
+
*/
|
|
56
|
+
constructor(options = {}) {
|
|
57
|
+
if (!options.id) throw new Error("[ChrxModal] id is required.");
|
|
58
|
+
if (!options.title) throw new Error("[ChrxModal] title is required.");
|
|
59
|
+
if (!options.inputs || options.inputs.length === 0) throw new Error("[ChrxModal] At least one input is required.");
|
|
60
|
+
|
|
61
|
+
// ── Validation ────────────────────────────────────────────────────────
|
|
62
|
+
if (options.title.length > 45) {
|
|
63
|
+
throw new Error(`[ChrxModal] Title exceeds 45 characters (got ${options.title.length}).`);
|
|
64
|
+
}
|
|
65
|
+
if (options.inputs.length > 5) {
|
|
66
|
+
throw new Error(`[ChrxModal] Maximum 5 inputs allowed (got ${options.inputs.length}).`);
|
|
67
|
+
}
|
|
68
|
+
for (const input of options.inputs) {
|
|
69
|
+
if (!input.id) throw new Error("[ChrxModal] Every input must have an id.");
|
|
70
|
+
if (!input.label) throw new Error("[ChrxModal] Every input must have a label.");
|
|
71
|
+
if (input.label.length > 100) {
|
|
72
|
+
throw new Error(`[ChrxModal] Input label "${input.label}" exceeds 100 characters.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this._options = options;
|
|
77
|
+
this.name = options.name ?? options.id;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build and return the raw discord.js Modal.
|
|
82
|
+
* @returns {ModalBuilder}
|
|
83
|
+
*/
|
|
84
|
+
build() {
|
|
85
|
+
const modal = new ModalBuilder()
|
|
86
|
+
.setCustomId(this._options.id)
|
|
87
|
+
.setTitle(this._options.title);
|
|
88
|
+
|
|
89
|
+
const rows = this._options.inputs.map((input) => {
|
|
90
|
+
const textInput = new TextInputBuilder()
|
|
91
|
+
.setCustomId(input.id)
|
|
92
|
+
.setLabel(input.label)
|
|
93
|
+
.setStyle(STYLE_MAP[input.type ?? "short"] ?? TextInputStyle.Short);
|
|
94
|
+
|
|
95
|
+
if (input.required) textInput.setRequired(input.required);
|
|
96
|
+
if (input.placeholder) textInput.setPlaceholder(input.placeholder);
|
|
97
|
+
if (input.value) textInput.setValue(input.value);
|
|
98
|
+
if (input.minLength) textInput.setMinLength(input.minLength);
|
|
99
|
+
if (input.maxLength) textInput.setMaxLength(input.maxLength);
|
|
100
|
+
|
|
101
|
+
return new ActionRowBuilder().addComponents(textInput);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
modal.addComponents(...rows);
|
|
105
|
+
return modal;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* ModalTrigger — shows a ChrxModalBuilder modal to the user.
|
|
111
|
+
* Handles rate limiting automatically.
|
|
112
|
+
*
|
|
113
|
+
* @param {import("discord.js").CommandInteraction} interaction
|
|
114
|
+
* @param {ChrxModalBuilder} modal
|
|
115
|
+
*/
|
|
116
|
+
async function ModalTrigger(interaction, modal) {
|
|
117
|
+
if (!checkRateLimit(interaction.user.id)) {
|
|
118
|
+
return interaction.reply({
|
|
119
|
+
content: "⏱ You're triggering too many modals! Slow down.",
|
|
120
|
+
ephemeral: true,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await interaction.showModal(modal.build());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* onModalSubmit — listen for a modal submission by ID.
|
|
129
|
+
* @param {import("discord.js").Client} client
|
|
130
|
+
* @param {string} modalId
|
|
131
|
+
* @param {Function} handler (interaction) => void
|
|
132
|
+
*/
|
|
133
|
+
function onModalSubmit(client, modalId, handler) {
|
|
134
|
+
client.on("interactionCreate", async (interaction) => {
|
|
135
|
+
if (!interaction.isModalSubmit()) return;
|
|
136
|
+
if (interaction.customId !== modalId) return;
|
|
137
|
+
try {
|
|
138
|
+
await handler(interaction);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error(`[ChrxModal] Error in modal "${modalId}":`, err);
|
|
141
|
+
const msg = { content: err.message || "❌ Something went wrong.", ephemeral: true };
|
|
142
|
+
if (interaction.replied || interaction.deferred) {
|
|
143
|
+
interaction.followUp(msg).catch(() => {});
|
|
144
|
+
} else {
|
|
145
|
+
interaction.reply(msg).catch(() => {});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = { ChrxModalBuilder, ModalTrigger, onModalSubmit };
|
|
@@ -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
|
+
};
|