@fluxerjs/core 1.0.9 → 1.1.1

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.
Files changed (36) hide show
  1. package/LICENSE +203 -0
  2. package/dist/{Channel-ICWNKXBR.mjs → Channel-4WVFDOCG.mjs} +4 -1
  3. package/dist/{ClientUser-WWXUMO5O.mjs → ClientUser-PXAAKR2P.mjs} +1 -1
  4. package/dist/Guild-FMBCTAV4.mjs +12 -0
  5. package/dist/{GuildBan-M4PA3HAA.mjs → GuildBan-7CXLTPKY.mjs} +1 -1
  6. package/dist/GuildMember-43B5E5CH.mjs +9 -0
  7. package/dist/Message-OFIVTTAZ.mjs +9 -0
  8. package/dist/{MessageReaction-XRPYZDSC.mjs → MessageReaction-AYSOCOMX.mjs} +2 -1
  9. package/dist/{Role-SVLWIAMN.mjs → Role-5MWSGL66.mjs} +1 -1
  10. package/dist/Webhook-RWDDYW2Q.mjs +10 -0
  11. package/dist/{chunk-GCIJYVRC.mjs → chunk-4F765HVV.mjs} +54 -3
  12. package/dist/chunk-AH7KYH2Z.mjs +50 -0
  13. package/dist/{chunk-53Y37KRG.mjs → chunk-CJVQNARM.mjs} +44 -10
  14. package/dist/chunk-DQ4TNBPG.mjs +63 -0
  15. package/dist/chunk-DUQAD7F6.mjs +173 -0
  16. package/dist/chunk-FRVZ7D6D.mjs +293 -0
  17. package/dist/{chunk-HBF5QEDH.mjs → chunk-K6NLD6SB.mjs} +23 -1
  18. package/dist/chunk-PM2IUGNR.mjs +29 -0
  19. package/dist/chunk-RWFKZ3DF.mjs +413 -0
  20. package/dist/{chunk-RCP27MRC.mjs → chunk-UXIF75BV.mjs} +3 -0
  21. package/dist/chunk-V6T5VMWD.mjs +26 -0
  22. package/dist/{chunk-DSPSRPHF.mjs → chunk-V7LPVPGH.mjs} +123 -18
  23. package/dist/chunk-X6K3ZD62.mjs +53 -0
  24. package/dist/index.d.mts +603 -113
  25. package/dist/index.d.ts +603 -113
  26. package/dist/index.js +1335 -426
  27. package/dist/index.mjs +189 -125
  28. package/package.json +8 -8
  29. package/dist/Guild-TM6YGJWB.mjs +0 -10
  30. package/dist/GuildMember-RZWZ3OCG.mjs +0 -7
  31. package/dist/Message-6IYEYSV6.mjs +0 -7
  32. package/dist/Webhook-32VJD4AL.mjs +0 -7
  33. package/dist/chunk-FJS5FBXO.mjs +0 -233
  34. package/dist/chunk-GFUJVQ7L.mjs +0 -64
  35. package/dist/chunk-SQVCCSNN.mjs +0 -41
  36. package/dist/chunk-X77DFNE3.mjs +0 -136
@@ -0,0 +1,413 @@
1
+ import {
2
+ ErrorCodes,
3
+ FluxerError
4
+ } from "./chunk-V6T5VMWD.mjs";
5
+ import {
6
+ Events
7
+ } from "./chunk-AH7KYH2Z.mjs";
8
+ import {
9
+ buildSendBody
10
+ } from "./chunk-PM2IUGNR.mjs";
11
+ import {
12
+ Base
13
+ } from "./chunk-XNS4O6QJ.mjs";
14
+
15
+ // src/client/MessageManager.ts
16
+ import { Routes } from "@fluxerjs/types";
17
+ import { FluxerAPIError, RateLimitError } from "@fluxerjs/rest";
18
+ var MessageManager = class {
19
+ constructor(client, channelId) {
20
+ this.client = client;
21
+ this.channelId = channelId;
22
+ }
23
+ /**
24
+ * Fetch a message by ID from this channel.
25
+ * @param messageId - Snowflake of the message
26
+ * @returns The message
27
+ * @throws FluxerError with MESSAGE_NOT_FOUND if the message does not exist
28
+ */
29
+ async fetch(messageId) {
30
+ try {
31
+ const { Message } = await import("./Message-OFIVTTAZ.mjs");
32
+ const data = await this.client.rest.get(
33
+ Routes.channelMessage(this.channelId, messageId)
34
+ );
35
+ return new Message(this.client, data);
36
+ } catch (err) {
37
+ if (err instanceof RateLimitError) throw err;
38
+ if (err instanceof FluxerAPIError && err.statusCode === 404) {
39
+ throw new FluxerError(`Message ${messageId} not found in channel ${this.channelId}`, {
40
+ code: ErrorCodes.MessageNotFound,
41
+ cause: err
42
+ });
43
+ }
44
+ throw err instanceof FluxerError ? err : new FluxerError(String(err), { cause: err });
45
+ }
46
+ }
47
+ };
48
+
49
+ // src/util/MessageCollector.ts
50
+ import { EventEmitter } from "events";
51
+ import { Collection } from "@fluxerjs/collection";
52
+ var MessageCollector = class extends EventEmitter {
53
+ client;
54
+ channelId;
55
+ options;
56
+ collected = new Collection();
57
+ _timeout = null;
58
+ _ended = false;
59
+ _listener;
60
+ constructor(client, channelId, options = {}) {
61
+ super();
62
+ this.client = client;
63
+ this.channelId = channelId;
64
+ this.options = {
65
+ filter: options.filter ?? (() => true),
66
+ time: options.time ?? 0,
67
+ max: options.max ?? 0
68
+ };
69
+ this._listener = (message) => {
70
+ if (this._ended || message.channelId !== this.channelId) return;
71
+ if (!this.options.filter(message)) return;
72
+ this.collected.set(message.id, message);
73
+ this.emit("collect", message);
74
+ if (this.options.max > 0 && this.collected.size >= this.options.max) {
75
+ this.stop("limit");
76
+ }
77
+ };
78
+ this.client.on(Events.MessageCreate, this._listener);
79
+ if (this.options.time > 0) {
80
+ this._timeout = setTimeout(() => this.stop("time"), this.options.time);
81
+ }
82
+ }
83
+ stop(reason = "user") {
84
+ if (this._ended) return;
85
+ this._ended = true;
86
+ this.client.off(Events.MessageCreate, this._listener);
87
+ if (this._timeout) {
88
+ clearTimeout(this._timeout);
89
+ this._timeout = null;
90
+ }
91
+ this.emit("end", this.collected, reason);
92
+ }
93
+ on(event, listener) {
94
+ return super.on(event, listener);
95
+ }
96
+ emit(event, ...args) {
97
+ return super.emit(event, ...args);
98
+ }
99
+ };
100
+
101
+ // src/structures/Channel.ts
102
+ import { ChannelType, Routes as Routes2 } from "@fluxerjs/types";
103
+ import { emitDeprecationWarning } from "@fluxerjs/util";
104
+ var Channel = class _Channel extends Base {
105
+ /** Whether this channel has a send method (TextChannel, DMChannel). */
106
+ isSendable() {
107
+ return "send" in this;
108
+ }
109
+ /** Whether this channel is a DM or Group DM. */
110
+ isDM() {
111
+ return this.type === ChannelType.DM || this.type === ChannelType.GroupDM;
112
+ }
113
+ /** Whether this channel is voice-based (VoiceChannel). */
114
+ isVoice() {
115
+ return "bitrate" in this;
116
+ }
117
+ /** Create a DM channel from API data (type DM or GroupDM). */
118
+ static createDM(client, data) {
119
+ return new DMChannel(client, data);
120
+ }
121
+ client;
122
+ id;
123
+ type;
124
+ /** Channel name. Guild channels and Group DMs have names; 1:1 DMs are typically null. */
125
+ name;
126
+ /** Channel icon hash (Group DMs). Null if none. */
127
+ icon;
128
+ /** ISO timestamp when the last message was pinned. Null if never pinned. */
129
+ lastPinTimestamp;
130
+ /** @param data - API channel from GET /channels/{id} or GET /guilds/{id}/channels */
131
+ constructor(client, data) {
132
+ super();
133
+ this.client = client;
134
+ this.id = data.id;
135
+ this.type = data.type;
136
+ this.name = data.name ?? null;
137
+ this.icon = data.icon ?? null;
138
+ this.lastPinTimestamp = data.last_pin_timestamp ?? null;
139
+ }
140
+ /**
141
+ * Create the appropriate channel subclass from API data.
142
+ * @param client - The client instance
143
+ * @param data - Channel data from the API
144
+ */
145
+ static from(client, data) {
146
+ const type = data.type ?? 0;
147
+ if (type === ChannelType.GuildText) return new TextChannel(client, data);
148
+ if (type === ChannelType.GuildCategory) return new CategoryChannel(client, data);
149
+ if (type === ChannelType.GuildVoice) return new VoiceChannel(client, data);
150
+ if (type === ChannelType.GuildLink || type === ChannelType.GuildLinkExtended)
151
+ return new LinkChannel(client, data);
152
+ return new GuildChannel(client, data);
153
+ }
154
+ /**
155
+ * Create a channel from API data, including DM and GroupDM.
156
+ * Used by ChannelManager.fetch() for GET /channels/{id}.
157
+ */
158
+ static fromOrCreate(client, data) {
159
+ const type = data.type ?? 0;
160
+ if (type === ChannelType.DM || type === ChannelType.GroupDM)
161
+ return _Channel.createDM(client, data);
162
+ return _Channel.from(client, data);
163
+ }
164
+ /**
165
+ * Bulk delete messages. Requires Manage Messages permission.
166
+ * @param messageIds - Array of message IDs to delete (2–100)
167
+ */
168
+ async bulkDeleteMessages(messageIds) {
169
+ await this.client.rest.post(Routes2.channelBulkDelete(this.id), {
170
+ body: { message_ids: messageIds },
171
+ auth: true
172
+ });
173
+ }
174
+ /**
175
+ * Send a typing indicator to the channel. Lasts ~10 seconds.
176
+ */
177
+ async sendTyping() {
178
+ await this.client.rest.post(Routes2.channelTyping(this.id), { auth: true });
179
+ }
180
+ };
181
+ var GuildChannel = class extends Channel {
182
+ guildId;
183
+ name;
184
+ position;
185
+ parentId;
186
+ /** Permission overwrites for roles and members. */
187
+ permissionOverwrites;
188
+ constructor(client, data) {
189
+ super(client, data);
190
+ this.guildId = data.guild_id ?? "";
191
+ this.name = data.name ?? null;
192
+ this.position = data.position;
193
+ this.parentId = data.parent_id ?? null;
194
+ this.permissionOverwrites = data.permission_overwrites ?? [];
195
+ }
196
+ /**
197
+ * Create a webhook in this channel.
198
+ * @param options - Webhook name and optional avatar URL
199
+ * @returns The webhook with token (required for send()). Requires Manage Webhooks permission.
200
+ */
201
+ async createWebhook(options) {
202
+ const { Webhook } = await import("./Webhook-RWDDYW2Q.mjs");
203
+ const data = await this.client.rest.post(Routes2.channelWebhooks(this.id), {
204
+ body: options,
205
+ auth: true
206
+ });
207
+ return new Webhook(this.client, data);
208
+ }
209
+ /**
210
+ * Fetch all webhooks in this channel.
211
+ * @returns Webhooks (includes token when listing from channel; can send via send())
212
+ */
213
+ async fetchWebhooks() {
214
+ const { Webhook } = await import("./Webhook-RWDDYW2Q.mjs");
215
+ const data = await this.client.rest.get(Routes2.channelWebhooks(this.id));
216
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
217
+ return list.map((w) => new Webhook(this.client, w));
218
+ }
219
+ /**
220
+ * Create an invite for this channel.
221
+ * @param options - max_uses (0–100), max_age (0–604800 seconds), unique, temporary
222
+ * Requires Create Instant Invite permission.
223
+ */
224
+ async createInvite(options) {
225
+ const { Invite } = await import("./Invite-UM5BU5A6.mjs");
226
+ const body = {};
227
+ if (options?.max_uses != null) body.max_uses = options.max_uses;
228
+ if (options?.max_age != null) body.max_age = options.max_age;
229
+ if (options?.unique != null) body.unique = options.unique;
230
+ if (options?.temporary != null) body.temporary = options.temporary;
231
+ const data = await this.client.rest.post(Routes2.channelInvites(this.id), {
232
+ body: Object.keys(body).length ? body : void 0,
233
+ auth: true
234
+ });
235
+ return new Invite(this.client, data);
236
+ }
237
+ /**
238
+ * Fetch invites for this channel.
239
+ * Requires Manage Channel permission.
240
+ */
241
+ async fetchInvites() {
242
+ const { Invite } = await import("./Invite-UM5BU5A6.mjs");
243
+ const data = await this.client.rest.get(Routes2.channelInvites(this.id));
244
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
245
+ return list.map((i) => new Invite(this.client, i));
246
+ }
247
+ };
248
+ var TextChannel = class extends GuildChannel {
249
+ topic;
250
+ nsfw;
251
+ rateLimitPerUser;
252
+ lastMessageId;
253
+ constructor(client, data) {
254
+ super(client, data);
255
+ this.topic = data.topic ?? null;
256
+ this.nsfw = data.nsfw ?? false;
257
+ this.rateLimitPerUser = data.rate_limit_per_user ?? 0;
258
+ this.lastMessageId = data.last_message_id ?? null;
259
+ }
260
+ /**
261
+ * Send a message to this channel.
262
+ * @param options - Text content or object with content, embeds, and/or files
263
+ */
264
+ async send(options) {
265
+ const opts = typeof options === "string" ? { content: options } : options;
266
+ const body = buildSendBody(options);
267
+ const { Message } = await import("./Message-OFIVTTAZ.mjs");
268
+ const postOptions = opts.files?.length ? { body, files: opts.files } : { body };
269
+ const data = await this.client.rest.post(Routes2.channelMessages(this.id), postOptions);
270
+ return new Message(this.client, data);
271
+ }
272
+ /** Message manager for this channel. Use channel.messages.fetch(messageId). */
273
+ get messages() {
274
+ return new MessageManager(this.client, this.id);
275
+ }
276
+ /**
277
+ * Create a message collector for this channel.
278
+ * Collects messages matching the filter until time expires or max is reached.
279
+ * @param options - Filter, time (ms), and max count
280
+ * @example
281
+ * const collector = channel.createMessageCollector({ filter: m => m.author.id === userId, time: 10000 });
282
+ * collector.on('collect', m => console.log(m.content));
283
+ * collector.on('end', (collected, reason) => { ... });
284
+ */
285
+ createMessageCollector(options) {
286
+ return new MessageCollector(this.client, this.id, options);
287
+ }
288
+ /**
289
+ * Fetch pinned messages in this channel.
290
+ * @returns Pinned messages
291
+ */
292
+ async fetchPinnedMessages() {
293
+ const { Message } = await import("./Message-OFIVTTAZ.mjs");
294
+ const data = await this.client.rest.get(Routes2.channelPins(this.id));
295
+ const list = Array.isArray(data) ? data : data?.items ?? [];
296
+ return list.map((item) => {
297
+ const msg = typeof item === "object" && item && "message" in item ? item.message : item;
298
+ return new Message(this.client, msg);
299
+ });
300
+ }
301
+ /**
302
+ * Fetch a message by ID from this channel.
303
+ * @param messageId - Snowflake of the message
304
+ * @returns The message, or null if not found
305
+ * @deprecated Use channel.messages.fetch(messageId) instead.
306
+ */
307
+ async fetchMessage(messageId) {
308
+ emitDeprecationWarning(
309
+ "Channel.fetchMessage()",
310
+ "Use channel.messages.fetch(messageId) instead."
311
+ );
312
+ return this.client.channels.fetchMessage(this.id, messageId);
313
+ }
314
+ };
315
+ var CategoryChannel = class extends GuildChannel {
316
+ };
317
+ var VoiceChannel = class extends GuildChannel {
318
+ bitrate;
319
+ userLimit;
320
+ rtcRegion;
321
+ constructor(client, data) {
322
+ super(client, data);
323
+ this.bitrate = data.bitrate ?? null;
324
+ this.userLimit = data.user_limit ?? null;
325
+ this.rtcRegion = data.rtc_region ?? null;
326
+ }
327
+ };
328
+ var LinkChannel = class extends GuildChannel {
329
+ url;
330
+ constructor(client, data) {
331
+ super(client, data);
332
+ this.url = data.url ?? null;
333
+ }
334
+ };
335
+ var DMChannel = class extends Channel {
336
+ lastMessageId;
337
+ /** Group DM creator ID. Null for 1:1 DMs. */
338
+ ownerId;
339
+ /** Group DM recipients as User objects. Empty for 1:1 DMs. */
340
+ recipients;
341
+ /** Group DM member display names (userId -> nickname). */
342
+ nicks;
343
+ constructor(client, data) {
344
+ super(client, data);
345
+ this.lastMessageId = data.last_message_id ?? null;
346
+ this.ownerId = data.owner_id ?? null;
347
+ this.recipients = (data.recipients ?? []).map(
348
+ (u) => client.getOrCreateUser(u)
349
+ );
350
+ this.nicks = data.nicks ?? {};
351
+ }
352
+ /**
353
+ * Send a message to this DM channel.
354
+ * @param options - Text content or object with content, embeds, and/or files
355
+ */
356
+ async send(options) {
357
+ const opts = typeof options === "string" ? { content: options } : options;
358
+ const body = buildSendBody(options);
359
+ const { Message } = await import("./Message-OFIVTTAZ.mjs");
360
+ const postOptions = opts.files?.length ? { body, files: opts.files } : { body };
361
+ const data = await this.client.rest.post(Routes2.channelMessages(this.id), postOptions);
362
+ return new Message(this.client, data);
363
+ }
364
+ /** Message manager for this channel. Use channel.messages.fetch(messageId). */
365
+ get messages() {
366
+ return new MessageManager(this.client, this.id);
367
+ }
368
+ /**
369
+ * Create a message collector for this DM channel.
370
+ * @param options - Filter, time (ms), and max count
371
+ */
372
+ createMessageCollector(options) {
373
+ return new MessageCollector(this.client, this.id, options);
374
+ }
375
+ /**
376
+ * Fetch pinned messages in this DM channel.
377
+ * @returns Pinned messages
378
+ */
379
+ async fetchPinnedMessages() {
380
+ const { Message } = await import("./Message-OFIVTTAZ.mjs");
381
+ const data = await this.client.rest.get(Routes2.channelPins(this.id));
382
+ const list = Array.isArray(data) ? data : data?.items ?? [];
383
+ return list.map((item) => {
384
+ const msg = typeof item === "object" && item && "message" in item ? item.message : item;
385
+ return new Message(this.client, msg);
386
+ });
387
+ }
388
+ /**
389
+ * Fetch a message by ID from this DM channel.
390
+ * @param messageId - Snowflake of the message
391
+ * @returns The message, or null if not found
392
+ * @deprecated Use channel.messages.fetch(messageId) instead.
393
+ */
394
+ async fetchMessage(messageId) {
395
+ emitDeprecationWarning(
396
+ "Channel.fetchMessage()",
397
+ "Use channel.messages.fetch(messageId) instead."
398
+ );
399
+ return this.client.channels.fetchMessage(this.id, messageId);
400
+ }
401
+ };
402
+
403
+ export {
404
+ MessageManager,
405
+ MessageCollector,
406
+ Channel,
407
+ GuildChannel,
408
+ TextChannel,
409
+ CategoryChannel,
410
+ VoiceChannel,
411
+ LinkChannel,
412
+ DMChannel
413
+ };
@@ -9,6 +9,8 @@ var GuildBan = class extends Base {
9
9
  guildId;
10
10
  user;
11
11
  reason;
12
+ /** ISO timestamp when a temporary ban expires. Null for permanent bans. */
13
+ expiresAt;
12
14
  /** @param data - API ban from GET /guilds/{id}/bans or gateway GUILD_BAN_ADD */
13
15
  constructor(client, data, guildId) {
14
16
  super();
@@ -16,6 +18,7 @@ var GuildBan = class extends Base {
16
18
  this.guildId = data.guild_id ?? guildId;
17
19
  this.user = client.getOrCreateUser(data.user);
18
20
  this.reason = data.reason ?? null;
21
+ this.expiresAt = data.expires_at ?? null;
19
22
  }
20
23
  /**
21
24
  * Remove this ban (unban the user).
@@ -0,0 +1,26 @@
1
+ // src/errors/FluxerError.ts
2
+ var FluxerError = class _FluxerError extends Error {
3
+ code;
4
+ constructor(message, options) {
5
+ super(message, options?.cause ? { cause: options.cause } : void 0);
6
+ this.name = "FluxerError";
7
+ this.code = options?.code;
8
+ Object.setPrototypeOf(this, _FluxerError.prototype);
9
+ }
10
+ };
11
+
12
+ // src/errors/ErrorCodes.ts
13
+ var ErrorCodes = {
14
+ ClientNotReady: "CLIENT_NOT_READY",
15
+ InvalidToken: "INVALID_TOKEN",
16
+ AlreadyLoggedIn: "ALREADY_LOGGED_IN",
17
+ ChannelNotFound: "CHANNEL_NOT_FOUND",
18
+ MessageNotFound: "MESSAGE_NOT_FOUND",
19
+ GuildNotFound: "GUILD_NOT_FOUND",
20
+ MemberNotFound: "MEMBER_NOT_FOUND"
21
+ };
22
+
23
+ export {
24
+ FluxerError,
25
+ ErrorCodes
26
+ };
@@ -1,11 +1,74 @@
1
+ import {
2
+ Events
3
+ } from "./chunk-AH7KYH2Z.mjs";
4
+ import {
5
+ buildSendBody
6
+ } from "./chunk-PM2IUGNR.mjs";
1
7
  import {
2
8
  Base
3
9
  } from "./chunk-XNS4O6QJ.mjs";
4
10
 
5
11
  // src/structures/Message.ts
6
- import { Collection } from "@fluxerjs/collection";
12
+ import { Collection as Collection2 } from "@fluxerjs/collection";
7
13
  import { MessageType, Routes } from "@fluxerjs/types";
8
14
  import { EmbedBuilder } from "@fluxerjs/builders";
15
+
16
+ // src/util/ReactionCollector.ts
17
+ import { EventEmitter } from "events";
18
+ import { Collection } from "@fluxerjs/collection";
19
+ var ReactionCollector = class extends EventEmitter {
20
+ client;
21
+ messageId;
22
+ channelId;
23
+ options;
24
+ collected = new Collection();
25
+ _timeout = null;
26
+ _ended = false;
27
+ _listener;
28
+ constructor(client, messageId, channelId, options = {}) {
29
+ super();
30
+ this.client = client;
31
+ this.messageId = messageId;
32
+ this.channelId = channelId;
33
+ this.options = {
34
+ filter: options.filter ?? (() => true),
35
+ time: options.time ?? 0,
36
+ max: options.max ?? 0
37
+ };
38
+ this._listener = (reaction, user, _msgId, chId, _emoji, userId) => {
39
+ if (this._ended || reaction.messageId !== this.messageId || chId !== this.channelId) return;
40
+ if (!this.options.filter(reaction, user)) return;
41
+ const key = `${userId}:${reaction.emoji.id ?? reaction.emoji.name}`;
42
+ this.collected.set(key, { reaction, user });
43
+ this.emit("collect", reaction, user);
44
+ if (this.options.max > 0 && this.collected.size >= this.options.max) {
45
+ this.stop("limit");
46
+ }
47
+ };
48
+ this.client.on(Events.MessageReactionAdd, this._listener);
49
+ if (this.options.time > 0) {
50
+ this._timeout = setTimeout(() => this.stop("time"), this.options.time);
51
+ }
52
+ }
53
+ stop(reason = "user") {
54
+ if (this._ended) return;
55
+ this._ended = true;
56
+ this.client.off(Events.MessageReactionAdd, this._listener);
57
+ if (this._timeout) {
58
+ clearTimeout(this._timeout);
59
+ this._timeout = null;
60
+ }
61
+ this.emit("end", this.collected, reason);
62
+ }
63
+ on(event, listener) {
64
+ return super.on(event, listener);
65
+ }
66
+ emit(event, ...args) {
67
+ return super.emit(event, ...args);
68
+ }
69
+ };
70
+
71
+ // src/structures/Message.ts
9
72
  var Message = class _Message extends Base {
10
73
  client;
11
74
  id;
@@ -28,6 +91,14 @@ var Message = class _Message extends Base {
28
91
  messageSnapshots;
29
92
  call;
30
93
  referencedMessage;
94
+ /** Webhook ID if this message was sent via webhook. Null otherwise. */
95
+ webhookId;
96
+ /** Users mentioned in this message. */
97
+ mentions;
98
+ /** Role IDs mentioned in this message. */
99
+ mentionRoles;
100
+ /** Client-side nonce for acknowledgment. Null if not provided. */
101
+ nonce;
31
102
  /** Channel where this message was sent. Resolved from cache; null if not cached (e.g. DM channel not in cache). */
32
103
  get channel() {
33
104
  return this.client.channels.get(this.channelId) ?? null;
@@ -48,7 +119,7 @@ var Message = class _Message extends Base {
48
119
  this.createdAt = new Date(data.timestamp);
49
120
  this.editedAt = data.edited_timestamp ? new Date(data.edited_timestamp) : null;
50
121
  this.pinned = data.pinned;
51
- this.attachments = new Collection();
122
+ this.attachments = new Collection2();
52
123
  for (const a of data.attachments ?? []) this.attachments.set(a.id, a);
53
124
  this.type = data.type ?? MessageType.Default;
54
125
  this.flags = data.flags ?? 0;
@@ -61,17 +132,24 @@ var Message = class _Message extends Base {
61
132
  this.messageSnapshots = data.message_snapshots ?? [];
62
133
  this.call = data.call ?? null;
63
134
  this.referencedMessage = data.referenced_message ? new _Message(client, data.referenced_message) : null;
135
+ this.webhookId = data.webhook_id ?? null;
136
+ this.mentions = (data.mentions ?? []).map((u) => client.getOrCreateUser(u));
137
+ this.mentionRoles = data.mention_roles ?? [];
138
+ this.nonce = data.nonce ?? null;
64
139
  }
65
140
  /**
66
141
  * Send a message to this channel without replying. Use when you want a standalone message.
67
- * @param options - Text content or object with content and/or embeds
142
+ * @param options - Text content or object with content, embeds, and/or files
68
143
  * @example
69
144
  * await message.send('Pong!');
70
145
  * await message.send({ embeds: [embed.toJSON()] });
146
+ * await message.send({ content: 'File', files: [{ name: 'data.txt', data }] });
71
147
  */
72
148
  async send(options) {
73
- const body = typeof options === "string" ? { content: options } : options;
74
- const data = await this.client.rest.post(Routes.channelMessages(this.channelId), { body });
149
+ const opts = typeof options === "string" ? { content: options } : options;
150
+ const body = buildSendBody(options);
151
+ const postOptions = opts.files?.length ? { body, files: opts.files } : { body };
152
+ const data = await this.client.rest.post(Routes.channelMessages(this.channelId), postOptions);
75
153
  return new _Message(this.client, data);
76
154
  }
77
155
  /**
@@ -87,25 +165,21 @@ var Message = class _Message extends Base {
87
165
  }
88
166
  /**
89
167
  * Reply to this message.
90
- * @param options - Text content or object with content and/or embeds
168
+ * @param options - Text content or object with content, embeds, and/or files
91
169
  */
92
170
  async reply(options) {
93
- const body = typeof options === "string" ? {
94
- content: options,
95
- message_reference: {
96
- channel_id: this.channelId,
97
- message_id: this.id,
98
- guild_id: this.guildId ?? void 0
99
- }
100
- } : {
101
- ...options,
171
+ const opts = typeof options === "string" ? { content: options } : options;
172
+ const base = buildSendBody(options);
173
+ const body = {
174
+ ...base,
102
175
  message_reference: {
103
176
  channel_id: this.channelId,
104
177
  message_id: this.id,
105
178
  guild_id: this.guildId ?? void 0
106
179
  }
107
180
  };
108
- const data = await this.client.rest.post(Routes.channelMessages(this.channelId), { body });
181
+ const postOptions = opts.files?.length ? { body, files: opts.files } : { body };
182
+ const data = await this.client.rest.post(Routes.channelMessages(this.channelId), postOptions);
109
183
  return new _Message(this.client, data);
110
184
  }
111
185
  /**
@@ -123,13 +197,26 @@ var Message = class _Message extends Base {
123
197
  });
124
198
  return new _Message(this.client, data);
125
199
  }
200
+ /**
201
+ * Create a reaction collector for this message.
202
+ * Collects reactions matching the filter until time expires or max is reached.
203
+ * @param options - Filter, time (ms), and max count
204
+ * @example
205
+ * const collector = message.createReactionCollector({ filter: (r, u) => u.id === userId, time: 10000 });
206
+ * collector.on('collect', (reaction, user) => console.log(user.username, 'reacted with', reaction.emoji.name));
207
+ * collector.on('end', (collected, reason) => { ... });
208
+ */
209
+ createReactionCollector(options) {
210
+ return new ReactionCollector(this.client, this.id, this.channelId, options);
211
+ }
126
212
  /**
127
213
  * Re-fetch this message from the API to get the latest content, embeds, reactions, etc.
128
214
  * Use when you have a stale Message (e.g. from an old event or cache) and need fresh data.
129
- * @returns The updated message, or null if deleted or not found
215
+ * @returns The updated message
216
+ * @throws FluxerError with MESSAGE_NOT_FOUND if the message was deleted or does not exist
130
217
  * @example
131
218
  * const updated = await message.fetch();
132
- * if (updated) console.log('Latest content:', updated.content);
219
+ * console.log('Latest content:', updated.content);
133
220
  */
134
221
  async fetch() {
135
222
  return this.client.channels.fetchMessage(this.channelId, this.id);
@@ -193,8 +280,26 @@ var Message = class _Message extends Base {
193
280
  const emojiStr = await this.resolveEmojiForReaction(emoji);
194
281
  await this.client.rest.delete(Routes.channelMessageReaction(this.channelId, this.id, emojiStr));
195
282
  }
283
+ /**
284
+ * Fetch users who reacted with the given emoji.
285
+ * @param emoji - Unicode emoji or custom `{ name, id }`
286
+ * @param options - limit (1–100), after (user ID for pagination)
287
+ * @returns Array of User objects
288
+ */
289
+ async fetchReactionUsers(emoji, options) {
290
+ const emojiStr = await this.resolveEmojiForReaction(emoji);
291
+ const params = new URLSearchParams();
292
+ if (options?.limit != null) params.set("limit", String(options.limit));
293
+ if (options?.after) params.set("after", options.after);
294
+ const qs = params.toString();
295
+ const route = Routes.channelMessageReaction(this.channelId, this.id, emojiStr) + (qs ? `?${qs}` : "");
296
+ const data = await this.client.rest.get(route);
297
+ const list = Array.isArray(data) ? data : data?.users ?? [];
298
+ return list.map((u) => this.client.getOrCreateUser(u));
299
+ }
196
300
  };
197
301
 
198
302
  export {
303
+ ReactionCollector,
199
304
  Message
200
305
  };