@fluxerjs/core 1.2.0 → 1.2.2

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/dist/index.mjs CHANGED
@@ -1,214 +1,2460 @@
1
- import {
2
- GuildEmoji
3
- } from "./chunk-IHXSHE2Z.mjs";
4
- import {
5
- GuildSticker
6
- } from "./chunk-KR26CBFE.mjs";
7
- import {
8
- Guild,
9
- GuildMemberManager
10
- } from "./chunk-YGXCMETR.mjs";
11
- import {
12
- MessageReaction
13
- } from "./chunk-75ODDXY3.mjs";
14
- import {
15
- ClientUser,
16
- User
17
- } from "./chunk-U33GNMEZ.mjs";
18
- import {
19
- Message,
20
- ReactionCollector
21
- } from "./chunk-HVCL7QLT.mjs";
22
- import {
23
- Webhook
24
- } from "./chunk-3JVE6RW7.mjs";
25
- import {
26
- Invite
27
- } from "./chunk-QEXIYXXU.mjs";
28
- import {
29
- CategoryChannel,
30
- Channel,
31
- DMChannel,
32
- GuildChannel,
33
- LinkChannel,
34
- MessageCollector,
35
- MessageManager,
36
- TextChannel,
37
- VoiceChannel
38
- } from "./chunk-IRDYSGQO.mjs";
39
- import {
40
- ErrorCodes,
41
- FluxerError
42
- } from "./chunk-6S3BDHBI.mjs";
43
- import {
44
- Events
45
- } from "./chunk-AH7KYH2Z.mjs";
46
- import {
47
- buildSendBody,
48
- resolveMessageFiles
49
- } from "./chunk-VJDH54HJ.mjs";
50
- import {
51
- GuildMember
52
- } from "./chunk-24E6OQID.mjs";
53
- import {
54
- cdnAvatarURL,
55
- cdnBannerURL,
56
- cdnDefaultAvatarURL,
57
- cdnDisplayAvatarURL,
58
- cdnMemberAvatarURL,
59
- cdnMemberBannerURL
60
- } from "./chunk-G6F6VL4O.mjs";
61
- import {
62
- CDN_URL,
63
- STATIC_CDN_URL
64
- } from "./chunk-BKPVGBOT.mjs";
65
- import {
66
- Role
67
- } from "./chunk-2ZSMLDEI.mjs";
68
- import {
69
- GuildBan
70
- } from "./chunk-UXIF75BV.mjs";
71
- import {
72
- Base
73
- } from "./chunk-XNS4O6QJ.mjs";
74
-
75
1
  // src/client/Client.ts
76
- import { EventEmitter } from "events";
2
+ import { EventEmitter as EventEmitter3 } from "events";
77
3
  import { REST } from "@fluxerjs/rest";
78
4
  import { WebSocketManager } from "@fluxerjs/ws";
79
- import { Routes as Routes4 } from "@fluxerjs/types";
5
+ import {
6
+ Routes as Routes20
7
+ } from "@fluxerjs/types";
80
8
 
81
9
  // src/client/ChannelManager.ts
10
+ import { Collection as Collection4 } from "@fluxerjs/collection";
11
+ import { Routes as Routes6 } from "@fluxerjs/types";
12
+ import { emitDeprecationWarning as emitDeprecationWarning2 } from "@fluxerjs/util";
13
+ import { FluxerAPIError as FluxerAPIError2, RateLimitError as RateLimitError2 } from "@fluxerjs/rest";
14
+
15
+ // src/errors/FluxerError.ts
16
+ var FluxerError = class _FluxerError extends Error {
17
+ code;
18
+ constructor(message, options) {
19
+ super(message, options?.cause ? { cause: options.cause } : void 0);
20
+ this.name = "FluxerError";
21
+ this.code = options?.code;
22
+ Object.setPrototypeOf(this, _FluxerError.prototype);
23
+ }
24
+ };
25
+
26
+ // src/errors/ErrorCodes.ts
27
+ var ErrorCodes = {
28
+ ClientNotReady: "CLIENT_NOT_READY",
29
+ InvalidToken: "INVALID_TOKEN",
30
+ AlreadyLoggedIn: "ALREADY_LOGGED_IN",
31
+ ChannelNotFound: "CHANNEL_NOT_FOUND",
32
+ MessageNotFound: "MESSAGE_NOT_FOUND",
33
+ GuildNotFound: "GUILD_NOT_FOUND",
34
+ MemberNotFound: "MEMBER_NOT_FOUND",
35
+ RoleNotFound: "ROLE_NOT_FOUND",
36
+ EmojiNotInGuild: "EMOJI_NOT_IN_GUILD",
37
+ EmojiNotFound: "EMOJI_NOT_FOUND"
38
+ };
39
+
40
+ // src/util/messageUtils.ts
41
+ import { EmbedBuilder } from "@fluxerjs/builders";
42
+ var FILE_FETCH_TIMEOUT_MS = 3e4;
43
+ async function resolveMessageFiles(files) {
44
+ const result = [];
45
+ for (let i = 0; i < files.length; i++) {
46
+ const f = files[i];
47
+ const filename = f.filename ?? f.name;
48
+ if ("url" in f && f.url) {
49
+ if (!URL.canParse(f.url)) {
50
+ throw new Error(`Invalid file URL at index ${i}: ${f.url}`);
51
+ }
52
+ const res = await fetch(f.url, {
53
+ signal: AbortSignal.timeout(FILE_FETCH_TIMEOUT_MS)
54
+ });
55
+ if (!res.ok) {
56
+ throw new Error(`Failed to fetch file from ${f.url}: ${res.status} ${res.statusText}`);
57
+ }
58
+ const data = await res.arrayBuffer();
59
+ result.push({ name: f.name, data, filename });
60
+ } else if ("data" in f && f.data != null) {
61
+ result.push({ name: f.name, data: f.data, filename });
62
+ } else {
63
+ throw new Error(`File at index ${i} must have either "data" or "url"`);
64
+ }
65
+ }
66
+ return result;
67
+ }
68
+ function buildSendBody(options) {
69
+ const body = typeof options === "string" ? { content: options } : options;
70
+ const result = {};
71
+ if (body.content !== void 0) result.content = body.content;
72
+ if (body.embeds?.length) {
73
+ result.embeds = body.embeds.map((e) => e instanceof EmbedBuilder ? e.toJSON() : e);
74
+ }
75
+ if (body.files?.length && body.attachments) {
76
+ result.attachments = body.attachments.map((a) => ({
77
+ id: a.id,
78
+ filename: a.filename,
79
+ ...a.title != null && { title: a.title },
80
+ ...a.description != null && { description: a.description },
81
+ ...a.flags != null && { flags: a.flags }
82
+ }));
83
+ } else if (body.files?.length) {
84
+ result.attachments = body.files.map((f, i) => ({
85
+ id: i,
86
+ filename: f.filename ?? f.name
87
+ }));
88
+ }
89
+ return result;
90
+ }
91
+
92
+ // src/client/MessageManager.ts
93
+ import { Routes as Routes2 } from "@fluxerjs/types";
94
+ import { FluxerAPIError, RateLimitError } from "@fluxerjs/rest";
95
+
96
+ // src/structures/Base.ts
97
+ var Base = class {
98
+ };
99
+
100
+ // src/structures/Message.ts
101
+ import { Collection as Collection2 } from "@fluxerjs/collection";
102
+ import { MessageType, Routes } from "@fluxerjs/types";
103
+ import { EmbedBuilder as EmbedBuilder2 } from "@fluxerjs/builders";
104
+
105
+ // src/util/ReactionCollector.ts
106
+ import { EventEmitter } from "events";
82
107
  import { Collection } from "@fluxerjs/collection";
83
- import { Routes } from "@fluxerjs/types";
108
+
109
+ // src/util/Events.ts
110
+ var Events = {
111
+ Ready: "ready",
112
+ MessageCreate: "messageCreate",
113
+ MessageUpdate: "messageUpdate",
114
+ MessageDelete: "messageDelete",
115
+ MessageDeleteBulk: "messageDeleteBulk",
116
+ MessageReactionAdd: "messageReactionAdd",
117
+ MessageReactionRemove: "messageReactionRemove",
118
+ MessageReactionRemoveAll: "messageReactionRemoveAll",
119
+ MessageReactionRemoveEmoji: "messageReactionRemoveEmoji",
120
+ InteractionCreate: "interactionCreate",
121
+ GuildCreate: "guildCreate",
122
+ GuildUpdate: "guildUpdate",
123
+ GuildDelete: "guildDelete",
124
+ GuildBanAdd: "guildBanAdd",
125
+ GuildBanRemove: "guildBanRemove",
126
+ GuildEmojisUpdate: "guildEmojisUpdate",
127
+ GuildStickersUpdate: "guildStickersUpdate",
128
+ GuildIntegrationsUpdate: "guildIntegrationsUpdate",
129
+ GuildMemberAdd: "guildMemberAdd",
130
+ GuildMemberUpdate: "guildMemberUpdate",
131
+ GuildMemberRemove: "guildMemberRemove",
132
+ GuildRoleCreate: "guildRoleCreate",
133
+ GuildRoleUpdate: "guildRoleUpdate",
134
+ GuildRoleDelete: "guildRoleDelete",
135
+ GuildScheduledEventCreate: "guildScheduledEventCreate",
136
+ GuildScheduledEventUpdate: "guildScheduledEventUpdate",
137
+ GuildScheduledEventDelete: "guildScheduledEventDelete",
138
+ ChannelCreate: "channelCreate",
139
+ ChannelUpdate: "channelUpdate",
140
+ ChannelDelete: "channelDelete",
141
+ ChannelPinsUpdate: "channelPinsUpdate",
142
+ InviteCreate: "inviteCreate",
143
+ InviteDelete: "inviteDelete",
144
+ TypingStart: "typingStart",
145
+ UserUpdate: "userUpdate",
146
+ PresenceUpdate: "presenceUpdate",
147
+ VoiceStateUpdate: "voiceStateUpdate",
148
+ VoiceServerUpdate: "voiceServerUpdate",
149
+ VoiceStatesSync: "voiceStatesSync",
150
+ WebhooksUpdate: "webhooksUpdate",
151
+ Resumed: "resumed",
152
+ Error: "error",
153
+ Debug: "debug"
154
+ };
155
+
156
+ // src/util/ReactionCollector.ts
157
+ var ReactionCollector = class extends EventEmitter {
158
+ client;
159
+ messageId;
160
+ channelId;
161
+ options;
162
+ collected = new Collection();
163
+ _timeout = null;
164
+ _ended = false;
165
+ _listener;
166
+ constructor(client, messageId, channelId, options = {}) {
167
+ super();
168
+ this.client = client;
169
+ this.messageId = messageId;
170
+ this.channelId = channelId;
171
+ this.options = {
172
+ filter: options.filter ?? (() => true),
173
+ time: options.time ?? 0,
174
+ max: options.max ?? 0
175
+ };
176
+ this._listener = (reaction, user, _msgId, chId, _emoji, userId) => {
177
+ if (this._ended || reaction.messageId !== this.messageId || chId !== this.channelId) return;
178
+ if (!this.options.filter(reaction, user)) return;
179
+ const key = `${userId}:${reaction.emoji.id ?? reaction.emoji.name}`;
180
+ this.collected.set(key, { reaction, user });
181
+ this.emit("collect", reaction, user);
182
+ if (this.options.max > 0 && this.collected.size >= this.options.max) {
183
+ this.stop("limit");
184
+ }
185
+ };
186
+ this.client.on(Events.MessageReactionAdd, this._listener);
187
+ if (this.options.time > 0) {
188
+ this._timeout = setTimeout(() => this.stop("time"), this.options.time);
189
+ }
190
+ }
191
+ stop(reason = "user") {
192
+ if (this._ended) return;
193
+ this._ended = true;
194
+ this.client.off(Events.MessageReactionAdd, this._listener);
195
+ if (this._timeout) {
196
+ clearTimeout(this._timeout);
197
+ this._timeout = null;
198
+ }
199
+ this.emit("end", this.collected, reason);
200
+ }
201
+ on(event, listener) {
202
+ return super.on(event, listener);
203
+ }
204
+ emit(event, ...args) {
205
+ return super.emit(event, ...args);
206
+ }
207
+ };
208
+
209
+ // src/structures/Message.ts
210
+ var Message = class _Message extends Base {
211
+ client;
212
+ id;
213
+ channelId;
214
+ guildId;
215
+ author;
216
+ content;
217
+ createdAt;
218
+ editedAt;
219
+ pinned;
220
+ attachments;
221
+ type;
222
+ flags;
223
+ mentionEveryone;
224
+ tts;
225
+ embeds;
226
+ stickers;
227
+ reactions;
228
+ messageReference;
229
+ messageSnapshots;
230
+ call;
231
+ referencedMessage;
232
+ /** Webhook ID if this message was sent via webhook. Null otherwise. */
233
+ webhookId;
234
+ /** Users mentioned in this message. */
235
+ mentions;
236
+ /** Role IDs mentioned in this message. */
237
+ mentionRoles;
238
+ /** Client-side nonce for acknowledgment. Null if not provided. */
239
+ nonce;
240
+ /**
241
+ * Channel where this message was sent. Resolved from cache; null if not cached.
242
+ * Messages can only exist in text-based channels (text, DM, announcement), so this always has send() when non-null.
243
+ */
244
+ get channel() {
245
+ return this.client.channels.get(this.channelId) ?? null;
246
+ }
247
+ /** Guild where this message was sent. Resolved from cache; null for DMs or if not cached. */
248
+ get guild() {
249
+ return this.guildId ? this.client.guilds.get(this.guildId) ?? null : null;
250
+ }
251
+ /**
252
+ * Resolve the channel (from cache or API). Use when you need the channel and it may not be cached.
253
+ * @returns The channel
254
+ * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
255
+ */
256
+ async resolveChannel() {
257
+ return this.client.channels.resolve(this.channelId);
258
+ }
259
+ /**
260
+ * Resolve the guild (from cache or API). Returns null for DMs.
261
+ * @returns The guild, or null if this is a DM or guild not found
262
+ */
263
+ async resolveGuild() {
264
+ return this.guildId ? this.client.guilds.resolve(this.guildId) : null;
265
+ }
266
+ /** @param data - API message from POST/PATCH /channels/{id}/messages or gateway MESSAGE_CREATE */
267
+ constructor(client, data) {
268
+ super();
269
+ this.client = client;
270
+ this.id = data.id;
271
+ this.channelId = data.channel_id;
272
+ this.guildId = data.guild_id ?? null;
273
+ this.author = client.getOrCreateUser(data.author);
274
+ this.content = data.content;
275
+ this.createdAt = new Date(data.timestamp);
276
+ this.editedAt = data.edited_timestamp ? new Date(data.edited_timestamp) : null;
277
+ this.pinned = data.pinned;
278
+ this.attachments = new Collection2();
279
+ for (const a of data.attachments ?? []) this.attachments.set(a.id, a);
280
+ this.type = data.type ?? MessageType.Default;
281
+ this.flags = data.flags ?? 0;
282
+ this.mentionEveryone = data.mention_everyone ?? false;
283
+ this.tts = data.tts ?? false;
284
+ this.embeds = data.embeds ?? [];
285
+ this.stickers = data.stickers ?? [];
286
+ this.reactions = data.reactions ?? [];
287
+ this.messageReference = data.message_reference ?? null;
288
+ this.messageSnapshots = data.message_snapshots ?? [];
289
+ this.call = data.call ?? null;
290
+ this.referencedMessage = data.referenced_message ? new _Message(client, data.referenced_message) : null;
291
+ this.webhookId = data.webhook_id ?? null;
292
+ this.mentions = (data.mentions ?? []).map((u) => client.getOrCreateUser(u));
293
+ this.mentionRoles = data.mention_roles ?? [];
294
+ this.nonce = data.nonce ?? null;
295
+ }
296
+ /**
297
+ * Send a message to this channel without replying. Use when you want a standalone message.
298
+ * @param options - Text content or object with content, embeds, and/or files
299
+ * @example
300
+ * await message.send('Pong!');
301
+ * await message.send({ embeds: [embed] }); // EmbedBuilder auto-converted
302
+ * await message.send({ content: 'File', files: [{ name: 'data.txt', data }] });
303
+ */
304
+ async send(options) {
305
+ const payload = await _Message._createMessageBody(options);
306
+ return this._send(payload);
307
+ }
308
+ /**
309
+ * Send a message to a specific channel. Use for logging, forwarding, or sending to another channel in the guild.
310
+ * @param channelId - Snowflake of the target channel (e.g. log channel ID)
311
+ * @param options - Text content or object with content and/or embeds
312
+ * @example
313
+ * await message.sendTo(logChannelId, 'User ' + message.author.username + ' said: ' + message.content);
314
+ * await message.sendTo(announceChannelId, { embeds: [embed] });
315
+ */
316
+ async sendTo(channelId, options) {
317
+ return this.client.channels.send(channelId, options);
318
+ }
319
+ /**
320
+ * Reply to this message (shows as a reply in the client).
321
+ * @param options - Text content or object with content, embeds, and/or files
322
+ * @example
323
+ * await message.reply('Pong!');
324
+ * await message.reply({ embeds: [embed] });
325
+ */
326
+ async reply(options) {
327
+ const payload = await _Message._createMessageBody(options, {
328
+ channel_id: this.channelId,
329
+ message_id: this.id,
330
+ guild_id: this.guildId ?? void 0
331
+ });
332
+ return this._send(payload);
333
+ }
334
+ /** Exposed for testing purposes, use Message.reply() or send() for normal use */
335
+ static async _createMessageBody(content, referenced_message) {
336
+ if (typeof content === "string") {
337
+ if (content.length === 0) {
338
+ throw new RangeError("Cannot send an empty message");
339
+ }
340
+ content = { content };
341
+ }
342
+ const base = buildSendBody(content);
343
+ const files = content.files?.length ? await resolveMessageFiles(content.files) : void 0;
344
+ return referenced_message ? { files, body: { ...base, referenced_message } } : { files, body: { ...base } };
345
+ }
346
+ async _send(payload) {
347
+ const data = await this.client.rest.post(
348
+ Routes.channelMessages(this.channelId),
349
+ payload
350
+ );
351
+ return new _Message(this.client, data);
352
+ }
353
+ /**
354
+ * Edit this message. Only the author (or admins) can edit.
355
+ * @param options - New content and/or embeds
356
+ */
357
+ async edit(options) {
358
+ const body = {};
359
+ if (options.content !== void 0) body.content = options.content;
360
+ if (options.embeds?.length) {
361
+ body.embeds = options.embeds.map((e) => e instanceof EmbedBuilder2 ? e.toJSON() : e);
362
+ }
363
+ const data = await this.client.rest.patch(Routes.channelMessage(this.channelId, this.id), {
364
+ body
365
+ });
366
+ return new _Message(this.client, data);
367
+ }
368
+ /**
369
+ * Create a reaction collector for this message.
370
+ * Collects reactions matching the filter until time expires or max is reached.
371
+ * @param options - Filter, time (ms), and max count
372
+ * @example
373
+ * const collector = message.createReactionCollector({ filter: (r, u) => u.id === userId, time: 10000 });
374
+ * collector.on('collect', (reaction, user) => console.log(user.username, 'reacted with', reaction.emoji.name));
375
+ * collector.on('end', (collected, reason) => { ... });
376
+ */
377
+ createReactionCollector(options) {
378
+ return new ReactionCollector(this.client, this.id, this.channelId, options);
379
+ }
380
+ /**
381
+ * Re-fetch this message from the API to get the latest content, embeds, reactions, etc.
382
+ * Use when you have a stale Message (e.g. from an old event or cache) and need fresh data.
383
+ * @returns The updated message
384
+ * @throws FluxerError with MESSAGE_NOT_FOUND if the message was deleted or does not exist
385
+ * @example
386
+ * const updated = await message.fetch();
387
+ * console.log('Latest content:', updated.content);
388
+ */
389
+ async fetch() {
390
+ return this.client.channels.fetchMessage(this.channelId, this.id);
391
+ }
392
+ /** Delete this message. */
393
+ async delete() {
394
+ await this.client.rest.delete(Routes.channelMessage(this.channelId, this.id));
395
+ }
396
+ /**
397
+ * Delete a specific attachment from this message.
398
+ * DELETE /channels/{id}/messages/{id}/attachments/{attachmentId}.
399
+ */
400
+ async deleteAttachment(attachmentId) {
401
+ await this.client.rest.delete(
402
+ Routes.channelMessageAttachment(this.channelId, this.id, attachmentId),
403
+ { auth: true }
404
+ );
405
+ this.attachments.delete(attachmentId);
406
+ }
407
+ /** Pin this message to the channel. Requires Manage Messages permission. */
408
+ async pin() {
409
+ await this.client.rest.put(Routes.channelPinMessage(this.channelId, this.id));
410
+ this.pinned = true;
411
+ }
412
+ /** Unpin this message from the channel. Requires Manage Messages permission. */
413
+ async unpin() {
414
+ await this.client.rest.delete(Routes.channelPinMessage(this.channelId, this.id));
415
+ this.pinned = false;
416
+ }
417
+ /**
418
+ * Format emoji for reaction API: unicode string or "name:id" for custom.
419
+ * For string resolution (e.g. :name:), use client.resolveEmoji; Message methods resolve automatically when guildId is available.
420
+ */
421
+ static formatEmoji(emoji) {
422
+ if (typeof emoji === "string") return emoji;
423
+ return `${emoji.name}:${emoji.id}`;
424
+ }
425
+ resolveEmojiForReaction(emoji) {
426
+ return this.client.resolveEmoji(emoji, this.guildId);
427
+ }
428
+ /**
429
+ * Add a reaction to this message (as the bot).
430
+ * @param emoji - Unicode emoji, custom `{ name, id }`, `:name:`, `name:id`, or `<:name:id>`
431
+ */
432
+ async react(emoji) {
433
+ const emojiStr = await this.resolveEmojiForReaction(emoji);
434
+ const route = `${Routes.channelMessageReaction(this.channelId, this.id, emojiStr)}/@me`;
435
+ await this.client.rest.put(route);
436
+ }
437
+ /**
438
+ * Remove the bot's reaction, or a specific user's reaction if userId is provided.
439
+ * @param emoji - Unicode emoji, custom `{ name, id }`, `:name:`, `name:id`, or `<:name:id>`
440
+ * @param userId - If provided, removes that user's reaction (requires moderator permissions)
441
+ */
442
+ async removeReaction(emoji, userId) {
443
+ const emojiStr = await this.resolveEmojiForReaction(emoji);
444
+ const route = `${Routes.channelMessageReaction(this.channelId, this.id, emojiStr)}/${userId ?? "@me"}`;
445
+ await this.client.rest.delete(route);
446
+ }
447
+ /**
448
+ * Remove all reactions from this message.
449
+ * Requires moderator permissions.
450
+ */
451
+ async removeAllReactions() {
452
+ await this.client.rest.delete(Routes.channelMessageReactions(this.channelId, this.id));
453
+ }
454
+ /**
455
+ * Remove all reactions of a specific emoji from this message.
456
+ * @param emoji - Unicode emoji, custom `{ name, id }`, `:name:`, `name:id`, or `<:name:id>`. Requires moderator permissions.
457
+ */
458
+ async removeReactionEmoji(emoji) {
459
+ const emojiStr = await this.resolveEmojiForReaction(emoji);
460
+ await this.client.rest.delete(Routes.channelMessageReaction(this.channelId, this.id, emojiStr));
461
+ }
462
+ /**
463
+ * Fetch users who reacted with the given emoji.
464
+ * @param emoji - Unicode emoji or custom `{ name, id }`
465
+ * @param options - limit (1–100), after (user ID for pagination)
466
+ * @returns Array of User objects
467
+ */
468
+ async fetchReactionUsers(emoji, options) {
469
+ const emojiStr = await this.resolveEmojiForReaction(emoji);
470
+ const params = new URLSearchParams();
471
+ if (options?.limit != null) params.set("limit", String(options.limit));
472
+ if (options?.after) params.set("after", options.after);
473
+ const qs = params.toString();
474
+ const route = Routes.channelMessageReaction(this.channelId, this.id, emojiStr) + (qs ? `?${qs}` : "");
475
+ const data = await this.client.rest.get(route);
476
+ const list = Array.isArray(data) ? data : data?.users ?? [];
477
+ return list.map((u) => this.client.getOrCreateUser(u));
478
+ }
479
+ };
480
+
481
+ // src/client/MessageManager.ts
482
+ var MessageManager = class {
483
+ constructor(client, channelId) {
484
+ this.client = client;
485
+ this.channelId = channelId;
486
+ }
487
+ /**
488
+ * Fetch a message by ID from this channel.
489
+ * @param messageId - Snowflake of the message
490
+ * @returns The message
491
+ * @throws FluxerError with MESSAGE_NOT_FOUND if the message does not exist
492
+ */
493
+ async fetch(messageId) {
494
+ try {
495
+ const data = await this.client.rest.get(
496
+ Routes2.channelMessage(this.channelId, messageId)
497
+ );
498
+ return new Message(this.client, data);
499
+ } catch (err) {
500
+ if (err instanceof RateLimitError) throw err;
501
+ if (err instanceof FluxerAPIError && err.statusCode === 404) {
502
+ throw new FluxerError(`Message ${messageId} not found in channel ${this.channelId}`, {
503
+ code: ErrorCodes.MessageNotFound,
504
+ cause: err
505
+ });
506
+ }
507
+ throw err instanceof FluxerError ? err : new FluxerError(String(err), { cause: err });
508
+ }
509
+ }
510
+ };
511
+
512
+ // src/util/MessageCollector.ts
513
+ import { EventEmitter as EventEmitter2 } from "events";
514
+ import { Collection as Collection3 } from "@fluxerjs/collection";
515
+ var MessageCollector = class extends EventEmitter2 {
516
+ client;
517
+ channelId;
518
+ options;
519
+ collected = new Collection3();
520
+ _timeout = null;
521
+ _ended = false;
522
+ _listener;
523
+ constructor(client, channelId, options = {}) {
524
+ super();
525
+ this.client = client;
526
+ this.channelId = channelId;
527
+ this.options = {
528
+ filter: options.filter ?? (() => true),
529
+ time: options.time ?? 0,
530
+ max: options.max ?? 0
531
+ };
532
+ this._listener = (message) => {
533
+ if (this._ended || message.channelId !== this.channelId) return;
534
+ if (!this.options.filter(message)) return;
535
+ this.collected.set(message.id, message);
536
+ this.emit("collect", message);
537
+ if (this.options.max > 0 && this.collected.size >= this.options.max) {
538
+ this.stop("limit");
539
+ }
540
+ };
541
+ this.client.on(Events.MessageCreate, this._listener);
542
+ if (this.options.time > 0) {
543
+ this._timeout = setTimeout(() => this.stop("time"), this.options.time);
544
+ }
545
+ }
546
+ stop(reason = "user") {
547
+ if (this._ended) return;
548
+ this._ended = true;
549
+ this.client.off(Events.MessageCreate, this._listener);
550
+ if (this._timeout) {
551
+ clearTimeout(this._timeout);
552
+ this._timeout = null;
553
+ }
554
+ this.emit("end", this.collected, reason);
555
+ }
556
+ on(event, listener) {
557
+ return super.on(event, listener);
558
+ }
559
+ emit(event, ...args) {
560
+ return super.emit(event, ...args);
561
+ }
562
+ };
563
+
564
+ // src/structures/Channel.ts
565
+ import { ChannelType, Routes as Routes5 } from "@fluxerjs/types";
566
+ import { PermissionFlags } from "@fluxerjs/util";
84
567
  import { emitDeprecationWarning } from "@fluxerjs/util";
85
- import { FluxerAPIError, RateLimitError } from "@fluxerjs/rest";
86
- var ChannelManager = class extends Collection {
87
- constructor(client) {
568
+
569
+ // src/structures/Webhook.ts
570
+ import { Routes as Routes3 } from "@fluxerjs/types";
571
+
572
+ // src/util/Constants.ts
573
+ var CDN_URL = "https://fluxerusercontent.com";
574
+ var STATIC_CDN_URL = "https://fluxerstatic.com";
575
+
576
+ // src/util/cdn.ts
577
+ function getExtension(hash, options) {
578
+ const ext = options?.extension ?? "png";
579
+ if (hash?.startsWith("a_")) return "gif";
580
+ return ext;
581
+ }
582
+ function appendSize(options) {
583
+ return options?.size ? `?size=${options.size}` : "";
584
+ }
585
+ function cdnAvatarURL(userId, avatarHash, options) {
586
+ if (!avatarHash) return null;
587
+ const ext = getExtension(avatarHash, options);
588
+ const size = appendSize(options);
589
+ return `${CDN_URL}/avatars/${userId}/${avatarHash}.${ext}${size}`;
590
+ }
591
+ function cdnDisplayAvatarURL(userId, avatarHash, options) {
592
+ return cdnAvatarURL(userId, avatarHash, options) ?? cdnDefaultAvatarURL(userId);
593
+ }
594
+ function cdnBannerURL(resourceId, bannerHash, options) {
595
+ if (!bannerHash) return null;
596
+ const ext = getExtension(bannerHash, options);
597
+ const size = appendSize(options);
598
+ return `${CDN_URL}/banners/${resourceId}/${bannerHash}.${ext}${size}`;
599
+ }
600
+ function cdnMemberAvatarURL(guildId, userId, avatarHash, options) {
601
+ if (!avatarHash) return null;
602
+ const ext = getExtension(avatarHash, options);
603
+ const size = appendSize(options);
604
+ return `${CDN_URL}/guilds/${guildId}/users/${userId}/avatars/${avatarHash}.${ext}${size}`;
605
+ }
606
+ function cdnMemberBannerURL(guildId, userId, bannerHash, options) {
607
+ if (!bannerHash) return null;
608
+ const ext = getExtension(bannerHash, options);
609
+ const size = appendSize(options);
610
+ return `${CDN_URL}/guilds/${guildId}/users/${userId}/banners/${bannerHash}.${ext}${size}`;
611
+ }
612
+ function cdnDefaultAvatarURL(userIdOrIndex) {
613
+ const index = typeof userIdOrIndex === "string" ? Number(BigInt(userIdOrIndex) % 6n) : Math.abs(Math.floor(userIdOrIndex) % 6);
614
+ return `${STATIC_CDN_URL}/avatars/${index}.png`;
615
+ }
616
+
617
+ // src/structures/Webhook.ts
618
+ var Webhook = class _Webhook extends Base {
619
+ client;
620
+ id;
621
+ guildId;
622
+ channelId;
623
+ name;
624
+ avatar;
625
+ /** Present only when webhook was created via createWebhook(); not returned when fetching. */
626
+ token;
627
+ /** User who created the webhook. */
628
+ user;
629
+ /** @param data - API webhook from POST /channels/{id}/webhooks (has token) or GET /webhooks/{id} (no token) */
630
+ constructor(client, data) {
88
631
  super();
89
632
  this.client = client;
90
- this.maxSize = client.options?.cache?.channels ?? 0;
633
+ this.id = data.id;
634
+ this.guildId = data.guild_id;
635
+ this.channelId = data.channel_id;
636
+ this.name = data.name ?? "Unknown";
637
+ this.avatar = data.avatar ?? null;
638
+ this.token = data.token ?? null;
639
+ this.user = client.getOrCreateUser(data.user);
640
+ }
641
+ /**
642
+ * Get the URL for this webhook's avatar.
643
+ * Returns null if the webhook has no custom avatar.
644
+ */
645
+ avatarURL(options) {
646
+ return cdnAvatarURL(this.id, this.avatar, options);
647
+ }
648
+ /** Delete this webhook. Requires bot token with Manage Webhooks permission. */
649
+ async delete() {
650
+ await this.client.rest.delete(Routes3.webhook(this.id), { auth: true });
651
+ }
652
+ /**
653
+ * Edit this webhook. With token: name and avatar only. Without token (bot auth): name, avatar, and channel_id.
654
+ * @param options - Fields to update (name, avatar, channel_id when using bot auth)
655
+ * @returns This webhook instance with updated fields
656
+ */
657
+ async edit(options) {
658
+ const body = {};
659
+ if (options.name !== void 0) body.name = options.name;
660
+ if (options.avatar !== void 0) body.avatar = options.avatar;
661
+ if ("channel_id" in options && options.channel_id !== void 0 && !this.token) {
662
+ body.channel_id = options.channel_id;
663
+ }
664
+ if (this.token) {
665
+ const data2 = await this.client.rest.patch(Routes3.webhookExecute(this.id, this.token), {
666
+ body,
667
+ auth: false
668
+ });
669
+ const w2 = data2;
670
+ this.name = w2.name ?? this.name;
671
+ this.avatar = w2.avatar ?? null;
672
+ return this;
673
+ }
674
+ const data = await this.client.rest.patch(Routes3.webhook(this.id), {
675
+ body,
676
+ auth: true
677
+ });
678
+ const w = data;
679
+ this.name = w.name ?? this.name;
680
+ this.avatar = w.avatar ?? null;
681
+ this.channelId = w.channel_id ?? this.channelId;
682
+ return this;
683
+ }
684
+ /**
685
+ * Send a message via this webhook. Requires the webhook token (only present when created, not when fetched).
686
+ * @param options - Text content or object with content, embeds, username, avatar_url, tts, files, attachments
687
+ * @param wait - If true, waits for the API and returns the created Message; otherwise returns void (204)
688
+ * @throws Error if token is not available
689
+ * @example
690
+ * await webhook.send('Hello!');
691
+ * await webhook.send({ embeds: [embed] });
692
+ * await webhook.send({ content: 'File attached', files: [{ name: 'data.txt', data: buffer }] });
693
+ * const msg = await webhook.send({ content: 'Hi' }, true);
694
+ */
695
+ async send(options, wait) {
696
+ if (!this.token) {
697
+ throw new Error(
698
+ "Webhook token is required to send. The token is only returned when creating a webhook; fetched webhooks cannot send."
699
+ );
700
+ }
701
+ const opts = typeof options === "string" ? { content: options } : options;
702
+ const body = buildSendBody(opts);
703
+ if (opts.username !== void 0) body.username = opts.username;
704
+ if (opts.avatar_url !== void 0) body.avatar_url = opts.avatar_url;
705
+ if (opts.tts !== void 0) body.tts = opts.tts;
706
+ const route = Routes3.webhookExecute(this.id, this.token) + (wait ? "?wait=true" : "");
707
+ const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
708
+ const postOptions = files?.length ? { body, files, auth: false } : { body, auth: false };
709
+ const data = await this.client.rest.post(route, postOptions);
710
+ if (wait && data) {
711
+ return new Message(this.client, data);
712
+ }
713
+ return void 0;
714
+ }
715
+ /**
716
+ * Fetch a webhook by ID using bot auth.
717
+ * @param client - The client instance
718
+ * @param webhookId - The webhook ID
719
+ * @returns Webhook without token (cannot send)
720
+ */
721
+ static async fetch(client, webhookId) {
722
+ const data = await client.rest.get(Routes3.webhook(webhookId));
723
+ return new _Webhook(client, data);
724
+ }
725
+ /**
726
+ * Create a Webhook instance from an ID and token (e.g. from a stored webhook URL).
727
+ * @param client - The client instance
728
+ * @param webhookId - The webhook ID
729
+ * @param token - The webhook token (from createWebhook or stored)
730
+ * @param options - Optional channelId, guildId, name for display
731
+ */
732
+ static fromToken(client, webhookId, token, options) {
733
+ return new _Webhook(client, {
734
+ id: webhookId,
735
+ guild_id: options?.guildId ?? "",
736
+ channel_id: options?.channelId ?? "",
737
+ name: options?.name ?? "Webhook",
738
+ avatar: null,
739
+ token,
740
+ user: { id: "", username: "webhook", discriminator: "0" }
741
+ });
742
+ }
743
+ };
744
+
745
+ // src/structures/Invite.ts
746
+ import { Routes as Routes4 } from "@fluxerjs/types";
747
+ var Invite = class extends Base {
748
+ client;
749
+ code;
750
+ type;
751
+ guild;
752
+ channel;
753
+ inviter;
754
+ memberCount;
755
+ presenceCount;
756
+ expiresAt;
757
+ temporary;
758
+ createdAt;
759
+ uses;
760
+ maxUses;
761
+ maxAge;
762
+ /** @param data - API invite from GET /invites/{code}, channel/guild invite list, or gateway INVITE_CREATE */
763
+ constructor(client, data) {
764
+ super();
765
+ this.client = client;
766
+ this.code = data.code;
767
+ this.type = data.type;
768
+ this.guild = data.guild;
769
+ this.channel = data.channel;
770
+ this.inviter = data.inviter ? client.getOrCreateUser(data.inviter) : null;
771
+ this.memberCount = data.member_count ?? null;
772
+ this.presenceCount = data.presence_count ?? null;
773
+ this.expiresAt = data.expires_at ?? null;
774
+ this.temporary = data.temporary ?? null;
775
+ this.createdAt = data.created_at ?? null;
776
+ this.uses = data.uses ?? null;
777
+ this.maxUses = data.max_uses ?? null;
778
+ this.maxAge = data.max_age ?? null;
779
+ }
780
+ /** Full invite URL (https://fluxer.gg/{code} or instance-specific). */
781
+ get url() {
782
+ return `https://fluxer.gg/${this.code}`;
783
+ }
784
+ /**
785
+ * Resolve the guild from cache if available.
786
+ * @returns The guild, or null if not cached
787
+ */
788
+ getGuild() {
789
+ return this.guild?.id ? this.client.guilds.get(this.guild.id) ?? null : null;
790
+ }
791
+ /**
792
+ * Delete this invite.
793
+ * Requires Manage Guild or Create Instant Invite permission.
794
+ */
795
+ async delete() {
796
+ await this.client.rest.delete(Routes4.invite(this.code), { auth: true });
797
+ }
798
+ };
799
+
800
+ // src/structures/Channel.ts
801
+ var Channel = class _Channel extends Base {
802
+ /** Whether this channel has a send method (TextChannel, DMChannel). */
803
+ isTextBased() {
804
+ return "send" in this;
805
+ }
806
+ /** Whether this channel is a DM or Group DM. */
807
+ isDM() {
808
+ return this.type === ChannelType.DM || this.type === ChannelType.GroupDM;
809
+ }
810
+ /** Whether this channel is voice-based (VoiceChannel). */
811
+ isVoice() {
812
+ return "bitrate" in this;
813
+ }
814
+ isLink() {
815
+ return "url" in this;
816
+ }
817
+ /** Create a DM channel from API data (type DM or GroupDM). */
818
+ static createDM(client, data) {
819
+ return new DMChannel(client, data);
820
+ }
821
+ client;
822
+ id;
823
+ type;
824
+ /** Channel name. Guild channels and Group DMs have names; 1:1 DMs are typically null. */
825
+ name;
826
+ /** Channel icon hash (Group DMs). Null if none. */
827
+ icon;
828
+ /** ISO timestamp when the last message was pinned. Null if never pinned. */
829
+ lastPinTimestamp;
830
+ /** @param data - API channel from GET /channels/{id} or GET /guilds/{id}/channels */
831
+ constructor(client, data) {
832
+ super();
833
+ this.client = client;
834
+ this.id = data.id;
835
+ this.type = data.type;
836
+ this.name = data.name ?? null;
837
+ this.icon = data.icon ?? null;
838
+ this.lastPinTimestamp = data.last_pin_timestamp ?? null;
839
+ }
840
+ /**
841
+ * Create the appropriate channel subclass from API data.
842
+ * @param client - The client instance
843
+ * @param data - Channel data from the API
844
+ */
845
+ static from(client, data) {
846
+ const type = data.type ?? 0;
847
+ if (type === ChannelType.GuildText) return new TextChannel(client, data);
848
+ if (type === ChannelType.GuildCategory) return new CategoryChannel(client, data);
849
+ if (type === ChannelType.GuildVoice) return new VoiceChannel(client, data);
850
+ if (type === ChannelType.GuildLink || type === ChannelType.GuildLinkExtended)
851
+ return new LinkChannel(client, data);
852
+ return new GuildChannel(client, data);
853
+ }
854
+ /**
855
+ * Create a channel from API data, including DM and GroupDM.
856
+ * Used by ChannelManager.fetch() for GET /channels/{id}.
857
+ */
858
+ static fromOrCreate(client, data) {
859
+ const type = data.type ?? 0;
860
+ if (type === ChannelType.DM || type === ChannelType.GroupDM)
861
+ return _Channel.createDM(client, data);
862
+ return _Channel.from(client, data);
863
+ }
864
+ /**
865
+ * Bulk delete messages. Requires Manage Messages permission.
866
+ * @param messageIds - Array of message IDs to delete (2–100)
867
+ */
868
+ async bulkDeleteMessages(messageIds) {
869
+ await this.client.rest.post(Routes5.channelBulkDelete(this.id), {
870
+ body: { message_ids: messageIds },
871
+ auth: true
872
+ });
873
+ }
874
+ /**
875
+ * Send a typing indicator to the channel. Lasts ~10 seconds.
876
+ */
877
+ async sendTyping() {
878
+ await this.client.rest.post(Routes5.channelTyping(this.id), { auth: true });
879
+ }
880
+ /**
881
+ * Whether the bot can send messages in this channel.
882
+ * For DMs: always true (when the channel exists).
883
+ * For guild channels: checks ViewChannel and SendMessages permissions via guild.members.me.
884
+ */
885
+ canSendMessage() {
886
+ if (this.isDM()) return true;
887
+ return false;
888
+ }
889
+ };
890
+ var GuildChannel = class extends Channel {
891
+ guildId;
892
+ name;
893
+ position;
894
+ parentId;
895
+ /** Permission overwrites for roles and members. */
896
+ permissionOverwrites;
897
+ constructor(client, data) {
898
+ super(client, data);
899
+ this.guildId = data.guild_id ?? "";
900
+ this.name = data.name ?? null;
901
+ this.position = data.position;
902
+ this.parentId = data.parent_id ?? null;
903
+ this.permissionOverwrites = data.permission_overwrites ?? [];
904
+ }
905
+ /**
906
+ * Create a webhook in this channel.
907
+ * @param options - Webhook name and optional avatar URL
908
+ * @returns The webhook with token (required for send()). Requires Manage Webhooks permission.
909
+ */
910
+ async createWebhook(options) {
911
+ const data = await this.client.rest.post(Routes5.channelWebhooks(this.id), {
912
+ body: options,
913
+ auth: true
914
+ });
915
+ return new Webhook(this.client, data);
916
+ }
917
+ /**
918
+ * Fetch all webhooks in this channel.
919
+ * @returns Webhooks (includes token when listing from channel; can send via send())
920
+ */
921
+ async fetchWebhooks() {
922
+ const data = await this.client.rest.get(Routes5.channelWebhooks(this.id));
923
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
924
+ return list.map((w) => new Webhook(this.client, w));
925
+ }
926
+ /**
927
+ * Create an invite for this channel.
928
+ * @param options - max_uses (0–100), max_age (0–604800 seconds), unique, temporary
929
+ * Requires Create Instant Invite permission.
930
+ */
931
+ async createInvite(options) {
932
+ const body = {};
933
+ if (options?.max_uses != null) body.max_uses = options.max_uses;
934
+ if (options?.max_age != null) body.max_age = options.max_age;
935
+ if (options?.unique != null) body.unique = options.unique;
936
+ if (options?.temporary != null) body.temporary = options.temporary;
937
+ const data = await this.client.rest.post(Routes5.channelInvites(this.id), {
938
+ body: Object.keys(body).length ? body : void 0,
939
+ auth: true
940
+ });
941
+ return new Invite(this.client, data);
942
+ }
943
+ /**
944
+ * Fetch invites for this channel.
945
+ * Requires Manage Channel permission.
946
+ */
947
+ async fetchInvites() {
948
+ const data = await this.client.rest.get(Routes5.channelInvites(this.id));
949
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
950
+ return list.map((i) => new Invite(this.client, i));
951
+ }
952
+ /**
953
+ * Set or update a permission overwrite. PUT /channels/{id}/permissions/{overwriteId}.
954
+ * @param overwriteId - Role or member ID
955
+ * @param options - type (0=role, 1=member), allow, deny (permission bitfields)
956
+ */
957
+ async editPermission(overwriteId, options) {
958
+ await this.client.rest.put(Routes5.channelPermission(this.id, overwriteId), {
959
+ body: options,
960
+ auth: true
961
+ });
962
+ const idx = this.permissionOverwrites.findIndex((o) => o.id === overwriteId);
963
+ const entry = {
964
+ id: overwriteId,
965
+ type: options.type,
966
+ allow: options.allow ?? "0",
967
+ deny: options.deny ?? "0"
968
+ };
969
+ if (idx >= 0) this.permissionOverwrites[idx] = entry;
970
+ else this.permissionOverwrites.push(entry);
971
+ }
972
+ /**
973
+ * Whether the bot can send messages in this channel.
974
+ * Checks ViewChannel and SendMessages via guild.members.me permissions.
975
+ * Returns false if guild or bot member not cached.
976
+ */
977
+ canSendMessage() {
978
+ const guild = this.client.guilds.get(this.guildId);
979
+ if (!guild) return false;
980
+ const me = guild.members.me;
981
+ if (!me) return false;
982
+ const perms = me.permissionsIn(this);
983
+ return perms.has(PermissionFlags.ViewChannel) && perms.has(PermissionFlags.SendMessages);
984
+ }
985
+ /**
986
+ * Send a message to this guild channel.
987
+ * Works for text and announcement channels. Voice/category/link channels will fail at the API.
988
+ */
989
+ async send(options) {
990
+ const opts = typeof options === "string" ? { content: options } : options;
991
+ const body = buildSendBody(options);
992
+ const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
993
+ const postOptions = files?.length ? { body, files } : { body };
994
+ const data = await this.client.rest.post(Routes5.channelMessages(this.id), postOptions);
995
+ return new Message(this.client, data);
996
+ }
997
+ /**
998
+ * Remove a permission overwrite. DELETE /channels/{id}/permissions/{overwriteId}.
999
+ */
1000
+ async deletePermission(overwriteId) {
1001
+ await this.client.rest.delete(Routes5.channelPermission(this.id, overwriteId), { auth: true });
1002
+ const idx = this.permissionOverwrites.findIndex((o) => o.id === overwriteId);
1003
+ if (idx >= 0) this.permissionOverwrites.splice(idx, 1);
1004
+ }
1005
+ /**
1006
+ * Edit this channel. PATCH /channels/{id}.
1007
+ * Requires Manage Channel permission.
1008
+ */
1009
+ async edit(options) {
1010
+ const data = await this.client.rest.patch(Routes5.channel(this.id), {
1011
+ body: options,
1012
+ auth: true
1013
+ });
1014
+ this.name = data.name ?? this.name;
1015
+ this.parentId = data.parent_id ?? this.parentId;
1016
+ this.permissionOverwrites = data.permission_overwrites ?? this.permissionOverwrites;
1017
+ const self = this;
1018
+ if ("topic" in self && "topic" in data) self.topic = data.topic ?? null;
1019
+ if ("nsfw" in self && "nsfw" in data) self.nsfw = data.nsfw ?? false;
1020
+ if ("rate_limit_per_user" in data) self.rateLimitPerUser = data.rate_limit_per_user ?? 0;
1021
+ if ("bitrate" in self && "bitrate" in data) self.bitrate = data.bitrate ?? null;
1022
+ if ("user_limit" in data) self.userLimit = data.user_limit ?? null;
1023
+ if ("rtc_region" in data) self.rtcRegion = data.rtc_region ?? null;
1024
+ return this;
1025
+ }
1026
+ /**
1027
+ * Delete this channel. Requires Manage Channel permission.
1028
+ * @param options - silent: if true, does not send a system message (default false)
1029
+ */
1030
+ async delete(options) {
1031
+ const url = Routes5.channel(this.id) + (options?.silent ? "?silent=true" : "");
1032
+ await this.client.rest.delete(url, { auth: true });
1033
+ this.client.channels.delete(this.id);
1034
+ const guild = this.client.guilds.get(this.guildId);
1035
+ if (guild) guild.channels.delete(this.id);
1036
+ }
1037
+ };
1038
+ var TextChannel = class extends GuildChannel {
1039
+ topic;
1040
+ nsfw;
1041
+ rateLimitPerUser;
1042
+ lastMessageId;
1043
+ constructor(client, data) {
1044
+ super(client, data);
1045
+ this.topic = data.topic ?? null;
1046
+ this.nsfw = data.nsfw ?? false;
1047
+ this.rateLimitPerUser = data.rate_limit_per_user ?? 0;
1048
+ this.lastMessageId = data.last_message_id ?? null;
1049
+ }
1050
+ /**
1051
+ * Send a message to this channel.
1052
+ * @param options - Text content or object with content, embeds, and/or files
1053
+ */
1054
+ async send(options) {
1055
+ const opts = typeof options === "string" ? { content: options } : options;
1056
+ const body = buildSendBody(options);
1057
+ const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1058
+ const postOptions = files?.length ? { body, files } : { body };
1059
+ const data = await this.client.rest.post(Routes5.channelMessages(this.id), postOptions);
1060
+ return new Message(this.client, data);
1061
+ }
1062
+ /** Message manager for this channel. Use channel.messages.fetch(messageId). */
1063
+ get messages() {
1064
+ return new MessageManager(this.client, this.id);
1065
+ }
1066
+ /**
1067
+ * Create a message collector for this channel.
1068
+ * Collects messages matching the filter until time expires or max is reached.
1069
+ * @param options - Filter, time (ms), and max count
1070
+ * @example
1071
+ * const collector = channel.createMessageCollector({ filter: m => m.author.id === userId, time: 10000 });
1072
+ * collector.on('collect', m => console.log(m.content));
1073
+ * collector.on('end', (collected, reason) => { ... });
1074
+ */
1075
+ createMessageCollector(options) {
1076
+ return new MessageCollector(this.client, this.id, options);
1077
+ }
1078
+ /**
1079
+ * Fetch pinned messages in this channel.
1080
+ * @returns Pinned messages
1081
+ */
1082
+ async fetchPinnedMessages() {
1083
+ const data = await this.client.rest.get(Routes5.channelPins(this.id));
1084
+ const list = Array.isArray(data) ? data : data?.items ?? [];
1085
+ return list.map((item) => {
1086
+ const msg = typeof item === "object" && item && "message" in item ? item.message : item;
1087
+ return new Message(this.client, msg);
1088
+ });
1089
+ }
1090
+ /**
1091
+ * Fetch a message by ID from this channel.
1092
+ * @param messageId - Snowflake of the message
1093
+ * @returns The message, or null if not found
1094
+ * @deprecated Use channel.messages.fetch(messageId) instead.
1095
+ */
1096
+ async fetchMessage(messageId) {
1097
+ emitDeprecationWarning(
1098
+ "Channel.fetchMessage()",
1099
+ "Use channel.messages.fetch(messageId) instead."
1100
+ );
1101
+ return this.client.channels.fetchMessage(this.id, messageId);
1102
+ }
1103
+ };
1104
+ var CategoryChannel = class extends GuildChannel {
1105
+ };
1106
+ var VoiceChannel = class extends GuildChannel {
1107
+ bitrate;
1108
+ userLimit;
1109
+ rtcRegion;
1110
+ constructor(client, data) {
1111
+ super(client, data);
1112
+ this.bitrate = data.bitrate ?? null;
1113
+ this.userLimit = data.user_limit ?? null;
1114
+ this.rtcRegion = data.rtc_region ?? null;
1115
+ }
1116
+ };
1117
+ var LinkChannel = class extends GuildChannel {
1118
+ url;
1119
+ constructor(client, data) {
1120
+ super(client, data);
1121
+ this.url = data.url ?? null;
1122
+ }
1123
+ };
1124
+ var DMChannel = class extends Channel {
1125
+ lastMessageId;
1126
+ /** Group DM creator ID. Null for 1:1 DMs. */
1127
+ ownerId;
1128
+ /** Group DM recipients as User objects. Empty for 1:1 DMs. */
1129
+ recipients;
1130
+ /** Group DM member display names (userId -> nickname). */
1131
+ nicks;
1132
+ constructor(client, data) {
1133
+ super(client, data);
1134
+ this.lastMessageId = data.last_message_id ?? null;
1135
+ this.ownerId = data.owner_id ?? null;
1136
+ this.recipients = (data.recipients ?? []).map(
1137
+ (u) => client.getOrCreateUser(u)
1138
+ );
1139
+ this.nicks = data.nicks ?? {};
1140
+ }
1141
+ /**
1142
+ * Send a message to this DM channel.
1143
+ * @param options - Text content or object with content, embeds, and/or files
1144
+ */
1145
+ async send(options) {
1146
+ const opts = typeof options === "string" ? { content: options } : options;
1147
+ const body = buildSendBody(options);
1148
+ const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1149
+ const postOptions = files?.length ? { body, files } : { body };
1150
+ const data = await this.client.rest.post(Routes5.channelMessages(this.id), postOptions);
1151
+ return new Message(this.client, data);
1152
+ }
1153
+ /** Message manager for this channel. Use channel.messages.fetch(messageId). */
1154
+ get messages() {
1155
+ return new MessageManager(this.client, this.id);
1156
+ }
1157
+ /**
1158
+ * Create a message collector for this DM channel.
1159
+ * @param options - Filter, time (ms), and max count
1160
+ */
1161
+ createMessageCollector(options) {
1162
+ return new MessageCollector(this.client, this.id, options);
1163
+ }
1164
+ /**
1165
+ * Fetch pinned messages in this DM channel.
1166
+ * @returns Pinned messages
1167
+ */
1168
+ async fetchPinnedMessages() {
1169
+ const data = await this.client.rest.get(Routes5.channelPins(this.id));
1170
+ const list = Array.isArray(data) ? data : data?.items ?? [];
1171
+ return list.map((item) => {
1172
+ const msg = typeof item === "object" && item && "message" in item ? item.message : item;
1173
+ return new Message(this.client, msg);
1174
+ });
1175
+ }
1176
+ /**
1177
+ * Fetch a message by ID from this DM channel.
1178
+ * @param messageId - Snowflake of the message
1179
+ * @returns The message, or null if not found
1180
+ * @deprecated Use channel.messages.fetch(messageId) instead.
1181
+ */
1182
+ async fetchMessage(messageId) {
1183
+ emitDeprecationWarning(
1184
+ "Channel.fetchMessage()",
1185
+ "Use channel.messages.fetch(messageId) instead."
1186
+ );
1187
+ return this.client.channels.fetchMessage(this.id, messageId);
1188
+ }
1189
+ /**
1190
+ * Add a recipient to this Group DM. Requires Group DM (type GroupDM).
1191
+ * PUT /channels/{id}/recipients/{userId}.
1192
+ */
1193
+ async addRecipient(userId) {
1194
+ await this.client.rest.put(Routes5.channelRecipient(this.id, userId), { auth: true });
1195
+ const user = this.client.users.get(userId) ?? await this.client.users.fetch(userId);
1196
+ if (user) this.recipients.push(user);
1197
+ }
1198
+ /**
1199
+ * Remove a recipient from this Group DM. Requires Group DM (type GroupDM).
1200
+ * DELETE /channels/{id}/recipients/{userId}.
1201
+ * @param options - silent: if true, does not send a system message (default false)
1202
+ */
1203
+ async removeRecipient(userId, options) {
1204
+ const url = Routes5.channelRecipient(this.id, userId) + (options?.silent ? "?silent=true" : "");
1205
+ await this.client.rest.delete(url, { auth: true });
1206
+ const idx = this.recipients.findIndex((u) => u.id === userId);
1207
+ if (idx >= 0) this.recipients.splice(idx, 1);
1208
+ }
1209
+ };
1210
+
1211
+ // src/client/ChannelManager.ts
1212
+ var ChannelManager = class extends Collection4 {
1213
+ constructor(client) {
1214
+ super();
1215
+ this.client = client;
1216
+ this.maxSize = client.options?.cache?.channels ?? 0;
1217
+ }
1218
+ maxSize;
1219
+ set(key, value) {
1220
+ if (this.maxSize > 0 && this.size >= this.maxSize && !this.has(key)) {
1221
+ const firstKey = this.keys().next().value;
1222
+ if (firstKey !== void 0) this.delete(firstKey);
1223
+ }
1224
+ return super.set(key, value);
1225
+ }
1226
+ /**
1227
+ * Get a channel from cache or fetch from the API if not present.
1228
+ * Convenience helper to avoid repeating `client.channels.get(id) ?? (await client.channels.fetch(id))`.
1229
+ * @param channelId - Snowflake of the channel
1230
+ * @returns The channel
1231
+ * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1232
+ * @example
1233
+ * const channel = await client.channels.resolve(message.channelId);
1234
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1235
+ */
1236
+ async resolve(channelId) {
1237
+ return this.get(channelId) ?? this.fetch(channelId);
1238
+ }
1239
+ /**
1240
+ * Fetch a channel by ID from the API (or return from cache if present).
1241
+ * @param channelId - Snowflake of the channel
1242
+ * @returns The channel
1243
+ * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1244
+ * @example
1245
+ * const channel = await client.channels.fetch(channelId);
1246
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1247
+ */
1248
+ async fetch(channelId) {
1249
+ const cached = this.get(channelId);
1250
+ if (cached) return cached;
1251
+ try {
1252
+ const data = await this.client.rest.get(Routes6.channel(channelId));
1253
+ const channel = Channel.fromOrCreate(this.client, data);
1254
+ if (!channel) {
1255
+ throw new FluxerError("Channel data invalid or unsupported type", {
1256
+ code: ErrorCodes.ChannelNotFound
1257
+ });
1258
+ }
1259
+ this.set(channel.id, channel);
1260
+ if ("guildId" in channel) {
1261
+ const guild = this.client.guilds.get(channel.guildId);
1262
+ if (guild) guild.channels.set(channel.id, channel);
1263
+ }
1264
+ return channel;
1265
+ } catch (err) {
1266
+ if (err instanceof RateLimitError2) throw err;
1267
+ if (err instanceof FluxerAPIError2 && err.statusCode === 404) {
1268
+ throw new FluxerError(`Channel ${channelId} not found`, {
1269
+ code: ErrorCodes.ChannelNotFound,
1270
+ cause: err
1271
+ });
1272
+ }
1273
+ throw err instanceof FluxerError ? err : new FluxerError(String(err), { cause: err });
1274
+ }
1275
+ }
1276
+ /**
1277
+ * Fetch a message by ID from the API.
1278
+ * @param channelId - Snowflake of the channel
1279
+ * @param messageId - Snowflake of the message
1280
+ * @returns The message
1281
+ * @throws FluxerError with MESSAGE_NOT_FOUND if the message does not exist
1282
+ * @deprecated Use channel.messages.fetch(messageId). Prefer (await client.channels.resolve(channelId))?.messages?.fetch(messageId).
1283
+ * @example
1284
+ * const channel = await client.channels.resolve(channelId);
1285
+ * const message = await channel?.messages?.fetch(messageId);
1286
+ */
1287
+ async fetchMessage(channelId, messageId) {
1288
+ emitDeprecationWarning2(
1289
+ "ChannelManager.fetchMessage()",
1290
+ "Use channel.messages.fetch(messageId). Prefer (await client.channels.resolve(channelId))?.messages?.fetch(messageId)."
1291
+ );
1292
+ try {
1293
+ const data = await this.client.rest.get(
1294
+ Routes6.channelMessage(channelId, messageId)
1295
+ );
1296
+ return new Message(this.client, data);
1297
+ } catch (err) {
1298
+ if (err instanceof RateLimitError2) throw err;
1299
+ if (err instanceof FluxerAPIError2 && err.statusCode === 404) {
1300
+ throw new FluxerError(`Message ${messageId} not found in channel ${channelId}`, {
1301
+ code: ErrorCodes.MessageNotFound,
1302
+ cause: err
1303
+ });
1304
+ }
1305
+ throw err instanceof FluxerError ? err : new FluxerError(String(err), { cause: err });
1306
+ }
1307
+ }
1308
+ /**
1309
+ * Send a message to a channel by ID. Works even when the channel is not cached.
1310
+ * Skips the fetch when you only need to send.
1311
+ * @param channelId - Snowflake of the channel (text channel or DM)
1312
+ * @param payload - Text content or object with content, embeds, and/or files
1313
+ * @returns The created message
1314
+ * @example
1315
+ * await client.channels.send(logChannelId, 'User joined!');
1316
+ * await client.channels.send(channelId, { embeds: [embed] });
1317
+ * await client.channels.send(channelId, { content: 'Report', files: [{ name: 'log.txt', data }] });
1318
+ */
1319
+ async send(channelId, payload) {
1320
+ const opts = typeof payload === "string" ? { content: payload } : payload;
1321
+ const body = buildSendBody(payload);
1322
+ const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1323
+ const postOptions = files?.length ? { body, files } : { body };
1324
+ const data = await this.client.rest.post(Routes6.channelMessages(channelId), postOptions);
1325
+ return new Message(this.client, data);
1326
+ }
1327
+ };
1328
+
1329
+ // src/client/GuildManager.ts
1330
+ import { Collection as Collection8 } from "@fluxerjs/collection";
1331
+ import { Routes as Routes15 } from "@fluxerjs/types";
1332
+
1333
+ // src/structures/Guild.ts
1334
+ import {
1335
+ parseRoleMention,
1336
+ resolvePermissionsToBitfield as resolvePermissionsToBitfield2
1337
+ } from "@fluxerjs/util";
1338
+ import { FluxerAPIError as FluxerAPIError3 } from "@fluxerjs/rest";
1339
+ import { Collection as Collection7 } from "@fluxerjs/collection";
1340
+
1341
+ // src/client/GuildMemberManager.ts
1342
+ import { Collection as Collection6 } from "@fluxerjs/collection";
1343
+ import { Routes as Routes9 } from "@fluxerjs/types";
1344
+
1345
+ // src/structures/GuildMember.ts
1346
+ import { PermissionFlagsMap } from "@fluxerjs/util";
1347
+ import { Routes as Routes8 } from "@fluxerjs/types";
1348
+
1349
+ // src/util/permissions.ts
1350
+ import { OverwriteType } from "@fluxerjs/types";
1351
+ import { ALL_PERMISSIONS_BIGINT } from "@fluxerjs/util";
1352
+ function computePermissions(basePermissions, overwrites, memberRoles, memberId, isOwner) {
1353
+ if (isOwner) return ALL_PERMISSIONS_BIGINT;
1354
+ let perms = basePermissions;
1355
+ for (const overwrite of overwrites ?? []) {
1356
+ const applies = overwrite.type === OverwriteType.Role && memberRoles.includes(overwrite.id) || overwrite.type === OverwriteType.Member && overwrite.id === memberId;
1357
+ if (!applies) continue;
1358
+ const allow = BigInt(overwrite.allow || "0");
1359
+ const deny = BigInt(overwrite.deny || "0");
1360
+ perms = perms & ~deny | allow;
1361
+ }
1362
+ return perms;
1363
+ }
1364
+ function hasPermission(bitfield, permission) {
1365
+ const Administrator = 1n << 3n;
1366
+ if ((bitfield & Administrator) !== 0n) return true;
1367
+ return (bitfield & permission) === permission;
1368
+ }
1369
+
1370
+ // src/structures/GuildMemberRoleManager.ts
1371
+ import { Collection as Collection5 } from "@fluxerjs/collection";
1372
+ import { Routes as Routes7 } from "@fluxerjs/types";
1373
+ var GuildMemberRoleManager = class {
1374
+ constructor(member, initialRoleIds = []) {
1375
+ this.member = member;
1376
+ this._roleIds = [...initialRoleIds];
1377
+ }
1378
+ _roleIds = [];
1379
+ /** Role IDs for this member. Used by permissions; prefer cache for Role objects. */
1380
+ get roleIds() {
1381
+ return this._roleIds;
1382
+ }
1383
+ /** Check if the member has a role. Discord.js parity: member.roles.cache.has(roleId) */
1384
+ has(roleOrId) {
1385
+ return this._roleIds.includes(this._resolveId(roleOrId));
1386
+ }
1387
+ /**
1388
+ * Collection of Role objects for this member's roles (from guild.roles).
1389
+ * Discord.js parity: member.roles.cache
1390
+ * @discordJsCompat https://discord.js.org/docs/packages/discord.js/main/GuildMemberRoleManager
1391
+ */
1392
+ get cache() {
1393
+ const coll = new Collection5();
1394
+ for (const id of this._roleIds) {
1395
+ const role = this.member.guild.roles.get(id);
1396
+ if (role) coll.set(id, role);
1397
+ }
1398
+ return coll;
1399
+ }
1400
+ /** Resolve role ID from RoleResolvable. */
1401
+ _resolveId(roleOrId) {
1402
+ return typeof roleOrId === "string" ? roleOrId : roleOrId.id;
1403
+ }
1404
+ /**
1405
+ * Add a role to this member.
1406
+ * Discord.js parity: member.roles.add(roleId)
1407
+ * Requires Manage Roles permission.
1408
+ * @discordJsCompat https://discord.js.org/docs/packages/discord.js/main/GuildMemberRoleManager
1409
+ */
1410
+ async add(roleOrId) {
1411
+ const roleId = this._resolveId(roleOrId);
1412
+ if (this._roleIds.includes(roleId)) return;
1413
+ await this.member.client.rest.put(
1414
+ Routes7.guildMemberRole(this.member.guild.id, this.member.id, roleId)
1415
+ );
1416
+ this._roleIds.push(roleId);
1417
+ }
1418
+ /**
1419
+ * Remove a role from this member.
1420
+ * Discord.js parity: member.roles.remove(roleId)
1421
+ * Requires Manage Roles permission.
1422
+ * @discordJsCompat https://discord.js.org/docs/packages/discord.js/main/GuildMemberRoleManager
1423
+ */
1424
+ async remove(roleOrId) {
1425
+ const roleId = this._resolveId(roleOrId);
1426
+ const idx = this._roleIds.indexOf(roleId);
1427
+ if (idx === -1) return;
1428
+ await this.member.client.rest.delete(
1429
+ Routes7.guildMemberRole(this.member.guild.id, this.member.id, roleId)
1430
+ );
1431
+ this._roleIds.splice(idx, 1);
1432
+ }
1433
+ /**
1434
+ * Replace all roles for this member. PATCH /guilds/{id}/members/{userId}
1435
+ * Discord.js parity: member.roles.set(roleIds)
1436
+ * Requires Manage Roles permission.
1437
+ * @discordJsCompat https://discord.js.org/docs/packages/discord.js/main/GuildMemberRoleManager
1438
+ */
1439
+ async set(roleIds) {
1440
+ const data = await this.member.client.rest.patch(
1441
+ Routes7.guildMember(this.member.guild.id, this.member.id),
1442
+ { body: { roles: roleIds }, auth: true }
1443
+ );
1444
+ this._roleIds = data.roles ? [...data.roles] : [];
1445
+ }
1446
+ /**
1447
+ * Update internal role IDs from API response. Called by GuildMember.edit().
1448
+ * @internal
1449
+ */
1450
+ _patch(roleIds) {
1451
+ this._roleIds = [...roleIds];
1452
+ }
1453
+ };
1454
+
1455
+ // src/structures/GuildMember.ts
1456
+ var GuildMember = class extends Base {
1457
+ client;
1458
+ id;
1459
+ user;
1460
+ guild;
1461
+ nick;
1462
+ /**
1463
+ * Role manager with add/remove/set and cache. Discord.js parity: member.roles.add(), member.roles.cache
1464
+ * @discordJsCompat https://discord.js.org/docs/packages/discord.js/main/GuildMemberRoleManager
1465
+ */
1466
+ roles;
1467
+ joinedAt;
1468
+ communicationDisabledUntil;
1469
+ mute;
1470
+ deaf;
1471
+ avatar;
1472
+ banner;
1473
+ accentColor;
1474
+ profileFlags;
1475
+ /** @param data - API guild member from GET /guilds/{id}/members or GET /guilds/{id}/members/{user_id} */
1476
+ constructor(client, data, guild) {
1477
+ super();
1478
+ this.client = client;
1479
+ this.user = client.getOrCreateUser(data.user);
1480
+ this.id = data.user.id;
1481
+ this.guild = guild;
1482
+ this.nick = data.nick ?? null;
1483
+ this.roles = new GuildMemberRoleManager(this, data.roles ?? []);
1484
+ this.joinedAt = new Date(data.joined_at);
1485
+ this.communicationDisabledUntil = data.communication_disabled_until ? new Date(data.communication_disabled_until) : null;
1486
+ this.mute = data.mute ?? false;
1487
+ this.deaf = data.deaf ?? false;
1488
+ this.avatar = data.avatar ?? null;
1489
+ this.banner = data.banner ?? null;
1490
+ this.accentColor = data.accent_color ?? null;
1491
+ this.profileFlags = data.profile_flags ?? null;
1492
+ }
1493
+ /** Nickname, or global name, or username. */
1494
+ get displayName() {
1495
+ return this.nick ?? this.user.globalName ?? this.user.username;
1496
+ }
1497
+ /**
1498
+ * Get the guild-specific avatar URL for this member.
1499
+ * Returns null if the member has no guild avatar (use displayAvatarURL for fallback).
1500
+ */
1501
+ avatarURL(options) {
1502
+ return cdnMemberAvatarURL(this.guild.id, this.id, this.avatar, options);
1503
+ }
1504
+ /**
1505
+ * Get the avatar URL to display for this member.
1506
+ * Uses guild-specific avatar if set, otherwise falls back to the user's avatar.
1507
+ */
1508
+ displayAvatarURL(options) {
1509
+ return this.avatarURL(options) ?? this.user.displayAvatarURL(options);
1510
+ }
1511
+ /**
1512
+ * Get the guild-specific banner URL for this member.
1513
+ * Returns null if the member has no guild banner.
1514
+ */
1515
+ bannerURL(options) {
1516
+ return cdnMemberBannerURL(this.guild.id, this.id, this.banner, options);
1517
+ }
1518
+ /**
1519
+ * Add a role to this member.
1520
+ * Prefer member.roles.add(roleId) for Discord.js parity.
1521
+ * @param roleId - The role ID to add
1522
+ * Requires Manage Roles permission.
1523
+ */
1524
+ async addRole(roleId) {
1525
+ await this.roles.add(roleId);
1526
+ }
1527
+ /**
1528
+ * Remove a role from this member.
1529
+ * Prefer member.roles.remove(roleId) for Discord.js parity.
1530
+ * @param roleId - The role ID to remove
1531
+ * Requires Manage Roles permission.
1532
+ */
1533
+ async removeRole(roleId) {
1534
+ await this.roles.remove(roleId);
1535
+ }
1536
+ /**
1537
+ * Edit this guild member. PATCH /guilds/{id}/members/{userId} or /members/@me for the bot.
1538
+ * For @me: nick, avatar, banner, bio, pronouns, accent_color, profile_flags, mute, deaf,
1539
+ * communication_disabled_until, timeout_reason, channel_id, connection_id.
1540
+ * For other members: same plus roles (array of role IDs).
1541
+ */
1542
+ async edit(options) {
1543
+ const isMe = this.client.user?.id === this.id;
1544
+ const route = isMe ? `/guilds/${this.guild.id}/members/@me` : Routes8.guildMember(this.guild.id, this.id);
1545
+ const data = await this.client.rest.patch(route, {
1546
+ body: options,
1547
+ auth: true
1548
+ });
1549
+ this.nick = data.nick ?? this.nick;
1550
+ if (data.roles) this.roles._patch(data.roles);
1551
+ if (data.communication_disabled_until != null) {
1552
+ this.communicationDisabledUntil = data.communication_disabled_until ? new Date(data.communication_disabled_until) : null;
1553
+ }
1554
+ return this;
1555
+ }
1556
+ /**
1557
+ * Get the member's guild-level permissions (from roles only, no channel overwrites).
1558
+ * Use this for server-wide permission checks (e.g. ban, kick, manage roles).
1559
+ * @returns Object with has(permission) to check specific permissions
1560
+ * @example
1561
+ * const perms = member.permissions;
1562
+ * if (perms.has(PermissionFlags.BanMembers)) { ... }
1563
+ */
1564
+ get permissions() {
1565
+ const base = this._computeBasePermissions();
1566
+ const ownerId = this.guild.ownerId;
1567
+ const isOwner = ownerId != null && ownerId !== "" && String(ownerId) === String(this.id);
1568
+ const perms = computePermissions(base, [], [], this.id, isOwner);
1569
+ return {
1570
+ has(permission) {
1571
+ const perm = typeof permission === "number" ? permission : PermissionFlagsMap[String(permission)];
1572
+ if (perm === void 0) return false;
1573
+ return hasPermission(perms, BigInt(perm));
1574
+ }
1575
+ };
1576
+ }
1577
+ /**
1578
+ * Compute the member's effective permissions in a guild channel.
1579
+ * Applies role permissions and channel overwrites.
1580
+ * @param channel - The guild channel to check permissions for
1581
+ * @returns Object with has(permission) to check specific permissions
1582
+ * @example
1583
+ * const perms = member.permissionsIn(channel);
1584
+ * if (perms.has(PermissionFlags.SendMessages)) { ... }
1585
+ */
1586
+ permissionsIn(channel) {
1587
+ const base = this._computeBasePermissions();
1588
+ const ownerId = this.guild.ownerId;
1589
+ const isOwner = ownerId != null && ownerId !== "" && String(ownerId) === String(this.id);
1590
+ const perms = computePermissions(
1591
+ base,
1592
+ channel.permissionOverwrites,
1593
+ [...this.roles.roleIds],
1594
+ this.id,
1595
+ isOwner
1596
+ );
1597
+ return {
1598
+ has(permission) {
1599
+ const perm = typeof permission === "number" ? permission : PermissionFlagsMap[String(permission)];
1600
+ if (perm === void 0) return false;
1601
+ return hasPermission(perms, BigInt(perm));
1602
+ }
1603
+ };
1604
+ }
1605
+ _computeBasePermissions() {
1606
+ let base = 0n;
1607
+ const everyone = this.guild.roles.get(this.guild.id);
1608
+ if (everyone) base |= BigInt(everyone.permissions);
1609
+ for (const roleId of this.roles.roleIds) {
1610
+ if (roleId === this.guild.id) continue;
1611
+ const role = this.guild.roles.get(roleId);
1612
+ if (role) base |= BigInt(role.permissions);
1613
+ }
1614
+ return base;
1615
+ }
1616
+ };
1617
+
1618
+ // src/client/GuildMemberManager.ts
1619
+ var GuildMemberManager = class extends Collection6 {
1620
+ constructor(guild) {
1621
+ super();
1622
+ this.guild = guild;
1623
+ }
1624
+ /**
1625
+ * Get a guild member from cache or fetch from the API if not present.
1626
+ * Convenience helper to avoid repeating `guild.members.get(userId) ?? (await guild.fetchMember(userId))`.
1627
+ * @param userId - Snowflake of the user
1628
+ * @returns The guild member
1629
+ * @throws FluxerError with MEMBER_NOT_FOUND if user is not in the guild (404)
1630
+ * @example
1631
+ * const member = await guild.members.resolve(userId);
1632
+ * console.log(member.displayName);
1633
+ */
1634
+ async resolve(userId) {
1635
+ return this.get(userId) ?? this.guild.fetchMember(userId);
1636
+ }
1637
+ /**
1638
+ * The current bot user as a GuildMember in this guild.
1639
+ * Returns null if the bot's member is not cached or client.user is null.
1640
+ * Use fetchMe() to load the bot's member when not cached.
1641
+ *
1642
+ * @discordJsCompat https://discord.js.org/docs/packages/discord.js/main/GuildMemberManager
1643
+ * @example
1644
+ * const perms = guild.members.me?.permissions;
1645
+ * if (perms?.has(PermissionFlags.BanMembers)) { ... }
1646
+ */
1647
+ get me() {
1648
+ const userId = this.guild.client.user?.id;
1649
+ return userId ? this.get(userId) ?? null : null;
1650
+ }
1651
+ /**
1652
+ * Fetch the current bot user as a GuildMember in this guild.
1653
+ * Caches the result in guild.members.
1654
+ *
1655
+ * @throws Error if client.user is null (client not ready)
1656
+ * @example
1657
+ * const me = await guild.members.fetchMe();
1658
+ * console.log(me.displayName);
1659
+ */
1660
+ async fetchMe() {
1661
+ const userId = this.guild.client.user?.id;
1662
+ if (!userId) {
1663
+ throw new Error("Cannot fetch me: client.user is null (client not ready)");
1664
+ }
1665
+ return this.guild.fetchMember(userId);
1666
+ }
1667
+ /**
1668
+ * Fetch guild members with pagination. GET /guilds/{id}/members.
1669
+ * @param options - limit (1-1000), after (user ID for pagination)
1670
+ * @returns Array of GuildMember objects (cached in guild.members)
1671
+ */
1672
+ async fetch(options) {
1673
+ const params = new URLSearchParams();
1674
+ if (options?.limit != null) params.set("limit", String(options.limit));
1675
+ if (options?.after) params.set("after", options.after);
1676
+ const qs = params.toString();
1677
+ const url = Routes9.guildMembers(this.guild.id) + (qs ? `?${qs}` : "");
1678
+ const data = await this.guild.client.rest.get(url, { auth: true });
1679
+ const list = Array.isArray(data) ? data : data?.members ?? [];
1680
+ const members = [];
1681
+ for (const m of list) {
1682
+ const member = new GuildMember(
1683
+ this.guild.client,
1684
+ { ...m, guild_id: this.guild.id },
1685
+ this.guild
1686
+ );
1687
+ this.set(member.id, member);
1688
+ members.push(member);
1689
+ }
1690
+ return members;
1691
+ }
1692
+ };
1693
+
1694
+ // src/structures/Role.ts
1695
+ import { Routes as Routes10 } from "@fluxerjs/types";
1696
+ import {
1697
+ PermissionFlags as PermissionFlags2,
1698
+ resolvePermissionsToBitfield
1699
+ } from "@fluxerjs/util";
1700
+ var Role = class extends Base {
1701
+ client;
1702
+ id;
1703
+ guildId;
1704
+ name;
1705
+ color;
1706
+ position;
1707
+ permissions;
1708
+ hoist;
1709
+ mentionable;
1710
+ unicodeEmoji;
1711
+ /** Separately sorted position for hoisted roles. Null if not set. */
1712
+ hoistPosition;
1713
+ /** @param client - The client instance */
1714
+ /** @param data - API role from GET /guilds/{id}/roles or gateway role events */
1715
+ /** @param guildId - The guild this role belongs to */
1716
+ constructor(client, data, guildId) {
1717
+ super();
1718
+ this.client = client;
1719
+ this.id = data.id;
1720
+ this.guildId = guildId;
1721
+ this.name = data.name;
1722
+ this.color = data.color;
1723
+ this.position = data.position;
1724
+ this.permissions = data.permissions;
1725
+ this.hoist = !!data.hoist;
1726
+ this.mentionable = !!data.mentionable;
1727
+ this.unicodeEmoji = data.unicode_emoji ?? null;
1728
+ this.hoistPosition = data.hoist_position ?? null;
1729
+ }
1730
+ /** Update mutable fields from fresh API data. Used by edit and gateway events. */
1731
+ _patch(data) {
1732
+ if (data.name !== void 0) this.name = data.name;
1733
+ if (data.color !== void 0) this.color = data.color;
1734
+ if (data.position !== void 0) this.position = data.position;
1735
+ if (data.permissions !== void 0) this.permissions = data.permissions;
1736
+ if (data.hoist !== void 0) this.hoist = !!data.hoist;
1737
+ if (data.mentionable !== void 0) this.mentionable = !!data.mentionable;
1738
+ if (data.unicode_emoji !== void 0) this.unicodeEmoji = data.unicode_emoji ?? null;
1739
+ if (data.hoist_position !== void 0) this.hoistPosition = data.hoist_position ?? null;
1740
+ }
1741
+ /** Returns a mention string (e.g. `<@&123456>`). */
1742
+ toString() {
1743
+ return `<@&${this.id}>`;
1744
+ }
1745
+ /**
1746
+ * Check if this role has a permission. Administrator grants all permissions.
1747
+ * @param permission - Permission flag, name, or resolvable
1748
+ * @returns true if the role has the permission
1749
+ * @example
1750
+ * if (role.has(PermissionFlags.BanMembers)) { ... }
1751
+ * if (role.has('ManageChannels')) { ... }
1752
+ */
1753
+ has(permission) {
1754
+ const perm = typeof permission === "number" ? permission : PermissionFlags2[permission];
1755
+ if (perm === void 0) return false;
1756
+ const permNum = Number(perm);
1757
+ const rolePerms = BigInt(this.permissions);
1758
+ const permBig = BigInt(permNum);
1759
+ if (permBig < 0) return false;
1760
+ if ((rolePerms & BigInt(PermissionFlags2.Administrator)) !== 0n) return true;
1761
+ return (rolePerms & permBig) === permBig;
1762
+ }
1763
+ /**
1764
+ * Edit this role.
1765
+ * Requires Manage Roles permission.
1766
+ * @param options - Role updates (permissions accepts PermissionResolvable for convenience)
1767
+ * @returns This role (updated in place)
1768
+ * @example
1769
+ * await role.edit({ name: 'Moderator', permissions: ['BanMembers', 'KickMembers'] });
1770
+ */
1771
+ async edit(options) {
1772
+ const body = {};
1773
+ if (options.name !== void 0) body.name = options.name;
1774
+ if (options.permissions !== void 0) {
1775
+ body.permissions = typeof options.permissions === "string" ? options.permissions : resolvePermissionsToBitfield(options.permissions);
1776
+ }
1777
+ if (options.color !== void 0) body.color = options.color;
1778
+ if (options.hoist !== void 0) body.hoist = options.hoist;
1779
+ if (options.mentionable !== void 0) body.mentionable = options.mentionable;
1780
+ if (options.unicode_emoji !== void 0) body.unicode_emoji = options.unicode_emoji;
1781
+ if (options.position !== void 0) body.position = options.position;
1782
+ if (options.hoist_position !== void 0) body.hoist_position = options.hoist_position;
1783
+ const data = await this.client.rest.patch(Routes10.guildRole(this.guildId, this.id), {
1784
+ body: Object.keys(body).length ? body : void 0,
1785
+ auth: true
1786
+ });
1787
+ this._patch(data);
1788
+ return this;
1789
+ }
1790
+ /**
1791
+ * Delete this role.
1792
+ * Requires Manage Roles permission.
1793
+ */
1794
+ async delete() {
1795
+ await this.client.rest.delete(Routes10.guildRole(this.guildId, this.id), { auth: true });
1796
+ const guild = this.client.guilds.get(this.guildId);
1797
+ if (guild) guild.roles.delete(this.id);
1798
+ }
1799
+ };
1800
+
1801
+ // src/structures/Guild.ts
1802
+ import { Routes as Routes14 } from "@fluxerjs/types";
1803
+
1804
+ // src/structures/GuildBan.ts
1805
+ import { Routes as Routes11 } from "@fluxerjs/types";
1806
+ var GuildBan = class extends Base {
1807
+ client;
1808
+ guildId;
1809
+ user;
1810
+ reason;
1811
+ /** ISO timestamp when a temporary ban expires. Null for permanent bans. */
1812
+ expiresAt;
1813
+ /** @param data - API ban from GET /guilds/{id}/bans or gateway GUILD_BAN_ADD */
1814
+ constructor(client, data, guildId) {
1815
+ super();
1816
+ this.client = client;
1817
+ this.guildId = data.guild_id ?? guildId;
1818
+ this.user = client.getOrCreateUser(data.user);
1819
+ this.reason = data.reason ?? null;
1820
+ this.expiresAt = data.expires_at ?? null;
1821
+ }
1822
+ /**
1823
+ * Remove this ban (unban the user).
1824
+ * Requires Ban Members permission.
1825
+ */
1826
+ async unban() {
1827
+ await this.client.rest.delete(Routes11.guildBan(this.guildId, this.user.id), {
1828
+ auth: true
1829
+ });
1830
+ }
1831
+ };
1832
+
1833
+ // src/structures/GuildEmoji.ts
1834
+ import { Routes as Routes12 } from "@fluxerjs/types";
1835
+ var GuildEmoji = class extends Base {
1836
+ client;
1837
+ id;
1838
+ guildId;
1839
+ name;
1840
+ animated;
1841
+ /** @param data - API emoji from GET /guilds/{id}/emojis or guild emoji events */
1842
+ constructor(client, data, guildId) {
1843
+ super();
1844
+ this.client = client;
1845
+ this.id = data.id;
1846
+ this.guildId = data.guild_id ?? guildId;
1847
+ this.name = data.name;
1848
+ this.animated = data.animated ?? false;
1849
+ }
1850
+ /** CDN URL for this emoji image. */
1851
+ get url() {
1852
+ const ext = this.animated ? "gif" : "png";
1853
+ return `${CDN_URL}/emojis/${this.id}.${ext}`;
1854
+ }
1855
+ /** Emoji identifier for use in reactions: `name:id` */
1856
+ get identifier() {
1857
+ return `${this.name}:${this.id}`;
1858
+ }
1859
+ /** Delete this emoji. Requires Manage Emojis and Stickers permission. */
1860
+ async delete() {
1861
+ await this.client.rest.delete(Routes12.guildEmoji(this.guildId, this.id), {
1862
+ auth: true
1863
+ });
1864
+ const guild = this.client.guilds.get(this.guildId);
1865
+ if (guild) guild.emojis.delete(this.id);
1866
+ }
1867
+ /**
1868
+ * Edit this emoji's name.
1869
+ * Requires Manage Emojis and Stickers permission.
1870
+ */
1871
+ async edit(options) {
1872
+ const data = await this.client.rest.patch(Routes12.guildEmoji(this.guildId, this.id), {
1873
+ body: options,
1874
+ auth: true
1875
+ });
1876
+ this.name = data.name;
1877
+ return this;
1878
+ }
1879
+ };
1880
+
1881
+ // src/structures/GuildSticker.ts
1882
+ import { Routes as Routes13 } from "@fluxerjs/types";
1883
+ var GuildSticker = class extends Base {
1884
+ client;
1885
+ id;
1886
+ guildId;
1887
+ name;
1888
+ description;
1889
+ tags;
1890
+ animated;
1891
+ /** @param data - API sticker from GET /guilds/{id}/stickers or guild sticker events */
1892
+ constructor(client, data, guildId) {
1893
+ super();
1894
+ this.client = client;
1895
+ this.id = data.id;
1896
+ this.guildId = data.guild_id ?? guildId;
1897
+ this.name = data.name;
1898
+ this.description = data.description ?? "";
1899
+ this.tags = data.tags ?? [];
1900
+ this.animated = data.animated ?? false;
1901
+ }
1902
+ /** CDN URL for this sticker image. */
1903
+ get url() {
1904
+ const ext = this.animated ? "gif" : "png";
1905
+ return `${CDN_URL}/stickers/${this.id}.${ext}`;
1906
+ }
1907
+ /** Delete this sticker. Requires Manage Emojis and Stickers permission. */
1908
+ async delete() {
1909
+ await this.client.rest.delete(Routes13.guildSticker(this.guildId, this.id), {
1910
+ auth: true
1911
+ });
1912
+ }
1913
+ /**
1914
+ * Edit this sticker's name and/or description.
1915
+ * Requires Manage Emojis and Stickers permission.
1916
+ */
1917
+ async edit(options) {
1918
+ const data = await this.client.rest.patch(Routes13.guildSticker(this.guildId, this.id), {
1919
+ body: options,
1920
+ auth: true
1921
+ });
1922
+ const s = data;
1923
+ this.name = s.name;
1924
+ this.description = s.description ?? "";
1925
+ return this;
1926
+ }
1927
+ };
1928
+
1929
+ // src/structures/Guild.ts
1930
+ var Guild = class extends Base {
1931
+ client;
1932
+ id;
1933
+ name;
1934
+ icon;
1935
+ banner;
1936
+ ownerId;
1937
+ /** Invite splash image hash. Null if none. */
1938
+ splash;
1939
+ /** Custom vanity URL code (e.g. fluxer.gg/code). Null if none. */
1940
+ vanityURLCode;
1941
+ /** Enabled guild features. */
1942
+ features;
1943
+ verificationLevel;
1944
+ defaultMessageNotifications;
1945
+ explicitContentFilter;
1946
+ /** AFK voice channel ID. Null if none. */
1947
+ afkChannelId;
1948
+ /** AFK timeout in seconds. */
1949
+ afkTimeout;
1950
+ /** System messages channel ID. Null if none. */
1951
+ systemChannelId;
1952
+ /** Rules/guidelines channel ID. Null if none. */
1953
+ rulesChannelId;
1954
+ nsfwLevel;
1955
+ mfaLevel;
1956
+ /** Banner image width. Optional. */
1957
+ bannerWidth;
1958
+ /** Banner image height. Optional. */
1959
+ bannerHeight;
1960
+ /** Splash image width. Optional. */
1961
+ splashWidth;
1962
+ /** Splash image height. Optional. */
1963
+ splashHeight;
1964
+ members;
1965
+ channels = new Collection7();
1966
+ roles = new Collection7();
1967
+ emojis = new Collection7();
1968
+ /** @param data - API guild from GET /guilds/{id} or gateway GUILD_CREATE */
1969
+ constructor(client, data) {
1970
+ super();
1971
+ this.client = client;
1972
+ this.id = data.id;
1973
+ this.members = new GuildMemberManager(this);
1974
+ this.name = data.name;
1975
+ this.icon = data.icon ?? null;
1976
+ this.banner = data.banner ?? null;
1977
+ this.ownerId = data.owner_id ?? data.ownerId ?? "";
1978
+ this.splash = data.splash ?? null;
1979
+ this.vanityURLCode = data.vanity_url_code ?? null;
1980
+ this.features = data.features ?? [];
1981
+ this.verificationLevel = data.verification_level ?? 0;
1982
+ this.defaultMessageNotifications = data.default_message_notifications ?? 0;
1983
+ this.explicitContentFilter = data.explicit_content_filter ?? 0;
1984
+ this.afkChannelId = data.afk_channel_id ?? null;
1985
+ this.afkTimeout = data.afk_timeout ?? 0;
1986
+ this.systemChannelId = data.system_channel_id ?? null;
1987
+ this.rulesChannelId = data.rules_channel_id ?? null;
1988
+ this.nsfwLevel = data.nsfw_level ?? 0;
1989
+ this.mfaLevel = data.mfa_level ?? 0;
1990
+ this.bannerWidth = data.banner_width ?? null;
1991
+ this.bannerHeight = data.banner_height ?? null;
1992
+ this.splashWidth = data.splash_width ?? null;
1993
+ this.splashHeight = data.splash_height ?? null;
1994
+ for (const r of data.roles ?? []) {
1995
+ this.roles.set(r.id, new Role(client, r, this.id));
1996
+ }
1997
+ }
1998
+ /** Get the guild icon URL, or null if no icon. */
1999
+ iconURL(options) {
2000
+ if (!this.icon) return null;
2001
+ const size = options?.size ? `?size=${options.size}` : "";
2002
+ return `${CDN_URL}/icons/${this.id}/${this.icon}.png${size}`;
2003
+ }
2004
+ /** Get the guild banner URL, or null if no banner. */
2005
+ bannerURL(options) {
2006
+ if (!this.banner) return null;
2007
+ const size = options?.size ? `?size=${options.size}` : "";
2008
+ return `${CDN_URL}/banners/${this.id}/${this.banner}.png${size}`;
2009
+ }
2010
+ /** Get the guild splash (invite background) URL, or null if no splash. */
2011
+ splashURL(options) {
2012
+ if (!this.splash) return null;
2013
+ const size = options?.size ? `?size=${options.size}` : "";
2014
+ return `${CDN_URL}/splashes/${this.id}/${this.splash}.png${size}`;
2015
+ }
2016
+ /**
2017
+ * Add a role to a member by user ID. Does not require fetching the member first.
2018
+ * @param userId - The user ID of the member
2019
+ * @param roleId - The role ID to add (or use guild.resolveRoleId for mention/name resolution)
2020
+ * Requires Manage Roles permission.
2021
+ */
2022
+ async addRoleToMember(userId, roleId) {
2023
+ await this.client.rest.put(Routes14.guildMemberRole(this.id, userId, roleId));
2024
+ }
2025
+ /**
2026
+ * Remove a role from a member by user ID. Does not require fetching the member first.
2027
+ * @param userId - The user ID of the member
2028
+ * @param roleId - The role ID to remove
2029
+ * Requires Manage Roles permission.
2030
+ */
2031
+ async removeRoleFromMember(userId, roleId) {
2032
+ await this.client.rest.delete(Routes14.guildMemberRole(this.id, userId, roleId));
2033
+ }
2034
+ /**
2035
+ * Create a role in this guild.
2036
+ * Requires Manage Roles permission.
2037
+ * @param options - Role data (permissions accepts PermissionResolvable for convenience)
2038
+ * @returns The created role
2039
+ * @example
2040
+ * const role = await guild.createRole({ name: 'Mod', permissions: ['KickMembers', 'BanMembers'] });
2041
+ */
2042
+ async createRole(options) {
2043
+ const body = {};
2044
+ if (options.name !== void 0) body.name = options.name;
2045
+ if (options.permissions !== void 0) {
2046
+ body.permissions = typeof options.permissions === "string" ? options.permissions : resolvePermissionsToBitfield2(options.permissions);
2047
+ }
2048
+ if (options.color !== void 0) body.color = options.color;
2049
+ if (options.hoist !== void 0) body.hoist = options.hoist;
2050
+ if (options.mentionable !== void 0) body.mentionable = options.mentionable;
2051
+ if (options.unicode_emoji !== void 0) body.unicode_emoji = options.unicode_emoji;
2052
+ if (options.position !== void 0) body.position = options.position;
2053
+ if (options.hoist_position !== void 0) body.hoist_position = options.hoist_position;
2054
+ const data = await this.client.rest.post(Routes14.guildRoles(this.id), {
2055
+ body: Object.keys(body).length ? body : void 0,
2056
+ auth: true
2057
+ });
2058
+ const role = new Role(this.client, data, this.id);
2059
+ this.roles.set(role.id, role);
2060
+ return role;
2061
+ }
2062
+ /**
2063
+ * Fetch all roles in this guild.
2064
+ * @returns Array of Role objects (cached in guild.roles)
2065
+ */
2066
+ async fetchRoles() {
2067
+ const data = await this.client.rest.get(
2068
+ Routes14.guildRoles(this.id)
2069
+ );
2070
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
2071
+ const roles = [];
2072
+ for (const r of list) {
2073
+ const role = new Role(this.client, r, this.id);
2074
+ this.roles.set(role.id, role);
2075
+ roles.push(role);
2076
+ }
2077
+ return roles;
2078
+ }
2079
+ /**
2080
+ * Fetch a role by ID.
2081
+ * @param roleId - The role ID to fetch
2082
+ * @returns The role
2083
+ * @throws FluxerError with ROLE_NOT_FOUND if role does not exist (404)
2084
+ */
2085
+ async fetchRole(roleId) {
2086
+ try {
2087
+ const data = await this.client.rest.get(Routes14.guildRole(this.id, roleId));
2088
+ const role = new Role(this.client, data, this.id);
2089
+ this.roles.set(role.id, role);
2090
+ return role;
2091
+ } catch (err) {
2092
+ const statusCode = err instanceof FluxerAPIError3 ? err.statusCode : err?.statusCode;
2093
+ if (statusCode === 404) {
2094
+ throw new FluxerError(`Role ${roleId} not found in guild`, {
2095
+ code: ErrorCodes.RoleNotFound,
2096
+ cause: err
2097
+ });
2098
+ }
2099
+ throw err instanceof FluxerError ? err : new FluxerError("Failed to fetch guild role", { cause: err });
2100
+ }
91
2101
  }
92
- maxSize;
93
- set(key, value) {
94
- if (this.maxSize > 0 && this.size >= this.maxSize && !this.has(key)) {
95
- const firstKey = this.keys().next().value;
96
- if (firstKey !== void 0) this.delete(firstKey);
2102
+ /**
2103
+ * Resolve a role ID from an argument (role mention, raw ID, or name).
2104
+ * Fetches guild roles if name is provided.
2105
+ * @param arg - Role mention (@role), role ID, or role name
2106
+ * @returns The role ID, or null if not found
2107
+ */
2108
+ async resolveRoleId(arg) {
2109
+ const parsed = parseRoleMention(arg);
2110
+ if (parsed) return parsed;
2111
+ if (/^\d{17,19}$/.test(arg.trim())) return arg.trim();
2112
+ const cached = this.roles.find(
2113
+ (r) => !!(r.name && r.name.toLowerCase() === arg.trim().toLowerCase())
2114
+ );
2115
+ if (cached) return cached.id;
2116
+ const roles = await this.client.rest.get(Routes14.guildRoles(this.id));
2117
+ const list = Array.isArray(roles) ? roles : Object.values(roles ?? {});
2118
+ const role = list.find((r) => !!(r.name && r.name.toLowerCase() === arg.trim().toLowerCase()));
2119
+ if (role) {
2120
+ this.roles.set(role.id, new Role(this.client, role, this.id));
2121
+ return role.id;
97
2122
  }
98
- return super.set(key, value);
2123
+ return null;
99
2124
  }
100
2125
  /**
101
- * Get a channel from cache or fetch from the API if not present.
102
- * Convenience helper to avoid repeating `client.channels.get(id) ?? (await client.channels.fetch(id))`.
103
- * @param channelId - Snowflake of the channel
104
- * @returns The channel
105
- * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
106
- * @example
107
- * const channel = await client.channels.resolve(message.channelId);
108
- * if (channel?.isSendable()) await channel.send('Hello!');
2126
+ * Ban a user from this guild.
2127
+ * @param userId - The user ID to ban
2128
+ * @param options - Optional reason, delete_message_days (0–7), and ban_duration_seconds (temporary ban).
2129
+ * ban_duration_seconds: 0 = permanent, or use 3600, 43200, 86400, 259200, 432000, 604800, 1209600, 2592000.
2130
+ * Requires Ban Members permission.
109
2131
  */
110
- async resolve(channelId) {
111
- return this.get(channelId) ?? this.fetch(channelId);
2132
+ async ban(userId, options) {
2133
+ const body = {};
2134
+ if (options?.reason) body.reason = options.reason;
2135
+ if (options?.delete_message_days != null)
2136
+ body.delete_message_days = options.delete_message_days;
2137
+ if (options?.ban_duration_seconds != null)
2138
+ body.ban_duration_seconds = options.ban_duration_seconds;
2139
+ await this.client.rest.put(Routes14.guildBan(this.id, userId), {
2140
+ body: Object.keys(body).length ? body : void 0,
2141
+ auth: true
2142
+ });
112
2143
  }
113
2144
  /**
114
- * Fetch a channel by ID from the API (or return from cache if present).
115
- * @param channelId - Snowflake of the channel
116
- * @returns The channel
117
- * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
118
- * @example
119
- * const channel = await client.channels.fetch(channelId);
120
- * if (channel?.isSendable()) await channel.send('Hello!');
2145
+ * Fetch guild bans. Requires Ban Members permission.
2146
+ * @returns List of GuildBan objects
121
2147
  */
122
- async fetch(channelId) {
123
- const cached = this.get(channelId);
124
- if (cached) return cached;
2148
+ async fetchBans() {
2149
+ const data = await this.client.rest.get(
2150
+ Routes14.guildBans(this.id)
2151
+ );
2152
+ const list = Array.isArray(data) ? data : data?.bans ?? [];
2153
+ return list.map((b) => new GuildBan(this.client, { ...b, guild_id: this.id }, this.id));
2154
+ }
2155
+ /**
2156
+ * Remove a ban (unban a user).
2157
+ * @param userId - The user ID to unban
2158
+ * Requires Ban Members permission.
2159
+ */
2160
+ async unban(userId) {
2161
+ await this.client.rest.delete(Routes14.guildBan(this.id, userId), { auth: true });
2162
+ }
2163
+ /**
2164
+ * Kick a member from this guild.
2165
+ * @param userId - The user ID to kick
2166
+ * Requires Kick Members permission.
2167
+ */
2168
+ async kick(userId) {
2169
+ await this.client.rest.delete(Routes14.guildMember(this.id, userId), { auth: true });
2170
+ }
2171
+ /**
2172
+ * Fetch a guild member by user ID.
2173
+ * @param userId - The user ID of the member to fetch
2174
+ * @returns The guild member
2175
+ * @throws FluxerError with MEMBER_NOT_FOUND if user is not in the guild (404)
2176
+ * @throws FluxerError with cause for permission denied (403) or other REST errors
2177
+ */
2178
+ async fetchMember(userId) {
125
2179
  try {
126
- const { Channel: Channel2 } = await import("./Channel-4YUSB2MM.mjs");
127
2180
  const data = await this.client.rest.get(
128
- Routes.channel(channelId)
2181
+ Routes14.guildMember(this.id, userId)
129
2182
  );
130
- const channel = Channel2.fromOrCreate(this.client, data);
131
- if (!channel) {
132
- throw new FluxerError("Channel data invalid or unsupported type", {
133
- code: ErrorCodes.ChannelNotFound
134
- });
135
- }
136
- this.set(channel.id, channel);
137
- if ("guildId" in channel && channel.guildId) {
138
- const guild = this.client.guilds.get(channel.guildId);
139
- if (guild) guild.channels.set(channel.id, channel);
140
- }
141
- return channel;
2183
+ const member = new GuildMember(this.client, { ...data, guild_id: this.id }, this);
2184
+ this.members.set(member.id, member);
2185
+ return member;
142
2186
  } catch (err) {
143
- if (err instanceof RateLimitError) throw err;
144
- if (err instanceof FluxerAPIError && err.statusCode === 404) {
145
- throw new FluxerError(`Channel ${channelId} not found`, {
146
- code: ErrorCodes.ChannelNotFound,
2187
+ const statusCode = err instanceof FluxerAPIError3 ? err.statusCode : err?.statusCode;
2188
+ if (statusCode === 404) {
2189
+ throw new FluxerError(`Member ${userId} not found in guild`, {
2190
+ code: ErrorCodes.MemberNotFound,
147
2191
  cause: err
148
2192
  });
149
2193
  }
150
- throw err instanceof FluxerError ? err : new FluxerError(String(err), { cause: err });
2194
+ throw err instanceof FluxerError ? err : new FluxerError("Failed to fetch guild member", { cause: err });
151
2195
  }
152
2196
  }
153
2197
  /**
154
- * Fetch a message by ID from the API.
155
- * @param channelId - Snowflake of the channel
156
- * @param messageId - Snowflake of the message
157
- * @returns The message
158
- * @throws FluxerError with MESSAGE_NOT_FOUND if the message does not exist
159
- * @deprecated Use channel.messages.fetch(messageId). Prefer (await client.channels.resolve(channelId))?.messages?.fetch(messageId).
160
- * @example
161
- * const channel = await client.channels.resolve(channelId);
162
- * const message = await channel?.messages?.fetch(messageId);
2198
+ * Fetch guild audit logs. Requires View Audit Log permission.
2199
+ * @param options - Optional limit, before, after, user_id, action_type for filtering
163
2200
  */
164
- async fetchMessage(channelId, messageId) {
165
- emitDeprecationWarning(
166
- "ChannelManager.fetchMessage()",
167
- "Use channel.messages.fetch(messageId). Prefer (await client.channels.resolve(channelId))?.messages?.fetch(messageId)."
2201
+ async fetchAuditLogs(options) {
2202
+ const params = new URLSearchParams();
2203
+ if (options?.limit != null) params.set("limit", String(options.limit));
2204
+ if (options?.before) params.set("before", options.before);
2205
+ if (options?.after) params.set("after", options.after);
2206
+ if (options?.userId) params.set("user_id", options.userId);
2207
+ if (options?.actionType != null) params.set("action_type", String(options.actionType));
2208
+ const qs = params.toString();
2209
+ const url = Routes14.guildAuditLogs(this.id) + (qs ? `?${qs}` : "");
2210
+ return this.client.rest.get(url);
2211
+ }
2212
+ /** Fetch all webhooks in this guild. Returned webhooks do not include the token (cannot send). */
2213
+ async fetchWebhooks() {
2214
+ const data = await this.client.rest.get(Routes14.guildWebhooks(this.id));
2215
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
2216
+ return list.map((w) => new Webhook(this.client, w));
2217
+ }
2218
+ /**
2219
+ * Create a channel in this guild.
2220
+ * @param data - Channel data: type (0=text, 2=voice, 4=category, 5=link), name, and optional parent_id, topic, bitrate, user_limit, nsfw, permission_overwrites
2221
+ * Requires Manage Channels permission.
2222
+ */
2223
+ async createChannel(data) {
2224
+ const created = await this.client.rest.post(Routes14.guildChannels(this.id), {
2225
+ body: data,
2226
+ auth: true
2227
+ });
2228
+ const channel = Channel.from(this.client, created);
2229
+ if (channel) {
2230
+ this.client.channels.set(channel.id, channel);
2231
+ this.channels.set(channel.id, channel);
2232
+ }
2233
+ return channel;
2234
+ }
2235
+ /**
2236
+ * Fetch all channels in this guild.
2237
+ * @returns Array of GuildChannel objects (cached in guild.channels and client.channels)
2238
+ */
2239
+ async fetchChannels() {
2240
+ const data = await this.client.rest.get(Routes14.guildChannels(this.id));
2241
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
2242
+ const channels = [];
2243
+ for (const ch of list) {
2244
+ const channel = Channel.from(this.client, ch);
2245
+ if (channel) {
2246
+ this.client.channels.set(channel.id, channel);
2247
+ this.channels.set(channel.id, channel);
2248
+ channels.push(channel);
2249
+ }
2250
+ }
2251
+ return channels;
2252
+ }
2253
+ /**
2254
+ * Edit this guild. PATCH /guilds/{id}.
2255
+ * Requires guild owner or Administrator.
2256
+ */
2257
+ async edit(options) {
2258
+ const data = await this.client.rest.patch(Routes14.guild(this.id), {
2259
+ body: options,
2260
+ auth: true
2261
+ });
2262
+ this.name = data.name;
2263
+ this.icon = data.icon ?? this.icon;
2264
+ this.banner = data.banner ?? this.banner;
2265
+ this.splash = data.splash ?? this.splash;
2266
+ this.systemChannelId = data.system_channel_id ?? this.systemChannelId;
2267
+ this.afkChannelId = data.afk_channel_id ?? this.afkChannelId;
2268
+ this.afkTimeout = data.afk_timeout ?? this.afkTimeout;
2269
+ this.verificationLevel = data.verification_level ?? this.verificationLevel;
2270
+ this.mfaLevel = data.mfa_level ?? this.mfaLevel;
2271
+ this.explicitContentFilter = data.explicit_content_filter ?? this.explicitContentFilter;
2272
+ this.defaultMessageNotifications = data.default_message_notifications ?? this.defaultMessageNotifications;
2273
+ this.features = data.features ?? this.features;
2274
+ return this;
2275
+ }
2276
+ /**
2277
+ * Delete this guild. POST /guilds/{id}/delete.
2278
+ * Must be the guild owner.
2279
+ */
2280
+ async delete() {
2281
+ await this.client.rest.post(Routes14.guildDelete(this.id), { auth: true });
2282
+ this.client.guilds.delete(this.id);
2283
+ }
2284
+ /**
2285
+ * Fetch vanity URL for this guild. GET /guilds/{id}/vanity-url.
2286
+ * Requires Manage Guild permission.
2287
+ */
2288
+ async fetchVanityURL() {
2289
+ return this.client.rest.get(Routes14.guildVanityUrl(this.id), { auth: true });
2290
+ }
2291
+ /**
2292
+ * Transfer guild ownership to another user. POST /guilds/{id}/transfer-ownership.
2293
+ * Must be the guild owner.
2294
+ */
2295
+ async transferOwnership(newOwnerId, password) {
2296
+ await this.client.rest.post(Routes14.guildTransferOwnership(this.id), {
2297
+ body: { new_owner_id: newOwnerId, ...password != null && { password } },
2298
+ auth: true
2299
+ });
2300
+ }
2301
+ /**
2302
+ * Set text channel flexible names feature. PATCH /guilds/{id}/text-channel-flexible-names.
2303
+ */
2304
+ setTextChannelFlexibleNames(enabled) {
2305
+ return this.client.rest.patch(Routes14.guildTextChannelFlexibleNames(this.id), {
2306
+ body: { enabled },
2307
+ auth: true
2308
+ }).then(() => this);
2309
+ }
2310
+ /**
2311
+ * Set detached banner feature. PATCH /guilds/{id}/detached-banner.
2312
+ */
2313
+ setDetachedBanner(enabled) {
2314
+ return this.client.rest.patch(Routes14.guildDetachedBanner(this.id), {
2315
+ body: { enabled },
2316
+ auth: true
2317
+ }).then(() => this);
2318
+ }
2319
+ /**
2320
+ * Set disallow unclaimed accounts. PATCH /guilds/{id}/disallow-unclaimed-accounts.
2321
+ */
2322
+ setDisallowUnclaimedAccounts(enabled) {
2323
+ return this.client.rest.patch(Routes14.guildDisallowUnclaimedAccounts(this.id), {
2324
+ body: { enabled },
2325
+ auth: true
2326
+ }).then(() => this);
2327
+ }
2328
+ /**
2329
+ * Update role positions. PATCH /guilds/{id}/roles.
2330
+ * @param updates - Array of { id, position? }
2331
+ */
2332
+ async setRolePositions(updates) {
2333
+ const data = await this.client.rest.patch(
2334
+ Routes14.guildRoles(this.id),
2335
+ { body: updates, auth: true }
2336
+ );
2337
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
2338
+ for (const r of list) {
2339
+ this.roles.set(r.id, new Role(this.client, r, this.id));
2340
+ }
2341
+ return list;
2342
+ }
2343
+ /**
2344
+ * Update role hoist positions. PATCH /guilds/{id}/roles/hoist-positions.
2345
+ */
2346
+ async setRoleHoistPositions(updates) {
2347
+ const data = await this.client.rest.patch(
2348
+ Routes14.guildRolesHoistPositions(this.id),
2349
+ { body: updates, auth: true }
2350
+ );
2351
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
2352
+ for (const r of list) {
2353
+ this.roles.set(r.id, new Role(this.client, r, this.id));
2354
+ }
2355
+ return list;
2356
+ }
2357
+ /**
2358
+ * Reset role hoist positions. DELETE /guilds/{id}/roles/hoist-positions.
2359
+ */
2360
+ async resetRoleHoistPositions() {
2361
+ const data = await this.client.rest.delete(
2362
+ Routes14.guildRolesHoistPositions(this.id),
2363
+ { auth: true }
2364
+ );
2365
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
2366
+ for (const r of list) {
2367
+ this.roles.set(r.id, new Role(this.client, r, this.id));
2368
+ }
2369
+ return list;
2370
+ }
2371
+ /**
2372
+ * Update channel positions.
2373
+ * @param updates - Array of { id, position?, parent_id?, lock_permissions? }
2374
+ * Requires Manage Channels permission.
2375
+ */
2376
+ async setChannelPositions(updates) {
2377
+ await this.client.rest.patch(Routes14.guildChannels(this.id), {
2378
+ body: updates,
2379
+ auth: true
2380
+ });
2381
+ }
2382
+ /**
2383
+ * Fetch all emojis in this guild.
2384
+ * @returns Array of GuildEmoji objects (cached in guild.emojis)
2385
+ */
2386
+ async fetchEmojis() {
2387
+ const data = await this.client.rest.get(
2388
+ Routes14.guildEmojis(this.id)
168
2389
  );
2390
+ const list = Array.isArray(data) ? data : Object.values(data ?? {});
2391
+ const emojis = [];
2392
+ for (const e of list) {
2393
+ const emoji = new GuildEmoji(this.client, { ...e, guild_id: this.id }, this.id);
2394
+ this.emojis.set(emoji.id, emoji);
2395
+ emojis.push(emoji);
2396
+ }
2397
+ return emojis;
2398
+ }
2399
+ /**
2400
+ * Fetch a single emoji by ID.
2401
+ * @param emojiId - The emoji ID to fetch
2402
+ * @returns The guild emoji
2403
+ * @throws FluxerError if emoji not found (404)
2404
+ */
2405
+ async fetchEmoji(emojiId) {
169
2406
  try {
170
- const { Message: Message2 } = await import("./Message-R7KSI5YU.mjs");
171
- const data = await this.client.rest.get(
172
- Routes.channelMessage(channelId, messageId)
173
- );
174
- return new Message2(this.client, data);
2407
+ const data = await this.client.rest.get(Routes14.guildEmoji(this.id, emojiId));
2408
+ const emoji = new GuildEmoji(this.client, { ...data, guild_id: this.id }, this.id);
2409
+ this.emojis.set(emoji.id, emoji);
2410
+ return emoji;
175
2411
  } catch (err) {
176
- if (err instanceof RateLimitError) throw err;
177
- if (err instanceof FluxerAPIError && err.statusCode === 404) {
178
- throw new FluxerError(`Message ${messageId} not found in channel ${channelId}`, {
179
- code: ErrorCodes.MessageNotFound,
2412
+ const statusCode = err instanceof FluxerAPIError3 ? err.statusCode : err?.statusCode;
2413
+ if (statusCode === 404) {
2414
+ throw new FluxerError(`Emoji ${emojiId} not found in guild`, {
2415
+ code: ErrorCodes.EmojiNotFound,
180
2416
  cause: err
181
2417
  });
182
2418
  }
183
- throw err instanceof FluxerError ? err : new FluxerError(String(err), { cause: err });
2419
+ throw err instanceof FluxerError ? err : new FluxerError("Failed to fetch guild emoji", { cause: err });
184
2420
  }
185
2421
  }
186
2422
  /**
187
- * Send a message to a channel by ID. Works even when the channel is not cached.
188
- * Skips the fetch when you only need to send.
189
- * @param channelId - Snowflake of the channel (text channel or DM)
190
- * @param payload - Text content or object with content, embeds, and/or files
191
- * @returns The created message
192
- * @example
193
- * await client.channels.send(logChannelId, 'User joined!');
194
- * await client.channels.send(channelId, { embeds: [embed] });
195
- * await client.channels.send(channelId, { content: 'Report', files: [{ name: 'log.txt', data }] });
2423
+ * Bulk create emojis. POST /guilds/{id}/emojis/bulk.
2424
+ * @param emojis - Array of { name, image } (base64), 1-50 emojis
2425
+ * @returns Array of created GuildEmoji objects
196
2426
  */
197
- async send(channelId, payload) {
198
- const opts = typeof payload === "string" ? { content: payload } : payload;
199
- const body = buildSendBody(payload);
200
- const { Message: Message2 } = await import("./Message-R7KSI5YU.mjs");
201
- const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
202
- const postOptions = files?.length ? { body, files } : { body };
203
- const data = await this.client.rest.post(Routes.channelMessages(channelId), postOptions);
204
- return new Message2(this.client, data);
2427
+ async createEmojisBulk(emojis) {
2428
+ const data = await this.client.rest.post(
2429
+ Routes14.guildEmojisBulk(this.id),
2430
+ {
2431
+ body: { emojis },
2432
+ auth: true
2433
+ }
2434
+ );
2435
+ const list = Array.isArray(data) ? data : data?.emojis ?? [];
2436
+ return list.map((e) => new GuildEmoji(this.client, { ...e, guild_id: this.id }, this.id));
2437
+ }
2438
+ /**
2439
+ * Bulk create stickers. POST /guilds/{id}/stickers/bulk.
2440
+ * @param stickers - Array of { name, image, description?, tags? }, 1-50 stickers
2441
+ * @returns Array of created GuildSticker objects
2442
+ */
2443
+ async createStickersBulk(stickers) {
2444
+ const data = await this.client.rest.post(
2445
+ Routes14.guildStickersBulk(this.id),
2446
+ {
2447
+ body: { stickers },
2448
+ auth: true
2449
+ }
2450
+ );
2451
+ const list = Array.isArray(data) ? data : data?.stickers ?? [];
2452
+ return list.map((s) => new GuildSticker(this.client, { ...s, guild_id: this.id }, this.id));
205
2453
  }
206
2454
  };
207
2455
 
208
2456
  // src/client/GuildManager.ts
209
- import { Collection as Collection2 } from "@fluxerjs/collection";
210
- import { Routes as Routes2 } from "@fluxerjs/types";
211
- var GuildManager = class extends Collection2 {
2457
+ var GuildManager = class extends Collection8 {
212
2458
  constructor(client) {
213
2459
  super();
214
2460
  this.client = client;
@@ -240,12 +2486,11 @@ var GuildManager = class extends Collection2 {
240
2486
  * @returns The created guild
241
2487
  */
242
2488
  async create(options) {
243
- const { Guild: Guild2 } = await import("./Guild-4U4U422Z.mjs");
244
- const data = await this.client.rest.post(Routes2.guilds(), {
2489
+ const data = await this.client.rest.post(Routes15.guilds(), {
245
2490
  body: options,
246
2491
  auth: true
247
2492
  });
248
- const guild = new Guild2(this.client, data);
2493
+ const guild = new Guild(this.client, data);
249
2494
  this.set(guild.id, guild);
250
2495
  return guild;
251
2496
  }
@@ -261,11 +2506,8 @@ var GuildManager = class extends Collection2 {
261
2506
  const cached = this.get(guildId);
262
2507
  if (cached) return cached;
263
2508
  try {
264
- const { Guild: Guild2 } = await import("./Guild-4U4U422Z.mjs");
265
- const data = await this.client.rest.get(
266
- Routes2.guild(guildId)
267
- );
268
- const guild = new Guild2(this.client, data);
2509
+ const data = await this.client.rest.get(Routes15.guild(guildId));
2510
+ const guild = new Guild(this.client, data);
269
2511
  this.set(guild.id, guild);
270
2512
  return guild;
271
2513
  } catch {
@@ -274,18 +2516,144 @@ var GuildManager = class extends Collection2 {
274
2516
  }
275
2517
  };
276
2518
 
2519
+ // src/structures/User.ts
2520
+ import { Routes as Routes16 } from "@fluxerjs/types";
2521
+ var User = class extends Base {
2522
+ client;
2523
+ id;
2524
+ username;
2525
+ discriminator;
2526
+ globalName;
2527
+ avatar;
2528
+ bot;
2529
+ /** RGB avatar color (e.g. 7577782). Null if not set. */
2530
+ avatarColor;
2531
+ /** Public flags bitfield. Null if not set. */
2532
+ flags;
2533
+ /** Whether this is an official system user. */
2534
+ system;
2535
+ /** Banner hash (from profile, member, or invite context). Null when not available. */
2536
+ banner;
2537
+ /** @param data - API user from message author, GET /users/{id}, or GET /users/@me */
2538
+ constructor(client, data) {
2539
+ super();
2540
+ this.client = client;
2541
+ this.id = data.id;
2542
+ this.username = data.username;
2543
+ this.discriminator = data.discriminator;
2544
+ this.globalName = data.global_name ?? null;
2545
+ this.avatar = data.avatar ?? null;
2546
+ this.bot = !!data.bot;
2547
+ this.avatarColor = data.avatar_color ?? null;
2548
+ this.flags = data.flags ?? data.public_flags ?? null;
2549
+ this.system = !!data.system;
2550
+ this.banner = data.banner ?? null;
2551
+ }
2552
+ /** Update mutable fields from fresh API data. Used by getOrCreateUser cache. */
2553
+ _patch(data) {
2554
+ this.username = data.username;
2555
+ this.discriminator = data.discriminator;
2556
+ this.globalName = data.global_name ?? null;
2557
+ this.avatar = data.avatar ?? null;
2558
+ if (data.avatar_color !== void 0) this.avatarColor = data.avatar_color;
2559
+ if (data.flags !== void 0) this.flags = data.flags;
2560
+ if (data.banner !== void 0) this.banner = data.banner;
2561
+ }
2562
+ /**
2563
+ * Get the URL for this user's avatar.
2564
+ * Auto-detects animated avatars (hash starting with `a_`) and uses gif extension.
2565
+ * @param options - Optional `size` and `extension` (default: png, or gif for animated)
2566
+ */
2567
+ avatarURL(options) {
2568
+ if (!this.avatar) return null;
2569
+ const ext = this.avatar.startsWith("a_") ? "gif" : options?.extension ?? "png";
2570
+ const size = options?.size ? `?size=${options.size}` : "";
2571
+ return `${CDN_URL}/avatars/${this.id}/${this.avatar}.${ext}${size}`;
2572
+ }
2573
+ /** Get the avatar URL, or the default avatar if none set (Fluxer: fluxerstatic.com). */
2574
+ displayAvatarURL(options) {
2575
+ return this.avatarURL(options) ?? cdnDefaultAvatarURL(this.id);
2576
+ }
2577
+ /**
2578
+ * Get the URL for this user's banner.
2579
+ * Returns null if the user has no banner (only available when fetched from profile/member context).
2580
+ */
2581
+ bannerURL(options) {
2582
+ if (!this.banner) return null;
2583
+ const ext = this.banner.startsWith("a_") ? "gif" : options?.extension ?? "png";
2584
+ const size = options?.size ? `?size=${options.size}` : "";
2585
+ return `${CDN_URL}/banners/${this.id}/${this.banner}.${ext}${size}`;
2586
+ }
2587
+ /** Returns a mention string (e.g. `<@123456>`). */
2588
+ toString() {
2589
+ return `<@${this.id}>`;
2590
+ }
2591
+ /**
2592
+ * Create or get a DM channel with this user.
2593
+ * Returns the DM channel; use {@link DMChannel.send} to send messages.
2594
+ */
2595
+ async createDM() {
2596
+ const data = await this.client.rest.post(Routes16.userMeChannels(), {
2597
+ body: { recipient_id: this.id },
2598
+ auth: true
2599
+ });
2600
+ return new DMChannel(this.client, data);
2601
+ }
2602
+ /**
2603
+ * Send a DM to this user.
2604
+ * Convenience method that creates the DM channel and sends the message.
2605
+ */
2606
+ async send(options) {
2607
+ const dm = await this.createDM();
2608
+ return dm.send(options);
2609
+ }
2610
+ };
2611
+
2612
+ // src/client/ClientUser.ts
2613
+ import { Routes as Routes17 } from "@fluxerjs/types";
2614
+ var ClientUser = class extends User {
2615
+ constructor(client, data) {
2616
+ super(client, { ...data });
2617
+ }
2618
+ /**
2619
+ * Fetch guilds the bot is a member of.
2620
+ * @returns Array of Guild objects (cached in client.guilds)
2621
+ */
2622
+ async fetchGuilds() {
2623
+ const data = await this.client.rest.get(
2624
+ Routes17.currentUserGuilds()
2625
+ );
2626
+ const list = Array.isArray(data) ? data : data?.guilds ?? [];
2627
+ const guilds = [];
2628
+ for (const g of list) {
2629
+ const guild = new Guild(this.client, g);
2630
+ this.client.guilds.set(guild.id, guild);
2631
+ guilds.push(guild);
2632
+ }
2633
+ return guilds;
2634
+ }
2635
+ /**
2636
+ * Leave a guild. Requires the bot to be a member.
2637
+ * @param guildId - The guild ID to leave
2638
+ */
2639
+ async leaveGuild(guildId) {
2640
+ await this.client.rest.delete(Routes17.leaveGuild(guildId), { auth: true });
2641
+ this.client.guilds.delete(guildId);
2642
+ }
2643
+ };
2644
+
277
2645
  // src/client/Client.ts
278
2646
  import {
279
- emitDeprecationWarning as emitDeprecationWarning2,
2647
+ emitDeprecationWarning as emitDeprecationWarning3,
280
2648
  formatEmoji,
281
2649
  getUnicodeFromShortcode,
282
2650
  parseEmoji
283
2651
  } from "@fluxerjs/util";
284
2652
 
285
2653
  // src/client/UsersManager.ts
286
- import { Collection as Collection3 } from "@fluxerjs/collection";
287
- import { Routes as Routes3 } from "@fluxerjs/types";
288
- var UsersManager = class extends Collection3 {
2654
+ import { Collection as Collection9 } from "@fluxerjs/collection";
2655
+ import { Routes as Routes18 } from "@fluxerjs/types";
2656
+ var UsersManager = class extends Collection9 {
289
2657
  constructor(client) {
290
2658
  super();
291
2659
  this.client = client;
@@ -310,7 +2678,7 @@ var UsersManager = class extends Collection3 {
310
2678
  * console.log(user.username);
311
2679
  */
312
2680
  async fetch(userId) {
313
- const data = await this.client.rest.get(Routes3.user(userId));
2681
+ const data = await this.client.rest.get(Routes18.user(userId));
314
2682
  return this.client.getOrCreateUser(data);
315
2683
  }
316
2684
  /**
@@ -330,10 +2698,10 @@ var UsersManager = class extends Collection3 {
330
2698
  async fetchWithProfile(userId, options) {
331
2699
  const guildId = options?.guildId ?? void 0;
332
2700
  const [userData, globalProfileData, serverProfileData, memberData] = await Promise.all([
333
- this.client.rest.get(Routes3.user(userId)),
334
- this.client.rest.get(Routes3.userProfile(userId)).catch(() => null),
335
- guildId ? this.client.rest.get(Routes3.userProfile(userId, guildId)).catch(() => null) : Promise.resolve(null),
336
- guildId ? this.client.rest.get(Routes3.guildMember(guildId, userId)).catch(() => null) : Promise.resolve(null)
2701
+ this.client.rest.get(Routes18.user(userId)),
2702
+ this.client.rest.get(Routes18.userProfile(userId)).catch(() => null),
2703
+ guildId ? this.client.rest.get(Routes18.userProfile(userId, guildId)).catch(() => null) : Promise.resolve(null),
2704
+ guildId ? this.client.rest.get(Routes18.guildMember(guildId, userId)).catch(() => null) : Promise.resolve(null)
337
2705
  ]);
338
2706
  const user = this.client.getOrCreateUser(userData);
339
2707
  const globalProfile = globalProfileData && typeof globalProfileData === "object" ? globalProfileData : null;
@@ -357,25 +2725,89 @@ var UsersManager = class extends Collection3 {
357
2725
  }
358
2726
  };
359
2727
 
2728
+ // src/util/guildUtils.ts
2729
+ function normalizeGuildPayload(raw) {
2730
+ if (!raw || typeof raw !== "object") {
2731
+ return null;
2732
+ }
2733
+ if ("properties" in raw && raw.properties != null && typeof raw.properties === "object") {
2734
+ const r = raw;
2735
+ return {
2736
+ ...r.properties,
2737
+ roles: r.roles
2738
+ };
2739
+ }
2740
+ return raw;
2741
+ }
2742
+
2743
+ // src/structures/MessageReaction.ts
2744
+ import { Routes as Routes19 } from "@fluxerjs/types";
2745
+ import { FluxerAPIError as FluxerAPIError4, RateLimitError as RateLimitError3 } from "@fluxerjs/rest";
2746
+ var MessageReaction = class extends Base {
2747
+ client;
2748
+ messageId;
2749
+ channelId;
2750
+ guildId;
2751
+ emoji;
2752
+ /** Raw gateway payload for low-level access. */
2753
+ _data;
2754
+ constructor(client, data) {
2755
+ super();
2756
+ this.client = client;
2757
+ this._data = data;
2758
+ this.messageId = data.message_id;
2759
+ this.channelId = data.channel_id;
2760
+ this.guildId = data.guild_id ?? null;
2761
+ this.emoji = data.emoji;
2762
+ }
2763
+ /** Emoji as a string: unicode or "name:id" for custom. */
2764
+ get emojiIdentifier() {
2765
+ return this.emoji.id ? `${this.emoji.name}:${this.emoji.id}` : this.emoji.name;
2766
+ }
2767
+ /** Guild where this reaction was added. Resolved from cache; null for DMs or if not cached. */
2768
+ get guild() {
2769
+ return this.guildId ? this.client.guilds.get(this.guildId) ?? null : null;
2770
+ }
2771
+ /**
2772
+ * Fetch the message this reaction belongs to.
2773
+ * Use when you need to edit, delete, or otherwise interact with the message.
2774
+ * @throws FluxerError with MESSAGE_NOT_FOUND if the message does not exist
2775
+ */
2776
+ async fetchMessage() {
2777
+ try {
2778
+ const data = await this.client.rest.get(
2779
+ Routes19.channelMessage(this.channelId, this.messageId)
2780
+ );
2781
+ return new Message(this.client, data);
2782
+ } catch (err) {
2783
+ if (err instanceof RateLimitError3) throw err;
2784
+ if (err instanceof FluxerAPIError4 && err.statusCode === 404) {
2785
+ throw new FluxerError(`Message ${this.messageId} not found in channel ${this.channelId}`, {
2786
+ code: ErrorCodes.MessageNotFound,
2787
+ cause: err
2788
+ });
2789
+ }
2790
+ throw err instanceof FluxerError ? err : new FluxerError(String(err), { cause: err });
2791
+ }
2792
+ }
2793
+ };
2794
+
360
2795
  // src/client/EventHandlerRegistry.ts
361
2796
  var handlers = /* @__PURE__ */ new Map();
362
2797
  handlers.set("MESSAGE_CREATE", async (client, d) => {
363
- const { Message: Message2 } = await import("./Message-R7KSI5YU.mjs");
364
- const { GuildMember: GuildMember2 } = await import("./GuildMember-FSGDPKUU.mjs");
365
2798
  const data = d;
366
2799
  if (data.guild_id && data.member && data.author) {
367
2800
  const guild = client.guilds.get(data.guild_id);
368
2801
  if (guild) {
369
2802
  const memberData = { ...data.member, user: data.author, guild_id: data.guild_id };
370
- const member = new GuildMember2(client, memberData, guild);
2803
+ const member = new GuildMember(client, memberData, guild);
371
2804
  guild.members.set(member.id, member);
372
2805
  }
373
2806
  }
374
- client.emit(Events.MessageCreate, new Message2(client, data));
2807
+ client.emit(Events.MessageCreate, new Message(client, data));
375
2808
  });
376
2809
  handlers.set("MESSAGE_UPDATE", async (client, d) => {
377
- const { Message: Message2 } = await import("./Message-R7KSI5YU.mjs");
378
- client.emit(Events.MessageUpdate, null, new Message2(client, d));
2810
+ client.emit(Events.MessageUpdate, null, new Message(client, d));
379
2811
  });
380
2812
  handlers.set("MESSAGE_DELETE", async (client, d) => {
381
2813
  const data = d;
@@ -390,41 +2822,23 @@ handlers.set("MESSAGE_DELETE", async (client, d) => {
390
2822
  });
391
2823
  handlers.set("MESSAGE_REACTION_ADD", async (client, d) => {
392
2824
  const data = d;
393
- const { MessageReaction: MessageReaction2 } = await import("./MessageReaction-EYO7AJTL.mjs");
394
- const reaction = new MessageReaction2(client, data);
2825
+ const reaction = new MessageReaction(client, data);
395
2826
  const user = client.getOrCreateUser({
396
2827
  id: data.user_id,
397
2828
  username: "Unknown",
398
2829
  discriminator: "0"
399
2830
  });
400
- client.emit(
401
- Events.MessageReactionAdd,
402
- reaction,
403
- user,
404
- reaction.messageId,
405
- reaction.channelId,
406
- reaction.emoji,
407
- user.id
408
- );
2831
+ client.emit(Events.MessageReactionAdd, reaction, user);
409
2832
  });
410
2833
  handlers.set("MESSAGE_REACTION_REMOVE", async (client, d) => {
411
2834
  const data = d;
412
- const { MessageReaction: MessageReaction2 } = await import("./MessageReaction-EYO7AJTL.mjs");
413
- const reaction = new MessageReaction2(client, data);
2835
+ const reaction = new MessageReaction(client, data);
414
2836
  const user = client.getOrCreateUser({
415
2837
  id: data.user_id,
416
2838
  username: "Unknown",
417
2839
  discriminator: "0"
418
2840
  });
419
- client.emit(
420
- Events.MessageReactionRemove,
421
- reaction,
422
- user,
423
- reaction.messageId,
424
- reaction.channelId,
425
- reaction.emoji,
426
- user.id
427
- );
2841
+ client.emit(Events.MessageReactionRemove, reaction, user);
428
2842
  });
429
2843
  handlers.set("MESSAGE_REACTION_REMOVE_ALL", async (client, d) => {
430
2844
  client.emit(Events.MessageReactionRemoveAll, d);
@@ -436,17 +2850,13 @@ handlers.set("MESSAGE_REACTION_REMOVE_EMOJI", async (client, d) => {
436
2850
  );
437
2851
  });
438
2852
  handlers.set("GUILD_CREATE", async (client, d) => {
439
- const { Guild: Guild2 } = await import("./Guild-4U4U422Z.mjs");
440
- const { Channel: Channel2 } = await import("./Channel-4YUSB2MM.mjs");
441
- const { GuildMember: GuildMember2 } = await import("./GuildMember-FSGDPKUU.mjs");
442
- const { normalizeGuildPayload } = await import("./guildUtils-FTEGCNNJ.mjs");
443
2853
  const guildData = normalizeGuildPayload(d);
444
2854
  if (!guildData) return;
445
- const guild = new Guild2(client, guildData);
2855
+ const guild = new Guild(client, guildData);
446
2856
  client.guilds.set(guild.id, guild);
447
2857
  const g = d;
448
2858
  for (const ch of g.channels ?? []) {
449
- const channel = Channel2.from(client, ch);
2859
+ const channel = Channel.from(client, ch);
450
2860
  if (channel) {
451
2861
  client.channels.set(channel.id, channel);
452
2862
  guild.channels.set(channel.id, channel);
@@ -455,7 +2865,7 @@ handlers.set("GUILD_CREATE", async (client, d) => {
455
2865
  for (const m of g.members ?? []) {
456
2866
  if (m?.user?.id) {
457
2867
  const memberData = { ...m, guild_id: guild.id };
458
- const member = new GuildMember2(client, memberData, guild);
2868
+ const member = new GuildMember(client, memberData, guild);
459
2869
  guild.members.set(member.id, member);
460
2870
  }
461
2871
  }
@@ -463,14 +2873,13 @@ handlers.set("GUILD_CREATE", async (client, d) => {
463
2873
  if (g.voice_states?.length) {
464
2874
  client.emit(Events.VoiceStatesSync, { guildId: guild.id, voiceStates: g.voice_states });
465
2875
  }
2876
+ client._onGuildReceived(guild.id);
466
2877
  });
467
2878
  handlers.set("GUILD_UPDATE", async (client, d) => {
468
- const { Guild: Guild2 } = await import("./Guild-4U4U422Z.mjs");
469
- const { normalizeGuildPayload } = await import("./guildUtils-FTEGCNNJ.mjs");
470
2879
  const guildData = normalizeGuildPayload(d);
471
2880
  if (!guildData) return;
472
2881
  const old = client.guilds.get(guildData.id);
473
- const updated = new Guild2(client, guildData);
2882
+ const updated = new Guild(client, guildData);
474
2883
  client.guilds.set(updated.id, updated);
475
2884
  client.emit(Events.GuildUpdate, old ?? updated, updated);
476
2885
  });
@@ -483,8 +2892,7 @@ handlers.set("GUILD_DELETE", async (client, d) => {
483
2892
  }
484
2893
  });
485
2894
  handlers.set("CHANNEL_CREATE", async (client, d) => {
486
- const { Channel: Channel2 } = await import("./Channel-4YUSB2MM.mjs");
487
- const ch = Channel2.from(client, d);
2895
+ const ch = Channel.from(client, d);
488
2896
  if (ch) {
489
2897
  client.channels.set(ch.id, ch);
490
2898
  if ("guildId" in ch && ch.guildId) {
@@ -495,10 +2903,9 @@ handlers.set("CHANNEL_CREATE", async (client, d) => {
495
2903
  }
496
2904
  });
497
2905
  handlers.set("CHANNEL_UPDATE", async (client, d) => {
498
- const { Channel: Channel2 } = await import("./Channel-4YUSB2MM.mjs");
499
2906
  const ch = d;
500
2907
  const oldCh = client.channels.get(ch.id);
501
- const newCh = Channel2.from(client, ch);
2908
+ const newCh = Channel.from(client, ch);
502
2909
  if (newCh) {
503
2910
  client.channels.set(newCh.id, newCh);
504
2911
  if ("guildId" in newCh && newCh.guildId) {
@@ -521,22 +2928,20 @@ handlers.set("CHANNEL_DELETE", async (client, d) => {
521
2928
  }
522
2929
  });
523
2930
  handlers.set("GUILD_MEMBER_ADD", async (client, d) => {
524
- const { GuildMember: GuildMember2 } = await import("./GuildMember-FSGDPKUU.mjs");
525
2931
  const data = d;
526
2932
  const guild = client.guilds.get(data.guild_id);
527
2933
  if (guild) {
528
- const member = new GuildMember2(client, data, guild);
2934
+ const member = new GuildMember(client, data, guild);
529
2935
  guild.members.set(member.id, member);
530
2936
  client.emit(Events.GuildMemberAdd, member);
531
2937
  }
532
2938
  });
533
2939
  handlers.set("GUILD_MEMBER_UPDATE", async (client, d) => {
534
- const { GuildMember: GuildMember2 } = await import("./GuildMember-FSGDPKUU.mjs");
535
2940
  const data = d;
536
2941
  const guild = client.guilds.get(data.guild_id);
537
2942
  if (guild) {
538
2943
  const oldM = guild.members.get(data.user.id);
539
- const newM = new GuildMember2(client, data, guild);
2944
+ const newM = new GuildMember(client, data, guild);
540
2945
  guild.members.set(newM.id, newM);
541
2946
  client.emit(Events.GuildMemberUpdate, oldM ?? newM, newM);
542
2947
  }
@@ -545,7 +2950,6 @@ handlers.set("GUILD_MEMBER_REMOVE", async (client, d) => {
545
2950
  const data = d;
546
2951
  const guild = client.guilds.get(data.guild_id);
547
2952
  if (!guild || !data.user?.id) return;
548
- const { GuildMember: GuildMember2 } = await import("./GuildMember-FSGDPKUU.mjs");
549
2953
  let member = guild.members.get(data.user.id);
550
2954
  if (member) {
551
2955
  guild.members.delete(data.user.id);
@@ -562,7 +2966,7 @@ handlers.set("GUILD_MEMBER_REMOVE", async (client, d) => {
562
2966
  joined_at: (/* @__PURE__ */ new Date(0)).toISOString(),
563
2967
  nick: null
564
2968
  };
565
- member = new GuildMember2(client, memberData, guild);
2969
+ member = new GuildMember(client, memberData, guild);
566
2970
  }
567
2971
  client.emit(Events.GuildMemberRemove, member);
568
2972
  });
@@ -580,22 +2984,36 @@ handlers.set("MESSAGE_DELETE_BULK", async (client, d) => {
580
2984
  });
581
2985
  handlers.set("GUILD_BAN_ADD", async (client, d) => {
582
2986
  const data = d;
583
- const { GuildBan: GuildBan2 } = await import("./GuildBan-7CXLTPKY.mjs");
584
2987
  const banData = {
585
2988
  user: data.user,
586
2989
  reason: data.reason ?? null,
587
2990
  guild_id: data.guild_id
588
2991
  };
589
- const ban = new GuildBan2(client, banData, data.guild_id);
2992
+ const ban = new GuildBan(client, banData, data.guild_id);
590
2993
  client.emit(Events.GuildBanAdd, ban);
591
2994
  });
592
2995
  handlers.set("GUILD_BAN_REMOVE", async (client, d) => {
593
2996
  const data = d;
594
- const { GuildBan: GuildBan2 } = await import("./GuildBan-7CXLTPKY.mjs");
595
- const ban = new GuildBan2(client, { ...data, reason: null }, data.guild_id);
2997
+ const ban = new GuildBan(client, { ...data, reason: null }, data.guild_id);
596
2998
  client.emit(Events.GuildBanRemove, ban);
597
2999
  });
598
3000
  handlers.set("GUILD_EMOJIS_UPDATE", async (client, d) => {
3001
+ const data = d;
3002
+ const guild = client.guilds.get(data.guild_id);
3003
+ if (guild) {
3004
+ guild.emojis.clear();
3005
+ for (const e of data.emojis ?? []) {
3006
+ if (!e.id || e.name == null) continue;
3007
+ guild.emojis.set(
3008
+ e.id,
3009
+ new GuildEmoji(
3010
+ client,
3011
+ { id: e.id, name: e.name, animated: e.animated ?? false, guild_id: guild.id },
3012
+ guild.id
3013
+ )
3014
+ );
3015
+ }
3016
+ }
599
3017
  client.emit(Events.GuildEmojisUpdate, d);
600
3018
  });
601
3019
  handlers.set("GUILD_STICKERS_UPDATE", async (client, d) => {
@@ -608,8 +3026,7 @@ handlers.set("GUILD_ROLE_CREATE", async (client, d) => {
608
3026
  const data = d;
609
3027
  const guild = client.guilds.get(data.guild_id);
610
3028
  if (guild) {
611
- const { Role: Role2 } = await import("./Role-SERSJFJC.mjs");
612
- guild.roles.set(data.role.id, new Role2(client, data.role, guild.id));
3029
+ guild.roles.set(data.role.id, new Role(client, data.role, guild.id));
613
3030
  }
614
3031
  client.emit(Events.GuildRoleCreate, data);
615
3032
  });
@@ -621,8 +3038,7 @@ handlers.set("GUILD_ROLE_UPDATE", async (client, d) => {
621
3038
  if (existing) {
622
3039
  existing._patch(data.role);
623
3040
  } else {
624
- const { Role: Role2 } = await import("./Role-SERSJFJC.mjs");
625
- guild.roles.set(data.role.id, new Role2(client, data.role, guild.id));
3041
+ guild.roles.set(data.role.id, new Role(client, data.role, guild.id));
626
3042
  }
627
3043
  }
628
3044
  client.emit(Events.GuildRoleUpdate, data);
@@ -647,8 +3063,7 @@ handlers.set("CHANNEL_PINS_UPDATE", async (client, d) => {
647
3063
  });
648
3064
  handlers.set("INVITE_CREATE", async (client, d) => {
649
3065
  const data = d;
650
- const { Invite: Invite2 } = await import("./Invite-UM5BU5A6.mjs");
651
- client.emit(Events.InviteCreate, new Invite2(client, data));
3066
+ client.emit(Events.InviteCreate, new Invite(client, data));
652
3067
  });
653
3068
  handlers.set("INVITE_DELETE", async (client, d) => {
654
3069
  client.emit(Events.InviteDelete, d);
@@ -686,7 +3101,7 @@ function createEventMethods(client) {
686
3101
  }
687
3102
  return result;
688
3103
  }
689
- var Client = class extends EventEmitter {
3104
+ var Client = class extends EventEmitter3 {
690
3105
  /** @param options - Token, REST config, WebSocket, presence, etc. */
691
3106
  constructor(options = {}) {
692
3107
  super();
@@ -721,6 +3136,8 @@ var Client = class extends EventEmitter {
721
3136
  /** Timestamp when the client became ready. Null until READY is received. */
722
3137
  readyAt = null;
723
3138
  _ws = null;
3139
+ /** When waitForGuilds, set of guild IDs we're waiting for GUILD_CREATE on. Null when not waiting. */
3140
+ _pendingGuildIds = null;
724
3141
  /**
725
3142
  * Resolve an emoji argument to the API format (unicode or "name:id").
726
3143
  * Supports: <:name:id>, :name:, name:id, { name, id }, unicode.
@@ -751,7 +3168,7 @@ var Client = class extends EventEmitter {
751
3168
  const unicodeFromShortcode = getUnicodeFromShortcode(parsed.name);
752
3169
  if (unicodeFromShortcode) return unicodeFromShortcode;
753
3170
  if (guildId) {
754
- const emojis = await this.rest.get(Routes4.guildEmojis(guildId));
3171
+ const emojis = await this.rest.get(Routes20.guildEmojis(guildId));
755
3172
  const list = Array.isArray(emojis) ? emojis : Object.values(emojis ?? {});
756
3173
  const found = list.find((e) => e.name && e.name.toLowerCase() === parsed.name.toLowerCase());
757
3174
  if (found) return formatEmoji({ ...parsed, id: found.id, animated: found.animated });
@@ -772,7 +3189,7 @@ var Client = class extends EventEmitter {
772
3189
  * @throws FluxerError with EMOJI_NOT_IN_GUILD if the emoji is not in the guild
773
3190
  */
774
3191
  async assertEmojiInGuild(emojiId, guildId) {
775
- const emojis = await this.rest.get(Routes4.guildEmojis(guildId));
3192
+ const emojis = await this.rest.get(Routes20.guildEmojis(guildId));
776
3193
  const list = Array.isArray(emojis) ? emojis : Object.values(emojis ?? {});
777
3194
  const found = list.some((e) => e.id === emojiId);
778
3195
  if (!found) {
@@ -786,7 +3203,7 @@ var Client = class extends EventEmitter {
786
3203
  * Does not require authentication.
787
3204
  */
788
3205
  async fetchInstance() {
789
- return this.rest.get(Routes4.instance(), { auth: false });
3206
+ return this.rest.get(Routes20.instance(), { auth: false });
790
3207
  }
791
3208
  /**
792
3209
  * Fetch a message by channel and message ID. Use when you have IDs (e.g. from a DB).
@@ -800,7 +3217,7 @@ var Client = class extends EventEmitter {
800
3217
  * const message = await channel?.messages?.fetch(messageId);
801
3218
  */
802
3219
  async fetchMessage(channelId, messageId) {
803
- emitDeprecationWarning2(
3220
+ emitDeprecationWarning3(
804
3221
  "Client.fetchMessage()",
805
3222
  "Use channel.messages.fetch(messageId). For IDs-only: (await client.channels.resolve(channelId))?.messages?.fetch(messageId)"
806
3223
  );
@@ -810,7 +3227,8 @@ var Client = class extends EventEmitter {
810
3227
  * Send a message to any channel by ID. Shorthand for client.channels.send().
811
3228
  * Works even when the channel is not cached.
812
3229
  */
813
- async sendToChannel(channelId, payload) {
3230
+ async sendToChannel(channelId, content) {
3231
+ const payload = await Message._createMessageBody(content);
814
3232
  return this.channels.send(channelId, payload);
815
3233
  }
816
3234
  /**
@@ -892,19 +3310,21 @@ var Client = class extends EventEmitter {
892
3310
  async ({
893
3311
  data
894
3312
  }) => {
895
- const { ClientUser: ClientUser2 } = await import("./ClientUser-ET7FW3XL.mjs");
896
- const { Guild: Guild2 } = await import("./Guild-4U4U422Z.mjs");
897
- const { Channel: Channel2 } = await import("./Channel-4YUSB2MM.mjs");
898
- this.user = new ClientUser2(this, data.user);
899
- const { normalizeGuildPayload } = await import("./guildUtils-FTEGCNNJ.mjs");
3313
+ this.user = new ClientUser(this, data.user);
3314
+ const waitForGuilds = this.options.waitForGuilds === true;
3315
+ const pending = waitForGuilds ? /* @__PURE__ */ new Set() : null;
900
3316
  for (const g of data.guilds ?? []) {
3317
+ if (g.unavailable === true) {
3318
+ if (pending !== null && g.id) pending.add(g.id);
3319
+ continue;
3320
+ }
901
3321
  const guildData = normalizeGuildPayload(g);
902
3322
  if (!guildData) continue;
903
- const guild = new Guild2(this, guildData);
3323
+ const guild = new Guild(this, guildData);
904
3324
  this.guilds.set(guild.id, guild);
905
3325
  const withCh = g;
906
3326
  for (const ch of withCh.channels ?? []) {
907
- const channel = Channel2.from(this, ch);
3327
+ const channel = Channel.from(this, ch);
908
3328
  if (channel) {
909
3329
  this.channels.set(channel.id, channel);
910
3330
  guild.channels.set(channel.id, channel);
@@ -917,8 +3337,11 @@ var Client = class extends EventEmitter {
917
3337
  });
918
3338
  }
919
3339
  }
920
- this.readyAt = /* @__PURE__ */ new Date();
921
- this.emit(Events.Ready);
3340
+ if (pending !== null && pending.size > 0) {
3341
+ this._pendingGuildIds = pending;
3342
+ return;
3343
+ }
3344
+ this._finalizeReady();
922
3345
  }
923
3346
  );
924
3347
  this._ws.on("error", ({ error }) => this.emit(Events.Error, error));
@@ -926,6 +3349,25 @@ var Client = class extends EventEmitter {
926
3349
  await this._ws.connect();
927
3350
  return token;
928
3351
  }
3352
+ /**
3353
+ * Called when all guilds have been received (or immediately if not waiting).
3354
+ * Sets readyAt, emits Ready, clears pending state.
3355
+ */
3356
+ _finalizeReady() {
3357
+ this._pendingGuildIds = null;
3358
+ this.readyAt = /* @__PURE__ */ new Date();
3359
+ this.emit(Events.Ready);
3360
+ }
3361
+ /**
3362
+ * Called by GUILD_CREATE handler when waitForGuilds is enabled.
3363
+ * Removes guild from pending set; when empty, finalizes ready.
3364
+ */
3365
+ _onGuildReceived(guildId) {
3366
+ const pending = this._pendingGuildIds;
3367
+ if (pending === null) return;
3368
+ pending.delete(guildId);
3369
+ if (pending.size === 0) this._finalizeReady();
3370
+ }
929
3371
  /** Disconnect from the gateway and clear cached data. */
930
3372
  async destroy() {
931
3373
  if (this._ws) {
@@ -935,6 +3377,7 @@ var Client = class extends EventEmitter {
935
3377
  this.rest.setToken(null);
936
3378
  this.user = null;
937
3379
  this.readyAt = null;
3380
+ this._pendingGuildIds = null;
938
3381
  this.guilds.clear();
939
3382
  this.channels.clear();
940
3383
  this.users.clear();
@@ -949,24 +3392,27 @@ var Client = class extends EventEmitter {
949
3392
  */
950
3393
  assertReady() {
951
3394
  if (!this.isReady()) {
952
- throw new FluxerError("Client is not ready yet. Wait for the Ready event before accessing client.user.", {
953
- code: ErrorCodes.ClientNotReady
954
- });
3395
+ throw new FluxerError(
3396
+ "Client is not ready yet. Wait for the Ready event before accessing client.user.",
3397
+ {
3398
+ code: ErrorCodes.ClientNotReady
3399
+ }
3400
+ );
955
3401
  }
956
3402
  }
957
3403
  static get Routes() {
958
- return Routes4;
3404
+ return Routes20;
959
3405
  }
960
3406
  };
961
3407
 
962
3408
  // src/index.ts
963
- import { EmbedBuilder, MessagePayload, AttachmentBuilder } from "@fluxerjs/builders";
964
- import { Routes as Routes5, GatewayOpcodes, MessageAttachmentFlags } from "@fluxerjs/types";
3409
+ import { EmbedBuilder as EmbedBuilder3, MessagePayload, AttachmentBuilder } from "@fluxerjs/builders";
3410
+ import { Routes as Routes21, GatewayOpcodes, MessageAttachmentFlags } from "@fluxerjs/types";
965
3411
  import { resolveTenorToImageUrl, parseUserMention, parsePrefixCommand } from "@fluxerjs/util";
966
3412
  import {
967
3413
  PermissionsBitField,
968
- PermissionFlags,
969
- resolvePermissionsToBitfield,
3414
+ PermissionFlags as PermissionFlags3,
3415
+ resolvePermissionsToBitfield as resolvePermissionsToBitfield3,
970
3416
  UserFlagsBitField,
971
3417
  UserFlagsBits
972
3418
  } from "@fluxerjs/util";
@@ -980,7 +3426,7 @@ export {
980
3426
  Client,
981
3427
  ClientUser,
982
3428
  DMChannel,
983
- EmbedBuilder,
3429
+ EmbedBuilder3 as EmbedBuilder,
984
3430
  ErrorCodes,
985
3431
  Events,
986
3432
  FluxerError,
@@ -991,6 +3437,7 @@ export {
991
3437
  GuildEmoji,
992
3438
  GuildMember,
993
3439
  GuildMemberManager,
3440
+ GuildMemberRoleManager,
994
3441
  GuildSticker,
995
3442
  Invite,
996
3443
  LinkChannel,
@@ -1000,11 +3447,11 @@ export {
1000
3447
  MessageManager,
1001
3448
  MessagePayload,
1002
3449
  MessageReaction,
1003
- PermissionFlags,
3450
+ PermissionFlags3 as PermissionFlags,
1004
3451
  PermissionsBitField,
1005
3452
  ReactionCollector,
1006
3453
  Role,
1007
- Routes5 as Routes,
3454
+ Routes21 as Routes,
1008
3455
  STATIC_CDN_URL,
1009
3456
  TextChannel,
1010
3457
  User,
@@ -1021,6 +3468,6 @@ export {
1021
3468
  cdnMemberBannerURL,
1022
3469
  parsePrefixCommand,
1023
3470
  parseUserMention,
1024
- resolvePermissionsToBitfield,
3471
+ resolvePermissionsToBitfield3 as resolvePermissionsToBitfield,
1025
3472
  resolveTenorToImageUrl
1026
3473
  };