@fluxerjs/core 1.2.1 → 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.d.mts CHANGED
@@ -9,6 +9,12 @@ export { AttachmentBuilder, EmbedBuilder, MessagePayload } from '@fluxerjs/build
9
9
  import { PermissionResolvable } from '@fluxerjs/util';
10
10
  export { PermissionFlags, PermissionResolvable, PermissionString, PermissionsBitField, UserFlagsBitField, UserFlagsBits, UserFlagsResolvable, UserFlagsString, parsePrefixCommand, parseUserMention, resolvePermissionsToBitfield, resolveTenorToImageUrl } from '@fluxerjs/util';
11
11
 
12
+ /** Resolved file data (after URL fetch). Used internally by REST layer. */
13
+ interface ResolvedMessageFile {
14
+ name: string;
15
+ data: Blob | ArrayBuffer | Uint8Array | Buffer;
16
+ filename?: string;
17
+ }
12
18
  /** File data for message attachment uploads. Use `data` for buffers or `url` to fetch from a URL. */
13
19
  type MessageFileData = {
14
20
  name: string;
@@ -31,7 +37,7 @@ interface MessageAttachmentMeta {
31
37
  /** Options for sending a message (content, embeds, files). Used by Message.send, Channel.send, ChannelManager.send.
32
38
  * EmbedBuilder instances are auto-converted to API format—no need to call .toJSON().
33
39
  */
34
- type MessageSendOptions = string | {
40
+ type MessageSendOptions = {
35
41
  content?: string;
36
42
  /** EmbedBuilder instances are auto-converted; raw APIEmbed also supported. */
37
43
  embeds?: (APIEmbed | EmbedBuilder)[];
@@ -40,6 +46,18 @@ type MessageSendOptions = string | {
40
46
  /** Attachment metadata for files (id = index). Use when files are provided. */
41
47
  attachments?: MessageAttachmentMeta[];
42
48
  };
49
+ /** API-ready body from MessageSendOptions (serializes EmbedBuilder, includes attachments when files present). */
50
+ interface SendBodyResult {
51
+ content?: string;
52
+ embeds?: APIEmbed[];
53
+ attachments?: Array<{
54
+ id: number;
55
+ filename: string;
56
+ title?: string | null;
57
+ description?: string | null;
58
+ flags?: number;
59
+ }>;
60
+ }
43
61
 
44
62
  /** Base class for all Fluxer structures. Provides the client reference. */
45
63
  declare abstract class Base {
@@ -908,6 +926,12 @@ interface MessageEditOptions {
908
926
  /** New embeds (replaces existing) */
909
927
  embeds?: (APIEmbed | EmbedBuilder)[];
910
928
  }
929
+ type MessagePayload = {
930
+ files?: ResolvedMessageFile[];
931
+ body: SendBodyResult & {
932
+ referenced_message?: APIMessageReference;
933
+ };
934
+ };
911
935
 
912
936
  /** Represents a message in a channel. */
913
937
  declare class Message extends Base {
@@ -940,8 +964,11 @@ declare class Message extends Base {
940
964
  readonly mentionRoles: string[];
941
965
  /** Client-side nonce for acknowledgment. Null if not provided. */
942
966
  readonly nonce: string | null;
943
- /** Channel where this message was sent. Resolved from cache; null if not cached (e.g. DM channel not in cache). */
944
- get channel(): Channel | null;
967
+ /**
968
+ * Channel where this message was sent. Resolved from cache; null if not cached.
969
+ * Messages can only exist in text-based channels (text, DM, announcement), so this always has send() when non-null.
970
+ */
971
+ get channel(): (TextChannel | DMChannel | GuildChannel) | null;
945
972
  /** Guild where this message was sent. Resolved from cache; null for DMs or if not cached. */
946
973
  get guild(): Guild | null;
947
974
  /**
@@ -965,7 +992,7 @@ declare class Message extends Base {
965
992
  * await message.send({ embeds: [embed] }); // EmbedBuilder auto-converted
966
993
  * await message.send({ content: 'File', files: [{ name: 'data.txt', data }] });
967
994
  */
968
- send(options: MessageSendOptions): Promise<Message>;
995
+ send(options: string | MessageSendOptions): Promise<Message>;
969
996
  /**
970
997
  * Send a message to a specific channel. Use for logging, forwarding, or sending to another channel in the guild.
971
998
  * @param channelId - Snowflake of the target channel (e.g. log channel ID)
@@ -982,7 +1009,14 @@ declare class Message extends Base {
982
1009
  * await message.reply('Pong!');
983
1010
  * await message.reply({ embeds: [embed] });
984
1011
  */
985
- reply(options: MessageSendOptions): Promise<Message>;
1012
+ reply(options: string | MessageSendOptions): Promise<Message>;
1013
+ /** Exposed for testing purposes, use Message.reply() or send() for normal use */
1014
+ static _createMessageBody(content: string | MessageSendOptions, referenced_message?: {
1015
+ channel_id: string;
1016
+ message_id: string;
1017
+ guild_id?: string;
1018
+ }): Promise<MessagePayload>;
1019
+ _send(payload: MessagePayload): Promise<Message>;
986
1020
  /**
987
1021
  * Edit this message. Only the author (or admins) can edit.
988
1022
  * @param options - New content and/or embeds
@@ -1162,11 +1196,12 @@ declare class Invite extends Base {
1162
1196
  /** Base class for all channel types. */
1163
1197
  declare abstract class Channel extends Base {
1164
1198
  /** Whether this channel has a send method (TextChannel, DMChannel). */
1165
- isSendable(): this is TextChannel | DMChannel;
1199
+ isTextBased(): this is TextChannel | DMChannel;
1166
1200
  /** Whether this channel is a DM or Group DM. */
1167
- isDM(): boolean;
1201
+ isDM(): this is DMChannel;
1168
1202
  /** Whether this channel is voice-based (VoiceChannel). */
1169
- isVoice(): boolean;
1203
+ isVoice(): this is VoiceChannel;
1204
+ isLink(): this is LinkChannel;
1170
1205
  /** Create a DM channel from API data (type DM or GroupDM). */
1171
1206
  static createDM(client: Client, data: APIChannelPartial): DMChannel;
1172
1207
  readonly client: Client;
@@ -1200,6 +1235,12 @@ declare abstract class Channel extends Base {
1200
1235
  * Send a typing indicator to the channel. Lasts ~10 seconds.
1201
1236
  */
1202
1237
  sendTyping(): Promise<void>;
1238
+ /**
1239
+ * Whether the bot can send messages in this channel.
1240
+ * For DMs: always true (when the channel exists).
1241
+ * For guild channels: checks ViewChannel and SendMessages permissions via guild.members.me.
1242
+ */
1243
+ canSendMessage(): boolean;
1203
1244
  }
1204
1245
  declare class GuildChannel extends Channel {
1205
1246
  readonly guildId: string;
@@ -1249,6 +1290,17 @@ declare class GuildChannel extends Channel {
1249
1290
  allow?: string;
1250
1291
  deny?: string;
1251
1292
  }): Promise<void>;
1293
+ /**
1294
+ * Whether the bot can send messages in this channel.
1295
+ * Checks ViewChannel and SendMessages via guild.members.me permissions.
1296
+ * Returns false if guild or bot member not cached.
1297
+ */
1298
+ canSendMessage(): boolean;
1299
+ /**
1300
+ * Send a message to this guild channel.
1301
+ * Works for text and announcement channels. Voice/category/link channels will fail at the API.
1302
+ */
1303
+ send(options: MessageSendOptions): Promise<Message>;
1252
1304
  /**
1253
1305
  * Remove a permission overwrite. DELETE /channels/{id}/permissions/{overwriteId}.
1254
1306
  */
@@ -1326,7 +1378,7 @@ declare class VoiceChannel extends GuildChannel {
1326
1378
  constructor(client: Client, data: APIChannel);
1327
1379
  }
1328
1380
  declare class LinkChannel extends GuildChannel {
1329
- url?: string | null;
1381
+ url: string | null;
1330
1382
  constructor(client: Client, data: APIChannel);
1331
1383
  }
1332
1384
  /** DM channel (direct message between bot and a user). */
@@ -1395,7 +1447,7 @@ declare class ChannelManager extends Collection<string, Channel | GuildChannel>
1395
1447
  * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1396
1448
  * @example
1397
1449
  * const channel = await client.channels.resolve(message.channelId);
1398
- * if (channel?.isSendable()) await channel.send('Hello!');
1450
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1399
1451
  */
1400
1452
  resolve(channelId: string): Promise<Channel>;
1401
1453
  /**
@@ -1405,7 +1457,7 @@ declare class ChannelManager extends Collection<string, Channel | GuildChannel>
1405
1457
  * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1406
1458
  * @example
1407
1459
  * const channel = await client.channels.fetch(channelId);
1408
- * if (channel?.isSendable()) await channel.send('Hello!');
1460
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1409
1461
  */
1410
1462
  fetch(channelId: string): Promise<Channel>;
1411
1463
  /**
@@ -1486,6 +1538,8 @@ interface ClientOptions {
1486
1538
  intents?: number;
1487
1539
  /** Suppress the warning when intents are set (Fluxer does not support intents yet). */
1488
1540
  suppressIntentWarning?: boolean;
1541
+ /** When true, delay the Ready event until all guilds from READY (including unavailable) have been received via GUILD_CREATE. Default: false. */
1542
+ waitForGuilds?: boolean;
1489
1543
  /** Cache size limits (channels, guilds, users). When exceeded, oldest entries are evicted. Omit or 0 = unbounded. */
1490
1544
  cache?: CacheSizeLimits;
1491
1545
  /** Initial presence (status, custom_status, etc.) sent on identify. Can also update via PresenceUpdate after connect. */
@@ -1720,6 +1774,8 @@ declare class Client extends EventEmitter {
1720
1774
  /** Timestamp when the client became ready. Null until READY is received. */
1721
1775
  readyAt: Date | null;
1722
1776
  private _ws;
1777
+ /** When waitForGuilds, set of guild IDs we're waiting for GUILD_CREATE on. Null when not waiting. */
1778
+ _pendingGuildIds: Set<string> | null;
1723
1779
  /** @param options - Token, REST config, WebSocket, presence, etc. */
1724
1780
  constructor(options?: ClientOptions);
1725
1781
  /**
@@ -1763,7 +1819,7 @@ declare class Client extends EventEmitter {
1763
1819
  * Send a message to any channel by ID. Shorthand for client.channels.send().
1764
1820
  * Works even when the channel is not cached.
1765
1821
  */
1766
- sendToChannel(channelId: string, payload: string | {
1822
+ sendToChannel(channelId: string, content: string | {
1767
1823
  content?: string;
1768
1824
  embeds?: APIEmbed[];
1769
1825
  }): Promise<Message>;
@@ -1786,6 +1842,16 @@ declare class Client extends EventEmitter {
1786
1842
  * @param token - Bot token (e.g. from FLUXER_BOT_TOKEN)
1787
1843
  */
1788
1844
  login(token: string): Promise<string>;
1845
+ /**
1846
+ * Called when all guilds have been received (or immediately if not waiting).
1847
+ * Sets readyAt, emits Ready, clears pending state.
1848
+ */
1849
+ _finalizeReady(): void;
1850
+ /**
1851
+ * Called by GUILD_CREATE handler when waitForGuilds is enabled.
1852
+ * Removes guild from pending set; when empty, finalizes ready.
1853
+ */
1854
+ _onGuildReceived(guildId: string): void;
1789
1855
  /** Disconnect from the gateway and clear cached data. */
1790
1856
  destroy(): Promise<void>;
1791
1857
  /** Returns true if the client has received Ready and `user` is set. */
package/dist/index.d.ts CHANGED
@@ -9,6 +9,12 @@ export { AttachmentBuilder, EmbedBuilder, MessagePayload } from '@fluxerjs/build
9
9
  import { PermissionResolvable } from '@fluxerjs/util';
10
10
  export { PermissionFlags, PermissionResolvable, PermissionString, PermissionsBitField, UserFlagsBitField, UserFlagsBits, UserFlagsResolvable, UserFlagsString, parsePrefixCommand, parseUserMention, resolvePermissionsToBitfield, resolveTenorToImageUrl } from '@fluxerjs/util';
11
11
 
12
+ /** Resolved file data (after URL fetch). Used internally by REST layer. */
13
+ interface ResolvedMessageFile {
14
+ name: string;
15
+ data: Blob | ArrayBuffer | Uint8Array | Buffer;
16
+ filename?: string;
17
+ }
12
18
  /** File data for message attachment uploads. Use `data` for buffers or `url` to fetch from a URL. */
13
19
  type MessageFileData = {
14
20
  name: string;
@@ -31,7 +37,7 @@ interface MessageAttachmentMeta {
31
37
  /** Options for sending a message (content, embeds, files). Used by Message.send, Channel.send, ChannelManager.send.
32
38
  * EmbedBuilder instances are auto-converted to API format—no need to call .toJSON().
33
39
  */
34
- type MessageSendOptions = string | {
40
+ type MessageSendOptions = {
35
41
  content?: string;
36
42
  /** EmbedBuilder instances are auto-converted; raw APIEmbed also supported. */
37
43
  embeds?: (APIEmbed | EmbedBuilder)[];
@@ -40,6 +46,18 @@ type MessageSendOptions = string | {
40
46
  /** Attachment metadata for files (id = index). Use when files are provided. */
41
47
  attachments?: MessageAttachmentMeta[];
42
48
  };
49
+ /** API-ready body from MessageSendOptions (serializes EmbedBuilder, includes attachments when files present). */
50
+ interface SendBodyResult {
51
+ content?: string;
52
+ embeds?: APIEmbed[];
53
+ attachments?: Array<{
54
+ id: number;
55
+ filename: string;
56
+ title?: string | null;
57
+ description?: string | null;
58
+ flags?: number;
59
+ }>;
60
+ }
43
61
 
44
62
  /** Base class for all Fluxer structures. Provides the client reference. */
45
63
  declare abstract class Base {
@@ -908,6 +926,12 @@ interface MessageEditOptions {
908
926
  /** New embeds (replaces existing) */
909
927
  embeds?: (APIEmbed | EmbedBuilder)[];
910
928
  }
929
+ type MessagePayload = {
930
+ files?: ResolvedMessageFile[];
931
+ body: SendBodyResult & {
932
+ referenced_message?: APIMessageReference;
933
+ };
934
+ };
911
935
 
912
936
  /** Represents a message in a channel. */
913
937
  declare class Message extends Base {
@@ -940,8 +964,11 @@ declare class Message extends Base {
940
964
  readonly mentionRoles: string[];
941
965
  /** Client-side nonce for acknowledgment. Null if not provided. */
942
966
  readonly nonce: string | null;
943
- /** Channel where this message was sent. Resolved from cache; null if not cached (e.g. DM channel not in cache). */
944
- get channel(): Channel | null;
967
+ /**
968
+ * Channel where this message was sent. Resolved from cache; null if not cached.
969
+ * Messages can only exist in text-based channels (text, DM, announcement), so this always has send() when non-null.
970
+ */
971
+ get channel(): (TextChannel | DMChannel | GuildChannel) | null;
945
972
  /** Guild where this message was sent. Resolved from cache; null for DMs or if not cached. */
946
973
  get guild(): Guild | null;
947
974
  /**
@@ -965,7 +992,7 @@ declare class Message extends Base {
965
992
  * await message.send({ embeds: [embed] }); // EmbedBuilder auto-converted
966
993
  * await message.send({ content: 'File', files: [{ name: 'data.txt', data }] });
967
994
  */
968
- send(options: MessageSendOptions): Promise<Message>;
995
+ send(options: string | MessageSendOptions): Promise<Message>;
969
996
  /**
970
997
  * Send a message to a specific channel. Use for logging, forwarding, or sending to another channel in the guild.
971
998
  * @param channelId - Snowflake of the target channel (e.g. log channel ID)
@@ -982,7 +1009,14 @@ declare class Message extends Base {
982
1009
  * await message.reply('Pong!');
983
1010
  * await message.reply({ embeds: [embed] });
984
1011
  */
985
- reply(options: MessageSendOptions): Promise<Message>;
1012
+ reply(options: string | MessageSendOptions): Promise<Message>;
1013
+ /** Exposed for testing purposes, use Message.reply() or send() for normal use */
1014
+ static _createMessageBody(content: string | MessageSendOptions, referenced_message?: {
1015
+ channel_id: string;
1016
+ message_id: string;
1017
+ guild_id?: string;
1018
+ }): Promise<MessagePayload>;
1019
+ _send(payload: MessagePayload): Promise<Message>;
986
1020
  /**
987
1021
  * Edit this message. Only the author (or admins) can edit.
988
1022
  * @param options - New content and/or embeds
@@ -1162,11 +1196,12 @@ declare class Invite extends Base {
1162
1196
  /** Base class for all channel types. */
1163
1197
  declare abstract class Channel extends Base {
1164
1198
  /** Whether this channel has a send method (TextChannel, DMChannel). */
1165
- isSendable(): this is TextChannel | DMChannel;
1199
+ isTextBased(): this is TextChannel | DMChannel;
1166
1200
  /** Whether this channel is a DM or Group DM. */
1167
- isDM(): boolean;
1201
+ isDM(): this is DMChannel;
1168
1202
  /** Whether this channel is voice-based (VoiceChannel). */
1169
- isVoice(): boolean;
1203
+ isVoice(): this is VoiceChannel;
1204
+ isLink(): this is LinkChannel;
1170
1205
  /** Create a DM channel from API data (type DM or GroupDM). */
1171
1206
  static createDM(client: Client, data: APIChannelPartial): DMChannel;
1172
1207
  readonly client: Client;
@@ -1200,6 +1235,12 @@ declare abstract class Channel extends Base {
1200
1235
  * Send a typing indicator to the channel. Lasts ~10 seconds.
1201
1236
  */
1202
1237
  sendTyping(): Promise<void>;
1238
+ /**
1239
+ * Whether the bot can send messages in this channel.
1240
+ * For DMs: always true (when the channel exists).
1241
+ * For guild channels: checks ViewChannel and SendMessages permissions via guild.members.me.
1242
+ */
1243
+ canSendMessage(): boolean;
1203
1244
  }
1204
1245
  declare class GuildChannel extends Channel {
1205
1246
  readonly guildId: string;
@@ -1249,6 +1290,17 @@ declare class GuildChannel extends Channel {
1249
1290
  allow?: string;
1250
1291
  deny?: string;
1251
1292
  }): Promise<void>;
1293
+ /**
1294
+ * Whether the bot can send messages in this channel.
1295
+ * Checks ViewChannel and SendMessages via guild.members.me permissions.
1296
+ * Returns false if guild or bot member not cached.
1297
+ */
1298
+ canSendMessage(): boolean;
1299
+ /**
1300
+ * Send a message to this guild channel.
1301
+ * Works for text and announcement channels. Voice/category/link channels will fail at the API.
1302
+ */
1303
+ send(options: MessageSendOptions): Promise<Message>;
1252
1304
  /**
1253
1305
  * Remove a permission overwrite. DELETE /channels/{id}/permissions/{overwriteId}.
1254
1306
  */
@@ -1326,7 +1378,7 @@ declare class VoiceChannel extends GuildChannel {
1326
1378
  constructor(client: Client, data: APIChannel);
1327
1379
  }
1328
1380
  declare class LinkChannel extends GuildChannel {
1329
- url?: string | null;
1381
+ url: string | null;
1330
1382
  constructor(client: Client, data: APIChannel);
1331
1383
  }
1332
1384
  /** DM channel (direct message between bot and a user). */
@@ -1395,7 +1447,7 @@ declare class ChannelManager extends Collection<string, Channel | GuildChannel>
1395
1447
  * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1396
1448
  * @example
1397
1449
  * const channel = await client.channels.resolve(message.channelId);
1398
- * if (channel?.isSendable()) await channel.send('Hello!');
1450
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1399
1451
  */
1400
1452
  resolve(channelId: string): Promise<Channel>;
1401
1453
  /**
@@ -1405,7 +1457,7 @@ declare class ChannelManager extends Collection<string, Channel | GuildChannel>
1405
1457
  * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1406
1458
  * @example
1407
1459
  * const channel = await client.channels.fetch(channelId);
1408
- * if (channel?.isSendable()) await channel.send('Hello!');
1460
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1409
1461
  */
1410
1462
  fetch(channelId: string): Promise<Channel>;
1411
1463
  /**
@@ -1486,6 +1538,8 @@ interface ClientOptions {
1486
1538
  intents?: number;
1487
1539
  /** Suppress the warning when intents are set (Fluxer does not support intents yet). */
1488
1540
  suppressIntentWarning?: boolean;
1541
+ /** When true, delay the Ready event until all guilds from READY (including unavailable) have been received via GUILD_CREATE. Default: false. */
1542
+ waitForGuilds?: boolean;
1489
1543
  /** Cache size limits (channels, guilds, users). When exceeded, oldest entries are evicted. Omit or 0 = unbounded. */
1490
1544
  cache?: CacheSizeLimits;
1491
1545
  /** Initial presence (status, custom_status, etc.) sent on identify. Can also update via PresenceUpdate after connect. */
@@ -1720,6 +1774,8 @@ declare class Client extends EventEmitter {
1720
1774
  /** Timestamp when the client became ready. Null until READY is received. */
1721
1775
  readyAt: Date | null;
1722
1776
  private _ws;
1777
+ /** When waitForGuilds, set of guild IDs we're waiting for GUILD_CREATE on. Null when not waiting. */
1778
+ _pendingGuildIds: Set<string> | null;
1723
1779
  /** @param options - Token, REST config, WebSocket, presence, etc. */
1724
1780
  constructor(options?: ClientOptions);
1725
1781
  /**
@@ -1763,7 +1819,7 @@ declare class Client extends EventEmitter {
1763
1819
  * Send a message to any channel by ID. Shorthand for client.channels.send().
1764
1820
  * Works even when the channel is not cached.
1765
1821
  */
1766
- sendToChannel(channelId: string, payload: string | {
1822
+ sendToChannel(channelId: string, content: string | {
1767
1823
  content?: string;
1768
1824
  embeds?: APIEmbed[];
1769
1825
  }): Promise<Message>;
@@ -1786,6 +1842,16 @@ declare class Client extends EventEmitter {
1786
1842
  * @param token - Bot token (e.g. from FLUXER_BOT_TOKEN)
1787
1843
  */
1788
1844
  login(token: string): Promise<string>;
1845
+ /**
1846
+ * Called when all guilds have been received (or immediately if not waiting).
1847
+ * Sets readyAt, emits Ready, clears pending state.
1848
+ */
1849
+ _finalizeReady(): void;
1850
+ /**
1851
+ * Called by GUILD_CREATE handler when waitForGuilds is enabled.
1852
+ * Removes guild from pending set; when empty, finalizes ready.
1853
+ */
1854
+ _onGuildReceived(guildId: string): void;
1789
1855
  /** Disconnect from the gateway and clear cached data. */
1790
1856
  destroy(): Promise<void>;
1791
1857
  /** Returns true if the client has received Ready and `user` is set. */
package/dist/index.js CHANGED
@@ -50,16 +50,16 @@ __export(index_exports, {
50
50
  MessageManager: () => MessageManager,
51
51
  MessagePayload: () => import_builders3.MessagePayload,
52
52
  MessageReaction: () => MessageReaction,
53
- PermissionFlags: () => import_util9.PermissionFlags,
54
- PermissionsBitField: () => import_util9.PermissionsBitField,
53
+ PermissionFlags: () => import_util10.PermissionFlags,
54
+ PermissionsBitField: () => import_util10.PermissionsBitField,
55
55
  ReactionCollector: () => ReactionCollector,
56
56
  Role: () => Role,
57
57
  Routes: () => import_types22.Routes,
58
58
  STATIC_CDN_URL: () => STATIC_CDN_URL,
59
59
  TextChannel: () => TextChannel,
60
60
  User: () => User,
61
- UserFlagsBitField: () => import_util9.UserFlagsBitField,
62
- UserFlagsBits: () => import_util9.UserFlagsBits,
61
+ UserFlagsBitField: () => import_util10.UserFlagsBitField,
62
+ UserFlagsBits: () => import_util10.UserFlagsBits,
63
63
  UsersManager: () => UsersManager,
64
64
  VoiceChannel: () => VoiceChannel,
65
65
  Webhook: () => Webhook,
@@ -69,10 +69,10 @@ __export(index_exports, {
69
69
  cdnDisplayAvatarURL: () => cdnDisplayAvatarURL,
70
70
  cdnMemberAvatarURL: () => cdnMemberAvatarURL,
71
71
  cdnMemberBannerURL: () => cdnMemberBannerURL,
72
- parsePrefixCommand: () => import_util8.parsePrefixCommand,
73
- parseUserMention: () => import_util8.parseUserMention,
74
- resolvePermissionsToBitfield: () => import_util9.resolvePermissionsToBitfield,
75
- resolveTenorToImageUrl: () => import_util8.resolveTenorToImageUrl
72
+ parsePrefixCommand: () => import_util9.parsePrefixCommand,
73
+ parseUserMention: () => import_util9.parseUserMention,
74
+ resolvePermissionsToBitfield: () => import_util10.resolvePermissionsToBitfield,
75
+ resolveTenorToImageUrl: () => import_util9.resolveTenorToImageUrl
76
76
  });
77
77
  module.exports = __toCommonJS(index_exports);
78
78
 
@@ -85,7 +85,7 @@ var import_types21 = require("@fluxerjs/types");
85
85
  // src/client/ChannelManager.ts
86
86
  var import_collection4 = require("@fluxerjs/collection");
87
87
  var import_types6 = require("@fluxerjs/types");
88
- var import_util2 = require("@fluxerjs/util");
88
+ var import_util3 = require("@fluxerjs/util");
89
89
  var import_rest2 = require("@fluxerjs/rest");
90
90
 
91
91
  // src/errors/FluxerError.ts
@@ -313,7 +313,10 @@ var Message = class _Message extends Base {
313
313
  mentionRoles;
314
314
  /** Client-side nonce for acknowledgment. Null if not provided. */
315
315
  nonce;
316
- /** Channel where this message was sent. Resolved from cache; null if not cached (e.g. DM channel not in cache). */
316
+ /**
317
+ * Channel where this message was sent. Resolved from cache; null if not cached.
318
+ * Messages can only exist in text-based channels (text, DM, announcement), so this always has send() when non-null.
319
+ */
317
320
  get channel() {
318
321
  return this.client.channels.get(this.channelId) ?? null;
319
322
  }
@@ -375,12 +378,8 @@ var Message = class _Message extends Base {
375
378
  * await message.send({ content: 'File', files: [{ name: 'data.txt', data }] });
376
379
  */
377
380
  async send(options) {
378
- const opts = typeof options === "string" ? { content: options } : options;
379
- const body = buildSendBody(options);
380
- const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
381
- const postOptions = files?.length ? { body, files } : { body };
382
- const data = await this.client.rest.post(import_types.Routes.channelMessages(this.channelId), postOptions);
383
- return new _Message(this.client, data);
381
+ const payload = await _Message._createMessageBody(options);
382
+ return this._send(payload);
384
383
  }
385
384
  /**
386
385
  * Send a message to a specific channel. Use for logging, forwarding, or sending to another channel in the guild.
@@ -401,19 +400,30 @@ var Message = class _Message extends Base {
401
400
  * await message.reply({ embeds: [embed] });
402
401
  */
403
402
  async reply(options) {
404
- const opts = typeof options === "string" ? { content: options } : options;
405
- const base = buildSendBody(options);
406
- const body = {
407
- ...base,
408
- message_reference: {
409
- channel_id: this.channelId,
410
- message_id: this.id,
411
- guild_id: this.guildId ?? void 0
403
+ const payload = await _Message._createMessageBody(options, {
404
+ channel_id: this.channelId,
405
+ message_id: this.id,
406
+ guild_id: this.guildId ?? void 0
407
+ });
408
+ return this._send(payload);
409
+ }
410
+ /** Exposed for testing purposes, use Message.reply() or send() for normal use */
411
+ static async _createMessageBody(content, referenced_message) {
412
+ if (typeof content === "string") {
413
+ if (content.length === 0) {
414
+ throw new RangeError("Cannot send an empty message");
412
415
  }
413
- };
414
- const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
415
- const postOptions = files?.length ? { body, files } : { body };
416
- const data = await this.client.rest.post(import_types.Routes.channelMessages(this.channelId), postOptions);
416
+ content = { content };
417
+ }
418
+ const base = buildSendBody(content);
419
+ const files = content.files?.length ? await resolveMessageFiles(content.files) : void 0;
420
+ return referenced_message ? { files, body: { ...base, referenced_message } } : { files, body: { ...base } };
421
+ }
422
+ async _send(payload) {
423
+ const data = await this.client.rest.post(
424
+ import_types.Routes.channelMessages(this.channelId),
425
+ payload
426
+ );
417
427
  return new _Message(this.client, data);
418
428
  }
419
429
  /**
@@ -630,6 +640,7 @@ var MessageCollector = class extends import_events2.EventEmitter {
630
640
  // src/structures/Channel.ts
631
641
  var import_types5 = require("@fluxerjs/types");
632
642
  var import_util = require("@fluxerjs/util");
643
+ var import_util2 = require("@fluxerjs/util");
633
644
 
634
645
  // src/structures/Webhook.ts
635
646
  var import_types3 = require("@fluxerjs/types");
@@ -764,7 +775,7 @@ var Webhook = class _Webhook extends Base {
764
775
  );
765
776
  }
766
777
  const opts = typeof options === "string" ? { content: options } : options;
767
- const body = buildSendBody(options);
778
+ const body = buildSendBody(opts);
768
779
  if (opts.username !== void 0) body.username = opts.username;
769
780
  if (opts.avatar_url !== void 0) body.avatar_url = opts.avatar_url;
770
781
  if (opts.tts !== void 0) body.tts = opts.tts;
@@ -865,7 +876,7 @@ var Invite = class extends Base {
865
876
  // src/structures/Channel.ts
866
877
  var Channel = class _Channel extends Base {
867
878
  /** Whether this channel has a send method (TextChannel, DMChannel). */
868
- isSendable() {
879
+ isTextBased() {
869
880
  return "send" in this;
870
881
  }
871
882
  /** Whether this channel is a DM or Group DM. */
@@ -876,6 +887,9 @@ var Channel = class _Channel extends Base {
876
887
  isVoice() {
877
888
  return "bitrate" in this;
878
889
  }
890
+ isLink() {
891
+ return "url" in this;
892
+ }
879
893
  /** Create a DM channel from API data (type DM or GroupDM). */
880
894
  static createDM(client, data) {
881
895
  return new DMChannel(client, data);
@@ -939,6 +953,15 @@ var Channel = class _Channel extends Base {
939
953
  async sendTyping() {
940
954
  await this.client.rest.post(import_types5.Routes.channelTyping(this.id), { auth: true });
941
955
  }
956
+ /**
957
+ * Whether the bot can send messages in this channel.
958
+ * For DMs: always true (when the channel exists).
959
+ * For guild channels: checks ViewChannel and SendMessages permissions via guild.members.me.
960
+ */
961
+ canSendMessage() {
962
+ if (this.isDM()) return true;
963
+ return false;
964
+ }
942
965
  };
943
966
  var GuildChannel = class extends Channel {
944
967
  guildId;
@@ -1022,6 +1045,31 @@ var GuildChannel = class extends Channel {
1022
1045
  if (idx >= 0) this.permissionOverwrites[idx] = entry;
1023
1046
  else this.permissionOverwrites.push(entry);
1024
1047
  }
1048
+ /**
1049
+ * Whether the bot can send messages in this channel.
1050
+ * Checks ViewChannel and SendMessages via guild.members.me permissions.
1051
+ * Returns false if guild or bot member not cached.
1052
+ */
1053
+ canSendMessage() {
1054
+ const guild = this.client.guilds.get(this.guildId);
1055
+ if (!guild) return false;
1056
+ const me = guild.members.me;
1057
+ if (!me) return false;
1058
+ const perms = me.permissionsIn(this);
1059
+ return perms.has(import_util.PermissionFlags.ViewChannel) && perms.has(import_util.PermissionFlags.SendMessages);
1060
+ }
1061
+ /**
1062
+ * Send a message to this guild channel.
1063
+ * Works for text and announcement channels. Voice/category/link channels will fail at the API.
1064
+ */
1065
+ async send(options) {
1066
+ const opts = typeof options === "string" ? { content: options } : options;
1067
+ const body = buildSendBody(options);
1068
+ const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1069
+ const postOptions = files?.length ? { body, files } : { body };
1070
+ const data = await this.client.rest.post(import_types5.Routes.channelMessages(this.id), postOptions);
1071
+ return new Message(this.client, data);
1072
+ }
1025
1073
  /**
1026
1074
  * Remove a permission overwrite. DELETE /channels/{id}/permissions/{overwriteId}.
1027
1075
  */
@@ -1122,7 +1170,7 @@ var TextChannel = class extends GuildChannel {
1122
1170
  * @deprecated Use channel.messages.fetch(messageId) instead.
1123
1171
  */
1124
1172
  async fetchMessage(messageId) {
1125
- (0, import_util.emitDeprecationWarning)(
1173
+ (0, import_util2.emitDeprecationWarning)(
1126
1174
  "Channel.fetchMessage()",
1127
1175
  "Use channel.messages.fetch(messageId) instead."
1128
1176
  );
@@ -1208,7 +1256,7 @@ var DMChannel = class extends Channel {
1208
1256
  * @deprecated Use channel.messages.fetch(messageId) instead.
1209
1257
  */
1210
1258
  async fetchMessage(messageId) {
1211
- (0, import_util.emitDeprecationWarning)(
1259
+ (0, import_util2.emitDeprecationWarning)(
1212
1260
  "Channel.fetchMessage()",
1213
1261
  "Use channel.messages.fetch(messageId) instead."
1214
1262
  );
@@ -1259,7 +1307,7 @@ var ChannelManager = class extends import_collection4.Collection {
1259
1307
  * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1260
1308
  * @example
1261
1309
  * const channel = await client.channels.resolve(message.channelId);
1262
- * if (channel?.isSendable()) await channel.send('Hello!');
1310
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1263
1311
  */
1264
1312
  async resolve(channelId) {
1265
1313
  return this.get(channelId) ?? this.fetch(channelId);
@@ -1271,7 +1319,7 @@ var ChannelManager = class extends import_collection4.Collection {
1271
1319
  * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1272
1320
  * @example
1273
1321
  * const channel = await client.channels.fetch(channelId);
1274
- * if (channel?.isSendable()) await channel.send('Hello!');
1322
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1275
1323
  */
1276
1324
  async fetch(channelId) {
1277
1325
  const cached = this.get(channelId);
@@ -1313,7 +1361,7 @@ var ChannelManager = class extends import_collection4.Collection {
1313
1361
  * const message = await channel?.messages?.fetch(messageId);
1314
1362
  */
1315
1363
  async fetchMessage(channelId, messageId) {
1316
- (0, import_util2.emitDeprecationWarning)(
1364
+ (0, import_util3.emitDeprecationWarning)(
1317
1365
  "ChannelManager.fetchMessage()",
1318
1366
  "Use channel.messages.fetch(messageId). Prefer (await client.channels.resolve(channelId))?.messages?.fetch(messageId)."
1319
1367
  );
@@ -1359,7 +1407,7 @@ var import_collection8 = require("@fluxerjs/collection");
1359
1407
  var import_types16 = require("@fluxerjs/types");
1360
1408
 
1361
1409
  // src/structures/Guild.ts
1362
- var import_util6 = require("@fluxerjs/util");
1410
+ var import_util7 = require("@fluxerjs/util");
1363
1411
  var import_rest3 = require("@fluxerjs/rest");
1364
1412
  var import_collection7 = require("@fluxerjs/collection");
1365
1413
 
@@ -1368,14 +1416,14 @@ var import_collection6 = require("@fluxerjs/collection");
1368
1416
  var import_types10 = require("@fluxerjs/types");
1369
1417
 
1370
1418
  // src/structures/GuildMember.ts
1371
- var import_util4 = require("@fluxerjs/util");
1419
+ var import_util5 = require("@fluxerjs/util");
1372
1420
  var import_types9 = require("@fluxerjs/types");
1373
1421
 
1374
1422
  // src/util/permissions.ts
1375
1423
  var import_types7 = require("@fluxerjs/types");
1376
- var import_util3 = require("@fluxerjs/util");
1424
+ var import_util4 = require("@fluxerjs/util");
1377
1425
  function computePermissions(basePermissions, overwrites, memberRoles, memberId, isOwner) {
1378
- if (isOwner) return import_util3.ALL_PERMISSIONS_BIGINT;
1426
+ if (isOwner) return import_util4.ALL_PERMISSIONS_BIGINT;
1379
1427
  let perms = basePermissions;
1380
1428
  for (const overwrite of overwrites ?? []) {
1381
1429
  const applies = overwrite.type === import_types7.OverwriteType.Role && memberRoles.includes(overwrite.id) || overwrite.type === import_types7.OverwriteType.Member && overwrite.id === memberId;
@@ -1593,7 +1641,7 @@ var GuildMember = class extends Base {
1593
1641
  const perms = computePermissions(base, [], [], this.id, isOwner);
1594
1642
  return {
1595
1643
  has(permission) {
1596
- const perm = typeof permission === "number" ? permission : import_util4.PermissionFlagsMap[String(permission)];
1644
+ const perm = typeof permission === "number" ? permission : import_util5.PermissionFlagsMap[String(permission)];
1597
1645
  if (perm === void 0) return false;
1598
1646
  return hasPermission(perms, BigInt(perm));
1599
1647
  }
@@ -1621,7 +1669,7 @@ var GuildMember = class extends Base {
1621
1669
  );
1622
1670
  return {
1623
1671
  has(permission) {
1624
- const perm = typeof permission === "number" ? permission : import_util4.PermissionFlagsMap[String(permission)];
1672
+ const perm = typeof permission === "number" ? permission : import_util5.PermissionFlagsMap[String(permission)];
1625
1673
  if (perm === void 0) return false;
1626
1674
  return hasPermission(perms, BigInt(perm));
1627
1675
  }
@@ -1718,7 +1766,7 @@ var GuildMemberManager = class extends import_collection6.Collection {
1718
1766
 
1719
1767
  // src/structures/Role.ts
1720
1768
  var import_types11 = require("@fluxerjs/types");
1721
- var import_util5 = require("@fluxerjs/util");
1769
+ var import_util6 = require("@fluxerjs/util");
1722
1770
  var Role = class extends Base {
1723
1771
  client;
1724
1772
  id;
@@ -1773,13 +1821,13 @@ var Role = class extends Base {
1773
1821
  * if (role.has('ManageChannels')) { ... }
1774
1822
  */
1775
1823
  has(permission) {
1776
- const perm = typeof permission === "number" ? permission : import_util5.PermissionFlags[permission];
1824
+ const perm = typeof permission === "number" ? permission : import_util6.PermissionFlags[permission];
1777
1825
  if (perm === void 0) return false;
1778
1826
  const permNum = Number(perm);
1779
1827
  const rolePerms = BigInt(this.permissions);
1780
1828
  const permBig = BigInt(permNum);
1781
1829
  if (permBig < 0) return false;
1782
- if ((rolePerms & BigInt(import_util5.PermissionFlags.Administrator)) !== 0n) return true;
1830
+ if ((rolePerms & BigInt(import_util6.PermissionFlags.Administrator)) !== 0n) return true;
1783
1831
  return (rolePerms & permBig) === permBig;
1784
1832
  }
1785
1833
  /**
@@ -1794,7 +1842,7 @@ var Role = class extends Base {
1794
1842
  const body = {};
1795
1843
  if (options.name !== void 0) body.name = options.name;
1796
1844
  if (options.permissions !== void 0) {
1797
- body.permissions = typeof options.permissions === "string" ? options.permissions : (0, import_util5.resolvePermissionsToBitfield)(options.permissions);
1845
+ body.permissions = typeof options.permissions === "string" ? options.permissions : (0, import_util6.resolvePermissionsToBitfield)(options.permissions);
1798
1846
  }
1799
1847
  if (options.color !== void 0) body.color = options.color;
1800
1848
  if (options.hoist !== void 0) body.hoist = options.hoist;
@@ -2065,7 +2113,7 @@ var Guild = class extends Base {
2065
2113
  const body = {};
2066
2114
  if (options.name !== void 0) body.name = options.name;
2067
2115
  if (options.permissions !== void 0) {
2068
- body.permissions = typeof options.permissions === "string" ? options.permissions : (0, import_util6.resolvePermissionsToBitfield)(options.permissions);
2116
+ body.permissions = typeof options.permissions === "string" ? options.permissions : (0, import_util7.resolvePermissionsToBitfield)(options.permissions);
2069
2117
  }
2070
2118
  if (options.color !== void 0) body.color = options.color;
2071
2119
  if (options.hoist !== void 0) body.hoist = options.hoist;
@@ -2128,7 +2176,7 @@ var Guild = class extends Base {
2128
2176
  * @returns The role ID, or null if not found
2129
2177
  */
2130
2178
  async resolveRoleId(arg) {
2131
- const parsed = (0, import_util6.parseRoleMention)(arg);
2179
+ const parsed = (0, import_util7.parseRoleMention)(arg);
2132
2180
  if (parsed) return parsed;
2133
2181
  if (/^\d{17,19}$/.test(arg.trim())) return arg.trim();
2134
2182
  const cached = this.roles.find(
@@ -2665,7 +2713,7 @@ var ClientUser = class extends User {
2665
2713
  };
2666
2714
 
2667
2715
  // src/client/Client.ts
2668
- var import_util7 = require("@fluxerjs/util");
2716
+ var import_util8 = require("@fluxerjs/util");
2669
2717
 
2670
2718
  // src/client/UsersManager.ts
2671
2719
  var import_collection9 = require("@fluxerjs/collection");
@@ -2890,6 +2938,7 @@ handlers.set("GUILD_CREATE", async (client, d) => {
2890
2938
  if (g.voice_states?.length) {
2891
2939
  client.emit(Events.VoiceStatesSync, { guildId: guild.id, voiceStates: g.voice_states });
2892
2940
  }
2941
+ client._onGuildReceived(guild.id);
2893
2942
  });
2894
2943
  handlers.set("GUILD_UPDATE", async (client, d) => {
2895
2944
  const guildData = normalizeGuildPayload(d);
@@ -3022,7 +3071,11 @@ handlers.set("GUILD_EMOJIS_UPDATE", async (client, d) => {
3022
3071
  if (!e.id || e.name == null) continue;
3023
3072
  guild.emojis.set(
3024
3073
  e.id,
3025
- new GuildEmoji(client, { id: e.id, name: e.name, animated: e.animated ?? false, guild_id: guild.id }, guild.id)
3074
+ new GuildEmoji(
3075
+ client,
3076
+ { id: e.id, name: e.name, animated: e.animated ?? false, guild_id: guild.id },
3077
+ guild.id
3078
+ )
3026
3079
  );
3027
3080
  }
3028
3081
  }
@@ -3148,6 +3201,8 @@ var Client = class extends import_events3.EventEmitter {
3148
3201
  /** Timestamp when the client became ready. Null until READY is received. */
3149
3202
  readyAt = null;
3150
3203
  _ws = null;
3204
+ /** When waitForGuilds, set of guild IDs we're waiting for GUILD_CREATE on. Null when not waiting. */
3205
+ _pendingGuildIds = null;
3151
3206
  /**
3152
3207
  * Resolve an emoji argument to the API format (unicode or "name:id").
3153
3208
  * Supports: <:name:id>, :name:, name:id, { name, id }, unicode.
@@ -3162,9 +3217,9 @@ var Client = class extends import_events3.EventEmitter {
3162
3217
  if (guildId) {
3163
3218
  await this.assertEmojiInGuild(emoji.id, guildId);
3164
3219
  }
3165
- return (0, import_util7.formatEmoji)({ name: emoji.name, id: emoji.id, animated: emoji.animated });
3220
+ return (0, import_util8.formatEmoji)({ name: emoji.name, id: emoji.id, animated: emoji.animated });
3166
3221
  }
3167
- const parsed = (0, import_util7.parseEmoji)(
3222
+ const parsed = (0, import_util8.parseEmoji)(
3168
3223
  typeof emoji === "string" ? emoji : emoji.id ? `:${emoji.name}:` : emoji.name
3169
3224
  );
3170
3225
  if (!parsed) throw new Error("Invalid emoji");
@@ -3172,16 +3227,16 @@ var Client = class extends import_events3.EventEmitter {
3172
3227
  if (guildId) {
3173
3228
  await this.assertEmojiInGuild(parsed.id, guildId);
3174
3229
  }
3175
- return (0, import_util7.formatEmoji)(parsed);
3230
+ return (0, import_util8.formatEmoji)(parsed);
3176
3231
  }
3177
3232
  if (!/^\w+$/.test(parsed.name)) return parsed.name;
3178
- const unicodeFromShortcode = (0, import_util7.getUnicodeFromShortcode)(parsed.name);
3233
+ const unicodeFromShortcode = (0, import_util8.getUnicodeFromShortcode)(parsed.name);
3179
3234
  if (unicodeFromShortcode) return unicodeFromShortcode;
3180
3235
  if (guildId) {
3181
3236
  const emojis = await this.rest.get(import_types21.Routes.guildEmojis(guildId));
3182
3237
  const list = Array.isArray(emojis) ? emojis : Object.values(emojis ?? {});
3183
3238
  const found = list.find((e) => e.name && e.name.toLowerCase() === parsed.name.toLowerCase());
3184
- if (found) return (0, import_util7.formatEmoji)({ ...parsed, id: found.id, animated: found.animated });
3239
+ if (found) return (0, import_util8.formatEmoji)({ ...parsed, id: found.id, animated: found.animated });
3185
3240
  throw new Error(
3186
3241
  `Custom emoji ":${parsed.name}:" not found in guild. Use name:id or <:name:id> format.`
3187
3242
  );
@@ -3227,7 +3282,7 @@ var Client = class extends import_events3.EventEmitter {
3227
3282
  * const message = await channel?.messages?.fetch(messageId);
3228
3283
  */
3229
3284
  async fetchMessage(channelId, messageId) {
3230
- (0, import_util7.emitDeprecationWarning)(
3285
+ (0, import_util8.emitDeprecationWarning)(
3231
3286
  "Client.fetchMessage()",
3232
3287
  "Use channel.messages.fetch(messageId). For IDs-only: (await client.channels.resolve(channelId))?.messages?.fetch(messageId)"
3233
3288
  );
@@ -3237,7 +3292,8 @@ var Client = class extends import_events3.EventEmitter {
3237
3292
  * Send a message to any channel by ID. Shorthand for client.channels.send().
3238
3293
  * Works even when the channel is not cached.
3239
3294
  */
3240
- async sendToChannel(channelId, payload) {
3295
+ async sendToChannel(channelId, content) {
3296
+ const payload = await Message._createMessageBody(content);
3241
3297
  return this.channels.send(channelId, payload);
3242
3298
  }
3243
3299
  /**
@@ -3320,7 +3376,13 @@ var Client = class extends import_events3.EventEmitter {
3320
3376
  data
3321
3377
  }) => {
3322
3378
  this.user = new ClientUser(this, data.user);
3379
+ const waitForGuilds = this.options.waitForGuilds === true;
3380
+ const pending = waitForGuilds ? /* @__PURE__ */ new Set() : null;
3323
3381
  for (const g of data.guilds ?? []) {
3382
+ if (g.unavailable === true) {
3383
+ if (pending !== null && g.id) pending.add(g.id);
3384
+ continue;
3385
+ }
3324
3386
  const guildData = normalizeGuildPayload(g);
3325
3387
  if (!guildData) continue;
3326
3388
  const guild = new Guild(this, guildData);
@@ -3340,8 +3402,11 @@ var Client = class extends import_events3.EventEmitter {
3340
3402
  });
3341
3403
  }
3342
3404
  }
3343
- this.readyAt = /* @__PURE__ */ new Date();
3344
- this.emit(Events.Ready);
3405
+ if (pending !== null && pending.size > 0) {
3406
+ this._pendingGuildIds = pending;
3407
+ return;
3408
+ }
3409
+ this._finalizeReady();
3345
3410
  }
3346
3411
  );
3347
3412
  this._ws.on("error", ({ error }) => this.emit(Events.Error, error));
@@ -3349,6 +3414,25 @@ var Client = class extends import_events3.EventEmitter {
3349
3414
  await this._ws.connect();
3350
3415
  return token;
3351
3416
  }
3417
+ /**
3418
+ * Called when all guilds have been received (or immediately if not waiting).
3419
+ * Sets readyAt, emits Ready, clears pending state.
3420
+ */
3421
+ _finalizeReady() {
3422
+ this._pendingGuildIds = null;
3423
+ this.readyAt = /* @__PURE__ */ new Date();
3424
+ this.emit(Events.Ready);
3425
+ }
3426
+ /**
3427
+ * Called by GUILD_CREATE handler when waitForGuilds is enabled.
3428
+ * Removes guild from pending set; when empty, finalizes ready.
3429
+ */
3430
+ _onGuildReceived(guildId) {
3431
+ const pending = this._pendingGuildIds;
3432
+ if (pending === null) return;
3433
+ pending.delete(guildId);
3434
+ if (pending.size === 0) this._finalizeReady();
3435
+ }
3352
3436
  /** Disconnect from the gateway and clear cached data. */
3353
3437
  async destroy() {
3354
3438
  if (this._ws) {
@@ -3358,6 +3442,7 @@ var Client = class extends import_events3.EventEmitter {
3358
3442
  this.rest.setToken(null);
3359
3443
  this.user = null;
3360
3444
  this.readyAt = null;
3445
+ this._pendingGuildIds = null;
3361
3446
  this.guilds.clear();
3362
3447
  this.channels.clear();
3363
3448
  this.users.clear();
@@ -3388,8 +3473,8 @@ var Client = class extends import_events3.EventEmitter {
3388
3473
  // src/index.ts
3389
3474
  var import_builders3 = require("@fluxerjs/builders");
3390
3475
  var import_types22 = require("@fluxerjs/types");
3391
- var import_util8 = require("@fluxerjs/util");
3392
3476
  var import_util9 = require("@fluxerjs/util");
3477
+ var import_util10 = require("@fluxerjs/util");
3393
3478
  // Annotate the CommonJS export names for ESM import in node:
3394
3479
  0 && (module.exports = {
3395
3480
  AttachmentBuilder,
package/dist/index.mjs CHANGED
@@ -237,7 +237,10 @@ var Message = class _Message extends Base {
237
237
  mentionRoles;
238
238
  /** Client-side nonce for acknowledgment. Null if not provided. */
239
239
  nonce;
240
- /** Channel where this message was sent. Resolved from cache; null if not cached (e.g. DM channel not in cache). */
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
+ */
241
244
  get channel() {
242
245
  return this.client.channels.get(this.channelId) ?? null;
243
246
  }
@@ -299,12 +302,8 @@ var Message = class _Message extends Base {
299
302
  * await message.send({ content: 'File', files: [{ name: 'data.txt', data }] });
300
303
  */
301
304
  async send(options) {
302
- const opts = typeof options === "string" ? { content: options } : options;
303
- const body = buildSendBody(options);
304
- const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
305
- const postOptions = files?.length ? { body, files } : { body };
306
- const data = await this.client.rest.post(Routes.channelMessages(this.channelId), postOptions);
307
- return new _Message(this.client, data);
305
+ const payload = await _Message._createMessageBody(options);
306
+ return this._send(payload);
308
307
  }
309
308
  /**
310
309
  * Send a message to a specific channel. Use for logging, forwarding, or sending to another channel in the guild.
@@ -325,19 +324,30 @@ var Message = class _Message extends Base {
325
324
  * await message.reply({ embeds: [embed] });
326
325
  */
327
326
  async reply(options) {
328
- const opts = typeof options === "string" ? { content: options } : options;
329
- const base = buildSendBody(options);
330
- const body = {
331
- ...base,
332
- message_reference: {
333
- channel_id: this.channelId,
334
- message_id: this.id,
335
- guild_id: this.guildId ?? void 0
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");
336
339
  }
337
- };
338
- const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
339
- const postOptions = files?.length ? { body, files } : { body };
340
- const data = await this.client.rest.post(Routes.channelMessages(this.channelId), postOptions);
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
+ );
341
351
  return new _Message(this.client, data);
342
352
  }
343
353
  /**
@@ -553,6 +563,7 @@ var MessageCollector = class extends EventEmitter2 {
553
563
 
554
564
  // src/structures/Channel.ts
555
565
  import { ChannelType, Routes as Routes5 } from "@fluxerjs/types";
566
+ import { PermissionFlags } from "@fluxerjs/util";
556
567
  import { emitDeprecationWarning } from "@fluxerjs/util";
557
568
 
558
569
  // src/structures/Webhook.ts
@@ -688,7 +699,7 @@ var Webhook = class _Webhook extends Base {
688
699
  );
689
700
  }
690
701
  const opts = typeof options === "string" ? { content: options } : options;
691
- const body = buildSendBody(options);
702
+ const body = buildSendBody(opts);
692
703
  if (opts.username !== void 0) body.username = opts.username;
693
704
  if (opts.avatar_url !== void 0) body.avatar_url = opts.avatar_url;
694
705
  if (opts.tts !== void 0) body.tts = opts.tts;
@@ -789,7 +800,7 @@ var Invite = class extends Base {
789
800
  // src/structures/Channel.ts
790
801
  var Channel = class _Channel extends Base {
791
802
  /** Whether this channel has a send method (TextChannel, DMChannel). */
792
- isSendable() {
803
+ isTextBased() {
793
804
  return "send" in this;
794
805
  }
795
806
  /** Whether this channel is a DM or Group DM. */
@@ -800,6 +811,9 @@ var Channel = class _Channel extends Base {
800
811
  isVoice() {
801
812
  return "bitrate" in this;
802
813
  }
814
+ isLink() {
815
+ return "url" in this;
816
+ }
803
817
  /** Create a DM channel from API data (type DM or GroupDM). */
804
818
  static createDM(client, data) {
805
819
  return new DMChannel(client, data);
@@ -863,6 +877,15 @@ var Channel = class _Channel extends Base {
863
877
  async sendTyping() {
864
878
  await this.client.rest.post(Routes5.channelTyping(this.id), { auth: true });
865
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
+ }
866
889
  };
867
890
  var GuildChannel = class extends Channel {
868
891
  guildId;
@@ -946,6 +969,31 @@ var GuildChannel = class extends Channel {
946
969
  if (idx >= 0) this.permissionOverwrites[idx] = entry;
947
970
  else this.permissionOverwrites.push(entry);
948
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
+ }
949
997
  /**
950
998
  * Remove a permission overwrite. DELETE /channels/{id}/permissions/{overwriteId}.
951
999
  */
@@ -1183,7 +1231,7 @@ var ChannelManager = class extends Collection4 {
1183
1231
  * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1184
1232
  * @example
1185
1233
  * const channel = await client.channels.resolve(message.channelId);
1186
- * if (channel?.isSendable()) await channel.send('Hello!');
1234
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1187
1235
  */
1188
1236
  async resolve(channelId) {
1189
1237
  return this.get(channelId) ?? this.fetch(channelId);
@@ -1195,7 +1243,7 @@ var ChannelManager = class extends Collection4 {
1195
1243
  * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1196
1244
  * @example
1197
1245
  * const channel = await client.channels.fetch(channelId);
1198
- * if (channel?.isSendable()) await channel.send('Hello!');
1246
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1199
1247
  */
1200
1248
  async fetch(channelId) {
1201
1249
  const cached = this.get(channelId);
@@ -1646,7 +1694,7 @@ var GuildMemberManager = class extends Collection6 {
1646
1694
  // src/structures/Role.ts
1647
1695
  import { Routes as Routes10 } from "@fluxerjs/types";
1648
1696
  import {
1649
- PermissionFlags,
1697
+ PermissionFlags as PermissionFlags2,
1650
1698
  resolvePermissionsToBitfield
1651
1699
  } from "@fluxerjs/util";
1652
1700
  var Role = class extends Base {
@@ -1703,13 +1751,13 @@ var Role = class extends Base {
1703
1751
  * if (role.has('ManageChannels')) { ... }
1704
1752
  */
1705
1753
  has(permission) {
1706
- const perm = typeof permission === "number" ? permission : PermissionFlags[permission];
1754
+ const perm = typeof permission === "number" ? permission : PermissionFlags2[permission];
1707
1755
  if (perm === void 0) return false;
1708
1756
  const permNum = Number(perm);
1709
1757
  const rolePerms = BigInt(this.permissions);
1710
1758
  const permBig = BigInt(permNum);
1711
1759
  if (permBig < 0) return false;
1712
- if ((rolePerms & BigInt(PermissionFlags.Administrator)) !== 0n) return true;
1760
+ if ((rolePerms & BigInt(PermissionFlags2.Administrator)) !== 0n) return true;
1713
1761
  return (rolePerms & permBig) === permBig;
1714
1762
  }
1715
1763
  /**
@@ -2825,6 +2873,7 @@ handlers.set("GUILD_CREATE", async (client, d) => {
2825
2873
  if (g.voice_states?.length) {
2826
2874
  client.emit(Events.VoiceStatesSync, { guildId: guild.id, voiceStates: g.voice_states });
2827
2875
  }
2876
+ client._onGuildReceived(guild.id);
2828
2877
  });
2829
2878
  handlers.set("GUILD_UPDATE", async (client, d) => {
2830
2879
  const guildData = normalizeGuildPayload(d);
@@ -2957,7 +3006,11 @@ handlers.set("GUILD_EMOJIS_UPDATE", async (client, d) => {
2957
3006
  if (!e.id || e.name == null) continue;
2958
3007
  guild.emojis.set(
2959
3008
  e.id,
2960
- new GuildEmoji(client, { id: e.id, name: e.name, animated: e.animated ?? false, guild_id: guild.id }, guild.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
+ )
2961
3014
  );
2962
3015
  }
2963
3016
  }
@@ -3083,6 +3136,8 @@ var Client = class extends EventEmitter3 {
3083
3136
  /** Timestamp when the client became ready. Null until READY is received. */
3084
3137
  readyAt = null;
3085
3138
  _ws = null;
3139
+ /** When waitForGuilds, set of guild IDs we're waiting for GUILD_CREATE on. Null when not waiting. */
3140
+ _pendingGuildIds = null;
3086
3141
  /**
3087
3142
  * Resolve an emoji argument to the API format (unicode or "name:id").
3088
3143
  * Supports: <:name:id>, :name:, name:id, { name, id }, unicode.
@@ -3172,7 +3227,8 @@ var Client = class extends EventEmitter3 {
3172
3227
  * Send a message to any channel by ID. Shorthand for client.channels.send().
3173
3228
  * Works even when the channel is not cached.
3174
3229
  */
3175
- async sendToChannel(channelId, payload) {
3230
+ async sendToChannel(channelId, content) {
3231
+ const payload = await Message._createMessageBody(content);
3176
3232
  return this.channels.send(channelId, payload);
3177
3233
  }
3178
3234
  /**
@@ -3255,7 +3311,13 @@ var Client = class extends EventEmitter3 {
3255
3311
  data
3256
3312
  }) => {
3257
3313
  this.user = new ClientUser(this, data.user);
3314
+ const waitForGuilds = this.options.waitForGuilds === true;
3315
+ const pending = waitForGuilds ? /* @__PURE__ */ new Set() : null;
3258
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
+ }
3259
3321
  const guildData = normalizeGuildPayload(g);
3260
3322
  if (!guildData) continue;
3261
3323
  const guild = new Guild(this, guildData);
@@ -3275,8 +3337,11 @@ var Client = class extends EventEmitter3 {
3275
3337
  });
3276
3338
  }
3277
3339
  }
3278
- this.readyAt = /* @__PURE__ */ new Date();
3279
- this.emit(Events.Ready);
3340
+ if (pending !== null && pending.size > 0) {
3341
+ this._pendingGuildIds = pending;
3342
+ return;
3343
+ }
3344
+ this._finalizeReady();
3280
3345
  }
3281
3346
  );
3282
3347
  this._ws.on("error", ({ error }) => this.emit(Events.Error, error));
@@ -3284,6 +3349,25 @@ var Client = class extends EventEmitter3 {
3284
3349
  await this._ws.connect();
3285
3350
  return token;
3286
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
+ }
3287
3371
  /** Disconnect from the gateway and clear cached data. */
3288
3372
  async destroy() {
3289
3373
  if (this._ws) {
@@ -3293,6 +3377,7 @@ var Client = class extends EventEmitter3 {
3293
3377
  this.rest.setToken(null);
3294
3378
  this.user = null;
3295
3379
  this.readyAt = null;
3380
+ this._pendingGuildIds = null;
3296
3381
  this.guilds.clear();
3297
3382
  this.channels.clear();
3298
3383
  this.users.clear();
@@ -3326,7 +3411,7 @@ import { Routes as Routes21, GatewayOpcodes, MessageAttachmentFlags } from "@flu
3326
3411
  import { resolveTenorToImageUrl, parseUserMention, parsePrefixCommand } from "@fluxerjs/util";
3327
3412
  import {
3328
3413
  PermissionsBitField,
3329
- PermissionFlags as PermissionFlags2,
3414
+ PermissionFlags as PermissionFlags3,
3330
3415
  resolvePermissionsToBitfield as resolvePermissionsToBitfield3,
3331
3416
  UserFlagsBitField,
3332
3417
  UserFlagsBits
@@ -3362,7 +3447,7 @@ export {
3362
3447
  MessageManager,
3363
3448
  MessagePayload,
3364
3449
  MessageReaction,
3365
- PermissionFlags2 as PermissionFlags,
3450
+ PermissionFlags3 as PermissionFlags,
3366
3451
  PermissionsBitField,
3367
3452
  ReactionCollector,
3368
3453
  Role,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.2.1",
6
+ "version": "1.2.2",
7
7
  "description": "A fully-featured SDK for Fluxer bots",
8
8
  "repository": {
9
9
  "type": "git",
@@ -34,12 +34,12 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
- "@fluxerjs/rest": "1.2.1",
38
- "@fluxerjs/ws": "1.2.1",
39
- "@fluxerjs/builders": "1.2.1",
40
- "@fluxerjs/collection": "1.2.1",
41
- "@fluxerjs/util": "1.2.1",
42
- "@fluxerjs/types": "1.2.1"
37
+ "@fluxerjs/rest": "1.2.2",
38
+ "@fluxerjs/ws": "1.2.2",
39
+ "@fluxerjs/util": "1.2.2",
40
+ "@fluxerjs/collection": "1.2.2",
41
+ "@fluxerjs/builders": "1.2.2",
42
+ "@fluxerjs/types": "1.2.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^20.0.0",