@fluxerjs/core 1.2.2 → 1.2.3

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
@@ -6,7 +6,7 @@ export { GatewayOpcodes, MessageAttachmentFlags, Routes } from '@fluxerjs/types'
6
6
  import { Collection } from '@fluxerjs/collection';
7
7
  import { EmbedBuilder } from '@fluxerjs/builders';
8
8
  export { AttachmentBuilder, EmbedBuilder, MessagePayload } from '@fluxerjs/builders';
9
- import { PermissionResolvable } from '@fluxerjs/util';
9
+ import { PermissionsBitField, PermissionResolvable, BitField } from '@fluxerjs/util';
10
10
  export { PermissionFlags, PermissionResolvable, PermissionString, PermissionsBitField, UserFlagsBitField, UserFlagsBits, UserFlagsResolvable, UserFlagsString, parsePrefixCommand, parseUserMention, resolvePermissionsToBitfield, resolveTenorToImageUrl } from '@fluxerjs/util';
11
11
 
12
12
  /** Resolved file data (after URL fetch). Used internally by REST layer. */
@@ -45,8 +45,10 @@ type MessageSendOptions = {
45
45
  files?: MessageFileData[];
46
46
  /** Attachment metadata for files (id = index). Use when files are provided. */
47
47
  attachments?: MessageAttachmentMeta[];
48
+ /** Message flags (e.g. MessageFlags.SuppressNotifications for reply without ping). */
49
+ flags?: number;
48
50
  };
49
- /** API-ready body from MessageSendOptions (serializes EmbedBuilder, includes attachments when files present). */
51
+ /** API-ready body from MessageSendOptions or text content (serializes EmbedBuilder, includes attachments when files present). */
50
52
  interface SendBodyResult {
51
53
  content?: string;
52
54
  embeds?: APIEmbed[];
@@ -57,6 +59,8 @@ interface SendBodyResult {
57
59
  description?: string | null;
58
60
  flags?: number;
59
61
  }>;
62
+ /** Message flags (e.g. SuppressNotifications for reply without ping). */
63
+ flags?: number;
60
64
  }
61
65
 
62
66
  /** Base class for all Fluxer structures. Provides the client reference. */
@@ -130,7 +134,7 @@ declare class Role extends Base {
130
134
  name: string;
131
135
  color: number;
132
136
  position: number;
133
- permissions: string;
137
+ _permissions: string;
134
138
  hoist: boolean;
135
139
  mentionable: boolean;
136
140
  unicodeEmoji: string | null;
@@ -140,19 +144,11 @@ declare class Role extends Base {
140
144
  /** @param data - API role from GET /guilds/{id}/roles or gateway role events */
141
145
  /** @param guildId - The guild this role belongs to */
142
146
  constructor(client: Client, data: APIRole, guildId: string);
147
+ get permissions(): PermissionsBitField;
143
148
  /** Update mutable fields from fresh API data. Used by edit and gateway events. */
144
149
  _patch(data: Partial<APIRole>): void;
145
150
  /** Returns a mention string (e.g. `<@&123456>`). */
146
151
  toString(): string;
147
- /**
148
- * Check if this role has a permission. Administrator grants all permissions.
149
- * @param permission - Permission flag, name, or resolvable
150
- * @returns true if the role has the permission
151
- * @example
152
- * if (role.has(PermissionFlags.BanMembers)) { ... }
153
- * if (role.has('ManageChannels')) { ... }
154
- */
155
- has(permission: PermissionResolvable): boolean;
156
152
  /**
157
153
  * Edit this role.
158
154
  * Requires Manage Roles permission.
@@ -332,9 +328,7 @@ declare class GuildMember extends Base {
332
328
  * const perms = member.permissions;
333
329
  * if (perms.has(PermissionFlags.BanMembers)) { ... }
334
330
  */
335
- get permissions(): {
336
- has(permission: PermissionResolvable): boolean;
337
- };
331
+ get permissions(): BitField<"CreateInstantInvite" | "KickMembers" | "BanMembers" | "Administrator" | "ManageChannels" | "ManageGuild" | "AddReactions" | "ViewAuditLog" | "PrioritySpeaker" | "Stream" | "ViewChannel" | "SendMessages" | "SendTtsMessages" | "ManageMessages" | "EmbedLinks" | "AttachFiles" | "ReadMessageHistory" | "MentionEveryone" | "UseExternalEmojis" | "Connect" | "Speak" | "MuteMembers" | "DeafenMembers" | "MoveMembers" | "UseVad" | "ChangeNickname" | "ManageNicknames" | "ManageRoles" | "ManageWebhooks" | "ManageEmojisAndStickers" | "ManageExpressions" | "UseExternalStickers" | "ModerateMembers" | "CreateExpressions" | "PinMessages" | "BypassSlowmode" | "UpdateRtcRegion">;
338
332
  /**
339
333
  * Compute the member's effective permissions in a guild channel.
340
334
  * Applies role permissions and channel overwrites.
@@ -344,9 +338,7 @@ declare class GuildMember extends Base {
344
338
  * const perms = member.permissionsIn(channel);
345
339
  * if (perms.has(PermissionFlags.SendMessages)) { ... }
346
340
  */
347
- permissionsIn(channel: GuildChannel): {
348
- has(permission: PermissionResolvable): boolean;
349
- };
341
+ permissionsIn(channel: GuildChannel): BitField<"CreateInstantInvite" | "KickMembers" | "BanMembers" | "Administrator" | "ManageChannels" | "ManageGuild" | "AddReactions" | "ViewAuditLog" | "PrioritySpeaker" | "Stream" | "ViewChannel" | "SendMessages" | "SendTtsMessages" | "ManageMessages" | "EmbedLinks" | "AttachFiles" | "ReadMessageHistory" | "MentionEveryone" | "UseExternalEmojis" | "Connect" | "Speak" | "MuteMembers" | "DeafenMembers" | "MoveMembers" | "UseVad" | "ChangeNickname" | "ManageNicknames" | "ManageRoles" | "ManageWebhooks" | "ManageEmojisAndStickers" | "ManageExpressions" | "UseExternalStickers" | "ModerateMembers" | "CreateExpressions" | "PinMessages" | "BypassSlowmode" | "UpdateRtcRegion">;
350
342
  private _computeBasePermissions;
351
343
  }
352
344
 
@@ -929,9 +921,20 @@ interface MessageEditOptions {
929
921
  type MessagePayload = {
930
922
  files?: ResolvedMessageFile[];
931
923
  body: SendBodyResult & {
932
- referenced_message?: APIMessageReference;
924
+ message_reference?: APIMessageReference;
925
+ flags?: number;
933
926
  };
934
927
  };
928
+ /** Options for message.reply() — ping toggle and reply-to-different-message. */
929
+ interface ReplyOptions {
930
+ /** Whether to ping the replied-to user (default true). Use false to suppress the mention notification. */
931
+ ping?: boolean;
932
+ /** Reply to a different message instead of this one. Default: this message. */
933
+ replyTo?: Message | {
934
+ channelId: string;
935
+ messageId: string;
936
+ };
937
+ }
935
938
 
936
939
  /** Represents a message in a channel. */
937
940
  declare class Message extends Base {
@@ -1004,18 +1007,20 @@ declare class Message extends Base {
1004
1007
  sendTo(channelId: string, options: MessageSendOptions): Promise<Message>;
1005
1008
  /**
1006
1009
  * Reply to this message (shows as a reply in the client).
1007
- * @param options - Text content or object with content, embeds, and/or files
1010
+ * @param options - Text content or object with content, embeds, and/or reply options (ping, replyTo)
1008
1011
  * @example
1009
1012
  * await message.reply('Pong!');
1010
1013
  * await message.reply({ embeds: [embed] });
1014
+ * await message.reply('No ping!', { ping: false });
1015
+ * await message.reply({ content: 'Reply to other', replyTo: otherMessage });
1011
1016
  */
1012
- reply(options: string | MessageSendOptions): Promise<Message>;
1017
+ reply(options: string | (MessageSendOptions & ReplyOptions), replyOptions?: ReplyOptions): Promise<Message>;
1013
1018
  /** Exposed for testing purposes, use Message.reply() or send() for normal use */
1014
1019
  static _createMessageBody(content: string | MessageSendOptions, referenced_message?: {
1015
1020
  channel_id: string;
1016
1021
  message_id: string;
1017
1022
  guild_id?: string;
1018
- }): Promise<MessagePayload>;
1023
+ }, ping?: boolean): Promise<MessagePayload>;
1019
1024
  _send(payload: MessagePayload): Promise<Message>;
1020
1025
  /**
1021
1026
  * Edit this message. Only the author (or admins) can edit.
@@ -1111,15 +1116,23 @@ declare class Message extends Base {
1111
1116
  /**
1112
1117
  * Manages messages for a channel. Access via channel.messages.
1113
1118
  * @example
1114
- * const message = await channel.messages.fetch(messageId);
1119
+ * const message = channel.messages.get(messageId); // from cache (if enabled)
1120
+ * const message = await channel.messages.fetch(messageId); // from API
1115
1121
  * if (message) await message.edit({ content: 'Updated!' });
1116
1122
  */
1117
1123
  declare class MessageManager {
1118
1124
  private readonly client;
1119
1125
  private readonly channelId;
1120
1126
  constructor(client: Client, channelId: string);
1127
+ /**
1128
+ * Get a message from cache. Returns undefined if not cached or caching is disabled.
1129
+ * Requires options.cache.messages > 0.
1130
+ * @param messageId - Snowflake of the message
1131
+ */
1132
+ get(messageId: string): Message | undefined;
1121
1133
  /**
1122
1134
  * Fetch a message by ID from this channel.
1135
+ * When message caching is enabled, the fetched message is added to the cache.
1123
1136
  * @param messageId - Snowflake of the message
1124
1137
  * @returns The message
1125
1138
  * @throws FluxerError with MESSAGE_NOT_FOUND if the message does not exist
@@ -1483,7 +1496,7 @@ declare class ChannelManager extends Collection<string, Channel | GuildChannel>
1483
1496
  * await client.channels.send(channelId, { embeds: [embed] });
1484
1497
  * await client.channels.send(channelId, { content: 'Report', files: [{ name: 'log.txt', data }] });
1485
1498
  */
1486
- send(channelId: string, payload: MessageSendOptions): Promise<Message>;
1499
+ send(channelId: string, payload: string | MessageSendOptions): Promise<Message>;
1487
1500
  }
1488
1501
 
1489
1502
  /**
@@ -1531,6 +1544,8 @@ interface CacheSizeLimits {
1531
1544
  channels?: number;
1532
1545
  guilds?: number;
1533
1546
  users?: number;
1547
+ /** Max messages per channel to cache. Enables channel.messages.get() and oldMessage in messageUpdate. 0 = disabled. */
1548
+ messages?: number;
1534
1549
  }
1535
1550
  interface ClientOptions {
1536
1551
  rest?: Partial<ConstructorParameters<typeof REST>[0]>;
@@ -1776,6 +1791,8 @@ declare class Client extends EventEmitter {
1776
1791
  private _ws;
1777
1792
  /** When waitForGuilds, set of guild IDs we're waiting for GUILD_CREATE on. Null when not waiting. */
1778
1793
  _pendingGuildIds: Set<string> | null;
1794
+ /** Per-channel message cache (channelId -> messageId -> APIMessage). Used when options.cache.messages > 0. */
1795
+ private _messageCaches;
1779
1796
  /** @param options - Token, REST config, WebSocket, presence, etc. */
1780
1797
  constructor(options?: ClientOptions);
1781
1798
  /**
@@ -1818,11 +1835,25 @@ declare class Client extends EventEmitter {
1818
1835
  /**
1819
1836
  * Send a message to any channel by ID. Shorthand for client.channels.send().
1820
1837
  * Works even when the channel is not cached.
1838
+ * @deprecated Use client.channels.send(channelId, payload).
1839
+ */
1840
+ sendToChannel(channelId: string, payload: string | MessageSendOptions): Promise<Message>;
1841
+ /**
1842
+ * Get the message cache for a channel. Returns null if message caching is disabled.
1843
+ * Used by MessageManager.get() and event handlers.
1844
+ * @internal
1845
+ */
1846
+ _getMessageCache(channelId: string): Map<string, APIMessage> | null;
1847
+ /**
1848
+ * Add a message to the channel cache. Evicts oldest (FIFO) when over limit.
1849
+ * @internal
1850
+ */
1851
+ _addMessageToCache(channelId: string, data: APIMessage): void;
1852
+ /**
1853
+ * Remove a message from the channel cache.
1854
+ * @internal
1821
1855
  */
1822
- sendToChannel(channelId: string, content: string | {
1823
- content?: string;
1824
- embeds?: APIEmbed[];
1825
- }): Promise<Message>;
1856
+ _removeMessageFromCache(channelId: string, messageId: string): void;
1826
1857
  /**
1827
1858
  * Get or create a User from API data. Caches in client.users.
1828
1859
  * Updates existing user's username, avatar, etc. when fresh data is provided.
@@ -1957,4 +1988,4 @@ declare function cdnMemberBannerURL(guildId: string, userId: string, bannerHash:
1957
1988
  */
1958
1989
  declare function cdnDefaultAvatarURL(userIdOrIndex: string | number): string;
1959
1990
 
1960
- export { Base, CDN_URL, CategoryChannel, type CdnUrlOptions, Channel, ChannelManager, Client, type ClientEventMethods, type ClientEvents, ClientUser, type CollectedReaction, DMChannel, ErrorCodes, Events, type FetchedUserWithProfile, FluxerError, type FluxerErrorOptions, Guild, GuildBan, GuildChannel, GuildEmoji, GuildMember, GuildMemberManager, GuildMemberRoleManager, GuildSticker, Invite, LinkChannel, Message, MessageCollector, type MessageCollectorEndReason, type MessageCollectorOptions, type MessageEditOptions, MessageManager, MessageReaction, type MessageSendOptions, type PartialMessage, ReactionCollector, type ReactionCollectorEndReason, type ReactionCollectorOptions, Role, type RoleResolvable, STATIC_CDN_URL, TextChannel, User, UsersManager, VoiceChannel, Webhook, type WebhookSendOptions, cdnAvatarURL, cdnBannerURL, cdnDefaultAvatarURL, cdnDisplayAvatarURL, cdnMemberAvatarURL, cdnMemberBannerURL };
1991
+ export { Base, CDN_URL, CategoryChannel, type CdnUrlOptions, Channel, ChannelManager, Client, type ClientEventMethods, type ClientEvents, ClientUser, type CollectedReaction, DMChannel, ErrorCodes, Events, type FetchedUserWithProfile, FluxerError, type FluxerErrorOptions, Guild, GuildBan, GuildChannel, GuildEmoji, GuildMember, GuildMemberManager, GuildMemberRoleManager, GuildSticker, Invite, LinkChannel, Message, MessageCollector, type MessageCollectorEndReason, type MessageCollectorOptions, type MessageEditOptions, MessageManager, MessageReaction, type MessageSendOptions, type PartialMessage, ReactionCollector, type ReactionCollectorEndReason, type ReactionCollectorOptions, type ReplyOptions, Role, type RoleResolvable, STATIC_CDN_URL, TextChannel, User, UsersManager, VoiceChannel, Webhook, type WebhookSendOptions, cdnAvatarURL, cdnBannerURL, cdnDefaultAvatarURL, cdnDisplayAvatarURL, cdnMemberAvatarURL, cdnMemberBannerURL };
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export { GatewayOpcodes, MessageAttachmentFlags, Routes } from '@fluxerjs/types'
6
6
  import { Collection } from '@fluxerjs/collection';
7
7
  import { EmbedBuilder } from '@fluxerjs/builders';
8
8
  export { AttachmentBuilder, EmbedBuilder, MessagePayload } from '@fluxerjs/builders';
9
- import { PermissionResolvable } from '@fluxerjs/util';
9
+ import { PermissionsBitField, PermissionResolvable, BitField } from '@fluxerjs/util';
10
10
  export { PermissionFlags, PermissionResolvable, PermissionString, PermissionsBitField, UserFlagsBitField, UserFlagsBits, UserFlagsResolvable, UserFlagsString, parsePrefixCommand, parseUserMention, resolvePermissionsToBitfield, resolveTenorToImageUrl } from '@fluxerjs/util';
11
11
 
12
12
  /** Resolved file data (after URL fetch). Used internally by REST layer. */
@@ -45,8 +45,10 @@ type MessageSendOptions = {
45
45
  files?: MessageFileData[];
46
46
  /** Attachment metadata for files (id = index). Use when files are provided. */
47
47
  attachments?: MessageAttachmentMeta[];
48
+ /** Message flags (e.g. MessageFlags.SuppressNotifications for reply without ping). */
49
+ flags?: number;
48
50
  };
49
- /** API-ready body from MessageSendOptions (serializes EmbedBuilder, includes attachments when files present). */
51
+ /** API-ready body from MessageSendOptions or text content (serializes EmbedBuilder, includes attachments when files present). */
50
52
  interface SendBodyResult {
51
53
  content?: string;
52
54
  embeds?: APIEmbed[];
@@ -57,6 +59,8 @@ interface SendBodyResult {
57
59
  description?: string | null;
58
60
  flags?: number;
59
61
  }>;
62
+ /** Message flags (e.g. SuppressNotifications for reply without ping). */
63
+ flags?: number;
60
64
  }
61
65
 
62
66
  /** Base class for all Fluxer structures. Provides the client reference. */
@@ -130,7 +134,7 @@ declare class Role extends Base {
130
134
  name: string;
131
135
  color: number;
132
136
  position: number;
133
- permissions: string;
137
+ _permissions: string;
134
138
  hoist: boolean;
135
139
  mentionable: boolean;
136
140
  unicodeEmoji: string | null;
@@ -140,19 +144,11 @@ declare class Role extends Base {
140
144
  /** @param data - API role from GET /guilds/{id}/roles or gateway role events */
141
145
  /** @param guildId - The guild this role belongs to */
142
146
  constructor(client: Client, data: APIRole, guildId: string);
147
+ get permissions(): PermissionsBitField;
143
148
  /** Update mutable fields from fresh API data. Used by edit and gateway events. */
144
149
  _patch(data: Partial<APIRole>): void;
145
150
  /** Returns a mention string (e.g. `<@&123456>`). */
146
151
  toString(): string;
147
- /**
148
- * Check if this role has a permission. Administrator grants all permissions.
149
- * @param permission - Permission flag, name, or resolvable
150
- * @returns true if the role has the permission
151
- * @example
152
- * if (role.has(PermissionFlags.BanMembers)) { ... }
153
- * if (role.has('ManageChannels')) { ... }
154
- */
155
- has(permission: PermissionResolvable): boolean;
156
152
  /**
157
153
  * Edit this role.
158
154
  * Requires Manage Roles permission.
@@ -332,9 +328,7 @@ declare class GuildMember extends Base {
332
328
  * const perms = member.permissions;
333
329
  * if (perms.has(PermissionFlags.BanMembers)) { ... }
334
330
  */
335
- get permissions(): {
336
- has(permission: PermissionResolvable): boolean;
337
- };
331
+ get permissions(): BitField<"CreateInstantInvite" | "KickMembers" | "BanMembers" | "Administrator" | "ManageChannels" | "ManageGuild" | "AddReactions" | "ViewAuditLog" | "PrioritySpeaker" | "Stream" | "ViewChannel" | "SendMessages" | "SendTtsMessages" | "ManageMessages" | "EmbedLinks" | "AttachFiles" | "ReadMessageHistory" | "MentionEveryone" | "UseExternalEmojis" | "Connect" | "Speak" | "MuteMembers" | "DeafenMembers" | "MoveMembers" | "UseVad" | "ChangeNickname" | "ManageNicknames" | "ManageRoles" | "ManageWebhooks" | "ManageEmojisAndStickers" | "ManageExpressions" | "UseExternalStickers" | "ModerateMembers" | "CreateExpressions" | "PinMessages" | "BypassSlowmode" | "UpdateRtcRegion">;
338
332
  /**
339
333
  * Compute the member's effective permissions in a guild channel.
340
334
  * Applies role permissions and channel overwrites.
@@ -344,9 +338,7 @@ declare class GuildMember extends Base {
344
338
  * const perms = member.permissionsIn(channel);
345
339
  * if (perms.has(PermissionFlags.SendMessages)) { ... }
346
340
  */
347
- permissionsIn(channel: GuildChannel): {
348
- has(permission: PermissionResolvable): boolean;
349
- };
341
+ permissionsIn(channel: GuildChannel): BitField<"CreateInstantInvite" | "KickMembers" | "BanMembers" | "Administrator" | "ManageChannels" | "ManageGuild" | "AddReactions" | "ViewAuditLog" | "PrioritySpeaker" | "Stream" | "ViewChannel" | "SendMessages" | "SendTtsMessages" | "ManageMessages" | "EmbedLinks" | "AttachFiles" | "ReadMessageHistory" | "MentionEveryone" | "UseExternalEmojis" | "Connect" | "Speak" | "MuteMembers" | "DeafenMembers" | "MoveMembers" | "UseVad" | "ChangeNickname" | "ManageNicknames" | "ManageRoles" | "ManageWebhooks" | "ManageEmojisAndStickers" | "ManageExpressions" | "UseExternalStickers" | "ModerateMembers" | "CreateExpressions" | "PinMessages" | "BypassSlowmode" | "UpdateRtcRegion">;
350
342
  private _computeBasePermissions;
351
343
  }
352
344
 
@@ -929,9 +921,20 @@ interface MessageEditOptions {
929
921
  type MessagePayload = {
930
922
  files?: ResolvedMessageFile[];
931
923
  body: SendBodyResult & {
932
- referenced_message?: APIMessageReference;
924
+ message_reference?: APIMessageReference;
925
+ flags?: number;
933
926
  };
934
927
  };
928
+ /** Options for message.reply() — ping toggle and reply-to-different-message. */
929
+ interface ReplyOptions {
930
+ /** Whether to ping the replied-to user (default true). Use false to suppress the mention notification. */
931
+ ping?: boolean;
932
+ /** Reply to a different message instead of this one. Default: this message. */
933
+ replyTo?: Message | {
934
+ channelId: string;
935
+ messageId: string;
936
+ };
937
+ }
935
938
 
936
939
  /** Represents a message in a channel. */
937
940
  declare class Message extends Base {
@@ -1004,18 +1007,20 @@ declare class Message extends Base {
1004
1007
  sendTo(channelId: string, options: MessageSendOptions): Promise<Message>;
1005
1008
  /**
1006
1009
  * Reply to this message (shows as a reply in the client).
1007
- * @param options - Text content or object with content, embeds, and/or files
1010
+ * @param options - Text content or object with content, embeds, and/or reply options (ping, replyTo)
1008
1011
  * @example
1009
1012
  * await message.reply('Pong!');
1010
1013
  * await message.reply({ embeds: [embed] });
1014
+ * await message.reply('No ping!', { ping: false });
1015
+ * await message.reply({ content: 'Reply to other', replyTo: otherMessage });
1011
1016
  */
1012
- reply(options: string | MessageSendOptions): Promise<Message>;
1017
+ reply(options: string | (MessageSendOptions & ReplyOptions), replyOptions?: ReplyOptions): Promise<Message>;
1013
1018
  /** Exposed for testing purposes, use Message.reply() or send() for normal use */
1014
1019
  static _createMessageBody(content: string | MessageSendOptions, referenced_message?: {
1015
1020
  channel_id: string;
1016
1021
  message_id: string;
1017
1022
  guild_id?: string;
1018
- }): Promise<MessagePayload>;
1023
+ }, ping?: boolean): Promise<MessagePayload>;
1019
1024
  _send(payload: MessagePayload): Promise<Message>;
1020
1025
  /**
1021
1026
  * Edit this message. Only the author (or admins) can edit.
@@ -1111,15 +1116,23 @@ declare class Message extends Base {
1111
1116
  /**
1112
1117
  * Manages messages for a channel. Access via channel.messages.
1113
1118
  * @example
1114
- * const message = await channel.messages.fetch(messageId);
1119
+ * const message = channel.messages.get(messageId); // from cache (if enabled)
1120
+ * const message = await channel.messages.fetch(messageId); // from API
1115
1121
  * if (message) await message.edit({ content: 'Updated!' });
1116
1122
  */
1117
1123
  declare class MessageManager {
1118
1124
  private readonly client;
1119
1125
  private readonly channelId;
1120
1126
  constructor(client: Client, channelId: string);
1127
+ /**
1128
+ * Get a message from cache. Returns undefined if not cached or caching is disabled.
1129
+ * Requires options.cache.messages > 0.
1130
+ * @param messageId - Snowflake of the message
1131
+ */
1132
+ get(messageId: string): Message | undefined;
1121
1133
  /**
1122
1134
  * Fetch a message by ID from this channel.
1135
+ * When message caching is enabled, the fetched message is added to the cache.
1123
1136
  * @param messageId - Snowflake of the message
1124
1137
  * @returns The message
1125
1138
  * @throws FluxerError with MESSAGE_NOT_FOUND if the message does not exist
@@ -1483,7 +1496,7 @@ declare class ChannelManager extends Collection<string, Channel | GuildChannel>
1483
1496
  * await client.channels.send(channelId, { embeds: [embed] });
1484
1497
  * await client.channels.send(channelId, { content: 'Report', files: [{ name: 'log.txt', data }] });
1485
1498
  */
1486
- send(channelId: string, payload: MessageSendOptions): Promise<Message>;
1499
+ send(channelId: string, payload: string | MessageSendOptions): Promise<Message>;
1487
1500
  }
1488
1501
 
1489
1502
  /**
@@ -1531,6 +1544,8 @@ interface CacheSizeLimits {
1531
1544
  channels?: number;
1532
1545
  guilds?: number;
1533
1546
  users?: number;
1547
+ /** Max messages per channel to cache. Enables channel.messages.get() and oldMessage in messageUpdate. 0 = disabled. */
1548
+ messages?: number;
1534
1549
  }
1535
1550
  interface ClientOptions {
1536
1551
  rest?: Partial<ConstructorParameters<typeof REST>[0]>;
@@ -1776,6 +1791,8 @@ declare class Client extends EventEmitter {
1776
1791
  private _ws;
1777
1792
  /** When waitForGuilds, set of guild IDs we're waiting for GUILD_CREATE on. Null when not waiting. */
1778
1793
  _pendingGuildIds: Set<string> | null;
1794
+ /** Per-channel message cache (channelId -> messageId -> APIMessage). Used when options.cache.messages > 0. */
1795
+ private _messageCaches;
1779
1796
  /** @param options - Token, REST config, WebSocket, presence, etc. */
1780
1797
  constructor(options?: ClientOptions);
1781
1798
  /**
@@ -1818,11 +1835,25 @@ declare class Client extends EventEmitter {
1818
1835
  /**
1819
1836
  * Send a message to any channel by ID. Shorthand for client.channels.send().
1820
1837
  * Works even when the channel is not cached.
1838
+ * @deprecated Use client.channels.send(channelId, payload).
1839
+ */
1840
+ sendToChannel(channelId: string, payload: string | MessageSendOptions): Promise<Message>;
1841
+ /**
1842
+ * Get the message cache for a channel. Returns null if message caching is disabled.
1843
+ * Used by MessageManager.get() and event handlers.
1844
+ * @internal
1845
+ */
1846
+ _getMessageCache(channelId: string): Map<string, APIMessage> | null;
1847
+ /**
1848
+ * Add a message to the channel cache. Evicts oldest (FIFO) when over limit.
1849
+ * @internal
1850
+ */
1851
+ _addMessageToCache(channelId: string, data: APIMessage): void;
1852
+ /**
1853
+ * Remove a message from the channel cache.
1854
+ * @internal
1821
1855
  */
1822
- sendToChannel(channelId: string, content: string | {
1823
- content?: string;
1824
- embeds?: APIEmbed[];
1825
- }): Promise<Message>;
1856
+ _removeMessageFromCache(channelId: string, messageId: string): void;
1826
1857
  /**
1827
1858
  * Get or create a User from API data. Caches in client.users.
1828
1859
  * Updates existing user's username, avatar, etc. when fresh data is provided.
@@ -1957,4 +1988,4 @@ declare function cdnMemberBannerURL(guildId: string, userId: string, bannerHash:
1957
1988
  */
1958
1989
  declare function cdnDefaultAvatarURL(userIdOrIndex: string | number): string;
1959
1990
 
1960
- export { Base, CDN_URL, CategoryChannel, type CdnUrlOptions, Channel, ChannelManager, Client, type ClientEventMethods, type ClientEvents, ClientUser, type CollectedReaction, DMChannel, ErrorCodes, Events, type FetchedUserWithProfile, FluxerError, type FluxerErrorOptions, Guild, GuildBan, GuildChannel, GuildEmoji, GuildMember, GuildMemberManager, GuildMemberRoleManager, GuildSticker, Invite, LinkChannel, Message, MessageCollector, type MessageCollectorEndReason, type MessageCollectorOptions, type MessageEditOptions, MessageManager, MessageReaction, type MessageSendOptions, type PartialMessage, ReactionCollector, type ReactionCollectorEndReason, type ReactionCollectorOptions, Role, type RoleResolvable, STATIC_CDN_URL, TextChannel, User, UsersManager, VoiceChannel, Webhook, type WebhookSendOptions, cdnAvatarURL, cdnBannerURL, cdnDefaultAvatarURL, cdnDisplayAvatarURL, cdnMemberAvatarURL, cdnMemberBannerURL };
1991
+ export { Base, CDN_URL, CategoryChannel, type CdnUrlOptions, Channel, ChannelManager, Client, type ClientEventMethods, type ClientEvents, ClientUser, type CollectedReaction, DMChannel, ErrorCodes, Events, type FetchedUserWithProfile, FluxerError, type FluxerErrorOptions, Guild, GuildBan, GuildChannel, GuildEmoji, GuildMember, GuildMemberManager, GuildMemberRoleManager, GuildSticker, Invite, LinkChannel, Message, MessageCollector, type MessageCollectorEndReason, type MessageCollectorOptions, type MessageEditOptions, MessageManager, MessageReaction, type MessageSendOptions, type PartialMessage, ReactionCollector, type ReactionCollectorEndReason, type ReactionCollectorOptions, type ReplyOptions, Role, type RoleResolvable, STATIC_CDN_URL, TextChannel, User, UsersManager, VoiceChannel, Webhook, type WebhookSendOptions, cdnAvatarURL, cdnBannerURL, cdnDefaultAvatarURL, cdnDisplayAvatarURL, cdnMemberAvatarURL, cdnMemberBannerURL };
package/dist/index.js CHANGED
@@ -162,6 +162,7 @@ function buildSendBody(options) {
162
162
  filename: f.filename ?? f.name
163
163
  }));
164
164
  }
165
+ if (body.flags !== void 0) result.flags = body.flags;
165
166
  return result;
166
167
  }
167
168
 
@@ -394,21 +395,23 @@ var Message = class _Message extends Base {
394
395
  }
395
396
  /**
396
397
  * Reply to this message (shows as a reply in the client).
397
- * @param options - Text content or object with content, embeds, and/or files
398
+ * @param options - Text content or object with content, embeds, and/or reply options (ping, replyTo)
398
399
  * @example
399
400
  * await message.reply('Pong!');
400
401
  * await message.reply({ embeds: [embed] });
402
+ * await message.reply('No ping!', { ping: false });
403
+ * await message.reply({ content: 'Reply to other', replyTo: otherMessage });
401
404
  */
402
- async reply(options) {
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
- });
405
+ async reply(options, replyOptions) {
406
+ const opts = typeof options === "string" ? { content: options } : options;
407
+ const mergedReply = replyOptions ?? (opts.ping !== void 0 || opts.replyTo !== void 0) ? { ping: opts.ping, replyTo: opts.replyTo } : void 0;
408
+ const refMessage = mergedReply?.replyTo ?? this;
409
+ const ref = refMessage instanceof _Message ? { channel_id: refMessage.channelId, message_id: refMessage.id, guild_id: refMessage.guildId ?? void 0 } : { channel_id: refMessage.channelId, message_id: refMessage.messageId, guild_id: void 0 };
410
+ const payload = await _Message._createMessageBody(opts, ref, mergedReply?.ping !== false);
408
411
  return this._send(payload);
409
412
  }
410
413
  /** Exposed for testing purposes, use Message.reply() or send() for normal use */
411
- static async _createMessageBody(content, referenced_message) {
414
+ static async _createMessageBody(content, referenced_message, ping) {
412
415
  if (typeof content === "string") {
413
416
  if (content.length === 0) {
414
417
  throw new RangeError("Cannot send an empty message");
@@ -417,13 +420,21 @@ var Message = class _Message extends Base {
417
420
  }
418
421
  const base = buildSendBody(content);
419
422
  const files = content.files?.length ? await resolveMessageFiles(content.files) : void 0;
420
- return referenced_message ? { files, body: { ...base, referenced_message } } : { files, body: { ...base } };
423
+ const body = { ...base };
424
+ if (referenced_message) {
425
+ body.message_reference = referenced_message;
426
+ if (ping === false) {
427
+ body.flags = (body.flags ?? 0) | import_types.MessageFlags.SuppressNotifications;
428
+ }
429
+ }
430
+ return { files, body };
421
431
  }
422
432
  async _send(payload) {
423
433
  const data = await this.client.rest.post(
424
434
  import_types.Routes.channelMessages(this.channelId),
425
435
  payload
426
436
  );
437
+ this.client._addMessageToCache(this.channelId, data);
427
438
  return new _Message(this.client, data);
428
439
  }
429
440
  /**
@@ -560,8 +571,18 @@ var MessageManager = class {
560
571
  this.client = client;
561
572
  this.channelId = channelId;
562
573
  }
574
+ /**
575
+ * Get a message from cache. Returns undefined if not cached or caching is disabled.
576
+ * Requires options.cache.messages > 0.
577
+ * @param messageId - Snowflake of the message
578
+ */
579
+ get(messageId) {
580
+ const data = this.client._getMessageCache(this.channelId)?.get(messageId);
581
+ return data ? new Message(this.client, data) : void 0;
582
+ }
563
583
  /**
564
584
  * Fetch a message by ID from this channel.
585
+ * When message caching is enabled, the fetched message is added to the cache.
565
586
  * @param messageId - Snowflake of the message
566
587
  * @returns The message
567
588
  * @throws FluxerError with MESSAGE_NOT_FOUND if the message does not exist
@@ -571,6 +592,7 @@ var MessageManager = class {
571
592
  const data = await this.client.rest.get(
572
593
  import_types2.Routes.channelMessage(this.channelId, messageId)
573
594
  );
595
+ this.client._addMessageToCache(this.channelId, data);
574
596
  return new Message(this.client, data);
575
597
  } catch (err) {
576
598
  if (err instanceof import_rest.RateLimitError) throw err;
@@ -1068,6 +1090,7 @@ var GuildChannel = class extends Channel {
1068
1090
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1069
1091
  const postOptions = files?.length ? { body, files } : { body };
1070
1092
  const data = await this.client.rest.post(import_types5.Routes.channelMessages(this.id), postOptions);
1093
+ this.client._addMessageToCache(this.id, data);
1071
1094
  return new Message(this.client, data);
1072
1095
  }
1073
1096
  /**
@@ -1133,6 +1156,7 @@ var TextChannel = class extends GuildChannel {
1133
1156
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1134
1157
  const postOptions = files?.length ? { body, files } : { body };
1135
1158
  const data = await this.client.rest.post(import_types5.Routes.channelMessages(this.id), postOptions);
1159
+ this.client._addMessageToCache(this.id, data);
1136
1160
  return new Message(this.client, data);
1137
1161
  }
1138
1162
  /** Message manager for this channel. Use channel.messages.fetch(messageId). */
@@ -1224,6 +1248,7 @@ var DMChannel = class extends Channel {
1224
1248
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1225
1249
  const postOptions = files?.length ? { body, files } : { body };
1226
1250
  const data = await this.client.rest.post(import_types5.Routes.channelMessages(this.id), postOptions);
1251
+ this.client._addMessageToCache(this.id, data);
1227
1252
  return new Message(this.client, data);
1228
1253
  }
1229
1254
  /** Message manager for this channel. Use channel.messages.fetch(messageId). */
@@ -1398,6 +1423,7 @@ var ChannelManager = class extends import_collection4.Collection {
1398
1423
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1399
1424
  const postOptions = files?.length ? { body, files } : { body };
1400
1425
  const data = await this.client.rest.post(import_types6.Routes.channelMessages(channelId), postOptions);
1426
+ this.client._addMessageToCache(channelId, data);
1401
1427
  return new Message(this.client, data);
1402
1428
  }
1403
1429
  };
@@ -1432,12 +1458,7 @@ function computePermissions(basePermissions, overwrites, memberRoles, memberId,
1432
1458
  const deny = BigInt(overwrite.deny || "0");
1433
1459
  perms = perms & ~deny | allow;
1434
1460
  }
1435
- return perms;
1436
- }
1437
- function hasPermission(bitfield, permission) {
1438
- const Administrator = 1n << 3n;
1439
- if ((bitfield & Administrator) !== 0n) return true;
1440
- return (bitfield & permission) === permission;
1461
+ return (perms & import_util4.PermissionFlags.Administrator) !== 0n ? import_util4.ALL_PERMISSIONS_BIGINT : perms;
1441
1462
  }
1442
1463
 
1443
1464
  // src/structures/GuildMemberRoleManager.ts
@@ -1639,13 +1660,7 @@ var GuildMember = class extends Base {
1639
1660
  const ownerId = this.guild.ownerId;
1640
1661
  const isOwner = ownerId != null && ownerId !== "" && String(ownerId) === String(this.id);
1641
1662
  const perms = computePermissions(base, [], [], this.id, isOwner);
1642
- return {
1643
- has(permission) {
1644
- const perm = typeof permission === "number" ? permission : import_util5.PermissionFlagsMap[String(permission)];
1645
- if (perm === void 0) return false;
1646
- return hasPermission(perms, BigInt(perm));
1647
- }
1648
- };
1663
+ return new import_util5.BitField(perms);
1649
1664
  }
1650
1665
  /**
1651
1666
  * Compute the member's effective permissions in a guild channel.
@@ -1667,22 +1682,16 @@ var GuildMember = class extends Base {
1667
1682
  this.id,
1668
1683
  isOwner
1669
1684
  );
1670
- return {
1671
- has(permission) {
1672
- const perm = typeof permission === "number" ? permission : import_util5.PermissionFlagsMap[String(permission)];
1673
- if (perm === void 0) return false;
1674
- return hasPermission(perms, BigInt(perm));
1675
- }
1676
- };
1685
+ return new import_util5.BitField(perms);
1677
1686
  }
1678
1687
  _computeBasePermissions() {
1679
1688
  let base = 0n;
1680
1689
  const everyone = this.guild.roles.get(this.guild.id);
1681
- if (everyone) base |= BigInt(everyone.permissions);
1690
+ if (everyone) base |= everyone.permissions.bitfield;
1682
1691
  for (const roleId of this.roles.roleIds) {
1683
1692
  if (roleId === this.guild.id) continue;
1684
1693
  const role = this.guild.roles.get(roleId);
1685
- if (role) base |= BigInt(role.permissions);
1694
+ if (role) base |= role.permissions.bitfield;
1686
1695
  }
1687
1696
  return base;
1688
1697
  }
@@ -1774,7 +1783,7 @@ var Role = class extends Base {
1774
1783
  name;
1775
1784
  color;
1776
1785
  position;
1777
- permissions;
1786
+ _permissions;
1778
1787
  hoist;
1779
1788
  mentionable;
1780
1789
  unicodeEmoji;
@@ -1791,18 +1800,24 @@ var Role = class extends Base {
1791
1800
  this.name = data.name;
1792
1801
  this.color = data.color;
1793
1802
  this.position = data.position;
1794
- this.permissions = data.permissions;
1803
+ this._permissions = data.permissions;
1795
1804
  this.hoist = !!data.hoist;
1796
1805
  this.mentionable = !!data.mentionable;
1797
1806
  this.unicodeEmoji = data.unicode_emoji ?? null;
1798
1807
  this.hoistPosition = data.hoist_position ?? null;
1799
1808
  }
1809
+ get permissions() {
1810
+ const bits = BigInt(this._permissions);
1811
+ return new import_util6.PermissionsBitField(
1812
+ (bits & import_util6.PermissionFlags.Administrator) !== 0n ? import_util6.ALL_PERMISSIONS_BIGINT : bits
1813
+ );
1814
+ }
1800
1815
  /** Update mutable fields from fresh API data. Used by edit and gateway events. */
1801
1816
  _patch(data) {
1802
1817
  if (data.name !== void 0) this.name = data.name;
1803
1818
  if (data.color !== void 0) this.color = data.color;
1804
1819
  if (data.position !== void 0) this.position = data.position;
1805
- if (data.permissions !== void 0) this.permissions = data.permissions;
1820
+ if (data.permissions !== void 0) this._permissions = data.permissions;
1806
1821
  if (data.hoist !== void 0) this.hoist = !!data.hoist;
1807
1822
  if (data.mentionable !== void 0) this.mentionable = !!data.mentionable;
1808
1823
  if (data.unicode_emoji !== void 0) this.unicodeEmoji = data.unicode_emoji ?? null;
@@ -1812,24 +1827,6 @@ var Role = class extends Base {
1812
1827
  toString() {
1813
1828
  return `<@&${this.id}>`;
1814
1829
  }
1815
- /**
1816
- * Check if this role has a permission. Administrator grants all permissions.
1817
- * @param permission - Permission flag, name, or resolvable
1818
- * @returns true if the role has the permission
1819
- * @example
1820
- * if (role.has(PermissionFlags.BanMembers)) { ... }
1821
- * if (role.has('ManageChannels')) { ... }
1822
- */
1823
- has(permission) {
1824
- const perm = typeof permission === "number" ? permission : import_util6.PermissionFlags[permission];
1825
- if (perm === void 0) return false;
1826
- const permNum = Number(perm);
1827
- const rolePerms = BigInt(this.permissions);
1828
- const permBig = BigInt(permNum);
1829
- if (permBig < 0) return false;
1830
- if ((rolePerms & BigInt(import_util6.PermissionFlags.Administrator)) !== 0n) return true;
1831
- return (rolePerms & permBig) === permBig;
1832
- }
1833
1830
  /**
1834
1831
  * Edit this role.
1835
1832
  * Requires Manage Roles permission.
@@ -2869,13 +2866,28 @@ handlers.set("MESSAGE_CREATE", async (client, d) => {
2869
2866
  guild.members.set(member.id, member);
2870
2867
  }
2871
2868
  }
2869
+ client._addMessageToCache(data.channel_id, data);
2872
2870
  client.emit(Events.MessageCreate, new Message(client, data));
2873
2871
  });
2874
2872
  handlers.set("MESSAGE_UPDATE", async (client, d) => {
2875
- client.emit(Events.MessageUpdate, null, new Message(client, d));
2873
+ const partial = d;
2874
+ const cache = client._getMessageCache(partial.channel_id);
2875
+ let oldMessage = null;
2876
+ let mergedData = partial;
2877
+ if (cache) {
2878
+ const oldData = cache.get(partial.id);
2879
+ if (oldData) {
2880
+ oldMessage = new Message(client, oldData);
2881
+ mergedData = { ...oldData, ...partial };
2882
+ }
2883
+ cache.set(partial.id, mergedData);
2884
+ }
2885
+ const newMessage = new Message(client, mergedData);
2886
+ client.emit(Events.MessageUpdate, oldMessage, newMessage);
2876
2887
  });
2877
2888
  handlers.set("MESSAGE_DELETE", async (client, d) => {
2878
2889
  const data = d;
2890
+ client._removeMessageFromCache(data.channel_id, data.id);
2879
2891
  const channel = client.channels.get(data.channel_id) ?? null;
2880
2892
  client.emit(Events.MessageDelete, {
2881
2893
  id: data.id,
@@ -3045,7 +3057,11 @@ handlers.set("VOICE_SERVER_UPDATE", async (client, d) => {
3045
3057
  client.emit(Events.VoiceServerUpdate, d);
3046
3058
  });
3047
3059
  handlers.set("MESSAGE_DELETE_BULK", async (client, d) => {
3048
- client.emit(Events.MessageDeleteBulk, d);
3060
+ const data = d;
3061
+ for (const id of data.ids ?? []) {
3062
+ client._removeMessageFromCache(data.channel_id, id);
3063
+ }
3064
+ client.emit(Events.MessageDeleteBulk, data);
3049
3065
  });
3050
3066
  handlers.set("GUILD_BAN_ADD", async (client, d) => {
3051
3067
  const data = d;
@@ -3203,6 +3219,8 @@ var Client = class extends import_events3.EventEmitter {
3203
3219
  _ws = null;
3204
3220
  /** When waitForGuilds, set of guild IDs we're waiting for GUILD_CREATE on. Null when not waiting. */
3205
3221
  _pendingGuildIds = null;
3222
+ /** Per-channel message cache (channelId -> messageId -> APIMessage). Used when options.cache.messages > 0. */
3223
+ _messageCaches = null;
3206
3224
  /**
3207
3225
  * Resolve an emoji argument to the API format (unicode or "name:id").
3208
3226
  * Supports: <:name:id>, :name:, name:id, { name, id }, unicode.
@@ -3291,11 +3309,52 @@ var Client = class extends import_events3.EventEmitter {
3291
3309
  /**
3292
3310
  * Send a message to any channel by ID. Shorthand for client.channels.send().
3293
3311
  * Works even when the channel is not cached.
3312
+ * @deprecated Use client.channels.send(channelId, payload).
3294
3313
  */
3295
- async sendToChannel(channelId, content) {
3296
- const payload = await Message._createMessageBody(content);
3314
+ async sendToChannel(channelId, payload) {
3315
+ (0, import_util8.emitDeprecationWarning)(
3316
+ "Client.sendToChannel()",
3317
+ "Use client.channels.send(channelId, payload)."
3318
+ );
3297
3319
  return this.channels.send(channelId, payload);
3298
3320
  }
3321
+ /**
3322
+ * Get the message cache for a channel. Returns null if message caching is disabled.
3323
+ * Used by MessageManager.get() and event handlers.
3324
+ * @internal
3325
+ */
3326
+ _getMessageCache(channelId) {
3327
+ const limit = this.options.cache?.messages ?? 0;
3328
+ if (limit <= 0) return null;
3329
+ if (!this._messageCaches) this._messageCaches = /* @__PURE__ */ new Map();
3330
+ let cache = this._messageCaches.get(channelId);
3331
+ if (!cache) {
3332
+ cache = /* @__PURE__ */ new Map();
3333
+ this._messageCaches.set(channelId, cache);
3334
+ }
3335
+ return cache;
3336
+ }
3337
+ /**
3338
+ * Add a message to the channel cache. Evicts oldest (FIFO) when over limit.
3339
+ * @internal
3340
+ */
3341
+ _addMessageToCache(channelId, data) {
3342
+ const cache = this._getMessageCache(channelId);
3343
+ if (!cache) return;
3344
+ const limit = this.options.cache?.messages ?? 0;
3345
+ if (limit > 0 && cache.size >= limit && !cache.has(data.id)) {
3346
+ const firstKey = cache.keys().next().value;
3347
+ if (firstKey !== void 0) cache.delete(firstKey);
3348
+ }
3349
+ cache.set(data.id, { ...data });
3350
+ }
3351
+ /**
3352
+ * Remove a message from the channel cache.
3353
+ * @internal
3354
+ */
3355
+ _removeMessageFromCache(channelId, messageId) {
3356
+ this._messageCaches?.get(channelId)?.delete(messageId);
3357
+ }
3299
3358
  /**
3300
3359
  * Get or create a User from API data. Caches in client.users.
3301
3360
  * Updates existing user's username, avatar, etc. when fresh data is provided.
package/dist/index.mjs CHANGED
@@ -86,6 +86,7 @@ function buildSendBody(options) {
86
86
  filename: f.filename ?? f.name
87
87
  }));
88
88
  }
89
+ if (body.flags !== void 0) result.flags = body.flags;
89
90
  return result;
90
91
  }
91
92
 
@@ -99,7 +100,7 @@ var Base = class {
99
100
 
100
101
  // src/structures/Message.ts
101
102
  import { Collection as Collection2 } from "@fluxerjs/collection";
102
- import { MessageType, Routes } from "@fluxerjs/types";
103
+ import { MessageType, MessageFlags, Routes } from "@fluxerjs/types";
103
104
  import { EmbedBuilder as EmbedBuilder2 } from "@fluxerjs/builders";
104
105
 
105
106
  // src/util/ReactionCollector.ts
@@ -318,21 +319,23 @@ var Message = class _Message extends Base {
318
319
  }
319
320
  /**
320
321
  * 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
+ * @param options - Text content or object with content, embeds, and/or reply options (ping, replyTo)
322
323
  * @example
323
324
  * await message.reply('Pong!');
324
325
  * await message.reply({ embeds: [embed] });
326
+ * await message.reply('No ping!', { ping: false });
327
+ * await message.reply({ content: 'Reply to other', replyTo: otherMessage });
325
328
  */
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
- });
329
+ async reply(options, replyOptions) {
330
+ const opts = typeof options === "string" ? { content: options } : options;
331
+ const mergedReply = replyOptions ?? (opts.ping !== void 0 || opts.replyTo !== void 0) ? { ping: opts.ping, replyTo: opts.replyTo } : void 0;
332
+ const refMessage = mergedReply?.replyTo ?? this;
333
+ const ref = refMessage instanceof _Message ? { channel_id: refMessage.channelId, message_id: refMessage.id, guild_id: refMessage.guildId ?? void 0 } : { channel_id: refMessage.channelId, message_id: refMessage.messageId, guild_id: void 0 };
334
+ const payload = await _Message._createMessageBody(opts, ref, mergedReply?.ping !== false);
332
335
  return this._send(payload);
333
336
  }
334
337
  /** Exposed for testing purposes, use Message.reply() or send() for normal use */
335
- static async _createMessageBody(content, referenced_message) {
338
+ static async _createMessageBody(content, referenced_message, ping) {
336
339
  if (typeof content === "string") {
337
340
  if (content.length === 0) {
338
341
  throw new RangeError("Cannot send an empty message");
@@ -341,13 +344,21 @@ var Message = class _Message extends Base {
341
344
  }
342
345
  const base = buildSendBody(content);
343
346
  const files = content.files?.length ? await resolveMessageFiles(content.files) : void 0;
344
- return referenced_message ? { files, body: { ...base, referenced_message } } : { files, body: { ...base } };
347
+ const body = { ...base };
348
+ if (referenced_message) {
349
+ body.message_reference = referenced_message;
350
+ if (ping === false) {
351
+ body.flags = (body.flags ?? 0) | MessageFlags.SuppressNotifications;
352
+ }
353
+ }
354
+ return { files, body };
345
355
  }
346
356
  async _send(payload) {
347
357
  const data = await this.client.rest.post(
348
358
  Routes.channelMessages(this.channelId),
349
359
  payload
350
360
  );
361
+ this.client._addMessageToCache(this.channelId, data);
351
362
  return new _Message(this.client, data);
352
363
  }
353
364
  /**
@@ -484,8 +495,18 @@ var MessageManager = class {
484
495
  this.client = client;
485
496
  this.channelId = channelId;
486
497
  }
498
+ /**
499
+ * Get a message from cache. Returns undefined if not cached or caching is disabled.
500
+ * Requires options.cache.messages > 0.
501
+ * @param messageId - Snowflake of the message
502
+ */
503
+ get(messageId) {
504
+ const data = this.client._getMessageCache(this.channelId)?.get(messageId);
505
+ return data ? new Message(this.client, data) : void 0;
506
+ }
487
507
  /**
488
508
  * Fetch a message by ID from this channel.
509
+ * When message caching is enabled, the fetched message is added to the cache.
489
510
  * @param messageId - Snowflake of the message
490
511
  * @returns The message
491
512
  * @throws FluxerError with MESSAGE_NOT_FOUND if the message does not exist
@@ -495,6 +516,7 @@ var MessageManager = class {
495
516
  const data = await this.client.rest.get(
496
517
  Routes2.channelMessage(this.channelId, messageId)
497
518
  );
519
+ this.client._addMessageToCache(this.channelId, data);
498
520
  return new Message(this.client, data);
499
521
  } catch (err) {
500
522
  if (err instanceof RateLimitError) throw err;
@@ -992,6 +1014,7 @@ var GuildChannel = class extends Channel {
992
1014
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
993
1015
  const postOptions = files?.length ? { body, files } : { body };
994
1016
  const data = await this.client.rest.post(Routes5.channelMessages(this.id), postOptions);
1017
+ this.client._addMessageToCache(this.id, data);
995
1018
  return new Message(this.client, data);
996
1019
  }
997
1020
  /**
@@ -1057,6 +1080,7 @@ var TextChannel = class extends GuildChannel {
1057
1080
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1058
1081
  const postOptions = files?.length ? { body, files } : { body };
1059
1082
  const data = await this.client.rest.post(Routes5.channelMessages(this.id), postOptions);
1083
+ this.client._addMessageToCache(this.id, data);
1060
1084
  return new Message(this.client, data);
1061
1085
  }
1062
1086
  /** Message manager for this channel. Use channel.messages.fetch(messageId). */
@@ -1148,6 +1172,7 @@ var DMChannel = class extends Channel {
1148
1172
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1149
1173
  const postOptions = files?.length ? { body, files } : { body };
1150
1174
  const data = await this.client.rest.post(Routes5.channelMessages(this.id), postOptions);
1175
+ this.client._addMessageToCache(this.id, data);
1151
1176
  return new Message(this.client, data);
1152
1177
  }
1153
1178
  /** Message manager for this channel. Use channel.messages.fetch(messageId). */
@@ -1322,6 +1347,7 @@ var ChannelManager = class extends Collection4 {
1322
1347
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1323
1348
  const postOptions = files?.length ? { body, files } : { body };
1324
1349
  const data = await this.client.rest.post(Routes6.channelMessages(channelId), postOptions);
1350
+ this.client._addMessageToCache(channelId, data);
1325
1351
  return new Message(this.client, data);
1326
1352
  }
1327
1353
  };
@@ -1343,12 +1369,12 @@ import { Collection as Collection6 } from "@fluxerjs/collection";
1343
1369
  import { Routes as Routes9 } from "@fluxerjs/types";
1344
1370
 
1345
1371
  // src/structures/GuildMember.ts
1346
- import { PermissionFlagsMap } from "@fluxerjs/util";
1372
+ import { BitField } from "@fluxerjs/util";
1347
1373
  import { Routes as Routes8 } from "@fluxerjs/types";
1348
1374
 
1349
1375
  // src/util/permissions.ts
1350
1376
  import { OverwriteType } from "@fluxerjs/types";
1351
- import { ALL_PERMISSIONS_BIGINT } from "@fluxerjs/util";
1377
+ import { ALL_PERMISSIONS_BIGINT, PermissionFlags as PermissionFlags2 } from "@fluxerjs/util";
1352
1378
  function computePermissions(basePermissions, overwrites, memberRoles, memberId, isOwner) {
1353
1379
  if (isOwner) return ALL_PERMISSIONS_BIGINT;
1354
1380
  let perms = basePermissions;
@@ -1359,12 +1385,7 @@ function computePermissions(basePermissions, overwrites, memberRoles, memberId,
1359
1385
  const deny = BigInt(overwrite.deny || "0");
1360
1386
  perms = perms & ~deny | allow;
1361
1387
  }
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;
1388
+ return (perms & PermissionFlags2.Administrator) !== 0n ? ALL_PERMISSIONS_BIGINT : perms;
1368
1389
  }
1369
1390
 
1370
1391
  // src/structures/GuildMemberRoleManager.ts
@@ -1566,13 +1587,7 @@ var GuildMember = class extends Base {
1566
1587
  const ownerId = this.guild.ownerId;
1567
1588
  const isOwner = ownerId != null && ownerId !== "" && String(ownerId) === String(this.id);
1568
1589
  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
- };
1590
+ return new BitField(perms);
1576
1591
  }
1577
1592
  /**
1578
1593
  * Compute the member's effective permissions in a guild channel.
@@ -1594,22 +1609,16 @@ var GuildMember = class extends Base {
1594
1609
  this.id,
1595
1610
  isOwner
1596
1611
  );
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
- };
1612
+ return new BitField(perms);
1604
1613
  }
1605
1614
  _computeBasePermissions() {
1606
1615
  let base = 0n;
1607
1616
  const everyone = this.guild.roles.get(this.guild.id);
1608
- if (everyone) base |= BigInt(everyone.permissions);
1617
+ if (everyone) base |= everyone.permissions.bitfield;
1609
1618
  for (const roleId of this.roles.roleIds) {
1610
1619
  if (roleId === this.guild.id) continue;
1611
1620
  const role = this.guild.roles.get(roleId);
1612
- if (role) base |= BigInt(role.permissions);
1621
+ if (role) base |= role.permissions.bitfield;
1613
1622
  }
1614
1623
  return base;
1615
1624
  }
@@ -1694,8 +1703,10 @@ var GuildMemberManager = class extends Collection6 {
1694
1703
  // src/structures/Role.ts
1695
1704
  import { Routes as Routes10 } from "@fluxerjs/types";
1696
1705
  import {
1697
- PermissionFlags as PermissionFlags2,
1698
- resolvePermissionsToBitfield
1706
+ PermissionFlags as PermissionFlags4,
1707
+ resolvePermissionsToBitfield,
1708
+ ALL_PERMISSIONS_BIGINT as ALL_PERMISSIONS_BIGINT2,
1709
+ PermissionsBitField
1699
1710
  } from "@fluxerjs/util";
1700
1711
  var Role = class extends Base {
1701
1712
  client;
@@ -1704,7 +1715,7 @@ var Role = class extends Base {
1704
1715
  name;
1705
1716
  color;
1706
1717
  position;
1707
- permissions;
1718
+ _permissions;
1708
1719
  hoist;
1709
1720
  mentionable;
1710
1721
  unicodeEmoji;
@@ -1721,18 +1732,24 @@ var Role = class extends Base {
1721
1732
  this.name = data.name;
1722
1733
  this.color = data.color;
1723
1734
  this.position = data.position;
1724
- this.permissions = data.permissions;
1735
+ this._permissions = data.permissions;
1725
1736
  this.hoist = !!data.hoist;
1726
1737
  this.mentionable = !!data.mentionable;
1727
1738
  this.unicodeEmoji = data.unicode_emoji ?? null;
1728
1739
  this.hoistPosition = data.hoist_position ?? null;
1729
1740
  }
1741
+ get permissions() {
1742
+ const bits = BigInt(this._permissions);
1743
+ return new PermissionsBitField(
1744
+ (bits & PermissionFlags4.Administrator) !== 0n ? ALL_PERMISSIONS_BIGINT2 : bits
1745
+ );
1746
+ }
1730
1747
  /** Update mutable fields from fresh API data. Used by edit and gateway events. */
1731
1748
  _patch(data) {
1732
1749
  if (data.name !== void 0) this.name = data.name;
1733
1750
  if (data.color !== void 0) this.color = data.color;
1734
1751
  if (data.position !== void 0) this.position = data.position;
1735
- if (data.permissions !== void 0) this.permissions = data.permissions;
1752
+ if (data.permissions !== void 0) this._permissions = data.permissions;
1736
1753
  if (data.hoist !== void 0) this.hoist = !!data.hoist;
1737
1754
  if (data.mentionable !== void 0) this.mentionable = !!data.mentionable;
1738
1755
  if (data.unicode_emoji !== void 0) this.unicodeEmoji = data.unicode_emoji ?? null;
@@ -1742,24 +1759,6 @@ var Role = class extends Base {
1742
1759
  toString() {
1743
1760
  return `<@&${this.id}>`;
1744
1761
  }
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
1762
  /**
1764
1763
  * Edit this role.
1765
1764
  * Requires Manage Roles permission.
@@ -2804,13 +2803,28 @@ handlers.set("MESSAGE_CREATE", async (client, d) => {
2804
2803
  guild.members.set(member.id, member);
2805
2804
  }
2806
2805
  }
2806
+ client._addMessageToCache(data.channel_id, data);
2807
2807
  client.emit(Events.MessageCreate, new Message(client, data));
2808
2808
  });
2809
2809
  handlers.set("MESSAGE_UPDATE", async (client, d) => {
2810
- client.emit(Events.MessageUpdate, null, new Message(client, d));
2810
+ const partial = d;
2811
+ const cache = client._getMessageCache(partial.channel_id);
2812
+ let oldMessage = null;
2813
+ let mergedData = partial;
2814
+ if (cache) {
2815
+ const oldData = cache.get(partial.id);
2816
+ if (oldData) {
2817
+ oldMessage = new Message(client, oldData);
2818
+ mergedData = { ...oldData, ...partial };
2819
+ }
2820
+ cache.set(partial.id, mergedData);
2821
+ }
2822
+ const newMessage = new Message(client, mergedData);
2823
+ client.emit(Events.MessageUpdate, oldMessage, newMessage);
2811
2824
  });
2812
2825
  handlers.set("MESSAGE_DELETE", async (client, d) => {
2813
2826
  const data = d;
2827
+ client._removeMessageFromCache(data.channel_id, data.id);
2814
2828
  const channel = client.channels.get(data.channel_id) ?? null;
2815
2829
  client.emit(Events.MessageDelete, {
2816
2830
  id: data.id,
@@ -2980,7 +2994,11 @@ handlers.set("VOICE_SERVER_UPDATE", async (client, d) => {
2980
2994
  client.emit(Events.VoiceServerUpdate, d);
2981
2995
  });
2982
2996
  handlers.set("MESSAGE_DELETE_BULK", async (client, d) => {
2983
- client.emit(Events.MessageDeleteBulk, d);
2997
+ const data = d;
2998
+ for (const id of data.ids ?? []) {
2999
+ client._removeMessageFromCache(data.channel_id, id);
3000
+ }
3001
+ client.emit(Events.MessageDeleteBulk, data);
2984
3002
  });
2985
3003
  handlers.set("GUILD_BAN_ADD", async (client, d) => {
2986
3004
  const data = d;
@@ -3138,6 +3156,8 @@ var Client = class extends EventEmitter3 {
3138
3156
  _ws = null;
3139
3157
  /** When waitForGuilds, set of guild IDs we're waiting for GUILD_CREATE on. Null when not waiting. */
3140
3158
  _pendingGuildIds = null;
3159
+ /** Per-channel message cache (channelId -> messageId -> APIMessage). Used when options.cache.messages > 0. */
3160
+ _messageCaches = null;
3141
3161
  /**
3142
3162
  * Resolve an emoji argument to the API format (unicode or "name:id").
3143
3163
  * Supports: <:name:id>, :name:, name:id, { name, id }, unicode.
@@ -3226,11 +3246,52 @@ var Client = class extends EventEmitter3 {
3226
3246
  /**
3227
3247
  * Send a message to any channel by ID. Shorthand for client.channels.send().
3228
3248
  * Works even when the channel is not cached.
3249
+ * @deprecated Use client.channels.send(channelId, payload).
3229
3250
  */
3230
- async sendToChannel(channelId, content) {
3231
- const payload = await Message._createMessageBody(content);
3251
+ async sendToChannel(channelId, payload) {
3252
+ emitDeprecationWarning3(
3253
+ "Client.sendToChannel()",
3254
+ "Use client.channels.send(channelId, payload)."
3255
+ );
3232
3256
  return this.channels.send(channelId, payload);
3233
3257
  }
3258
+ /**
3259
+ * Get the message cache for a channel. Returns null if message caching is disabled.
3260
+ * Used by MessageManager.get() and event handlers.
3261
+ * @internal
3262
+ */
3263
+ _getMessageCache(channelId) {
3264
+ const limit = this.options.cache?.messages ?? 0;
3265
+ if (limit <= 0) return null;
3266
+ if (!this._messageCaches) this._messageCaches = /* @__PURE__ */ new Map();
3267
+ let cache = this._messageCaches.get(channelId);
3268
+ if (!cache) {
3269
+ cache = /* @__PURE__ */ new Map();
3270
+ this._messageCaches.set(channelId, cache);
3271
+ }
3272
+ return cache;
3273
+ }
3274
+ /**
3275
+ * Add a message to the channel cache. Evicts oldest (FIFO) when over limit.
3276
+ * @internal
3277
+ */
3278
+ _addMessageToCache(channelId, data) {
3279
+ const cache = this._getMessageCache(channelId);
3280
+ if (!cache) return;
3281
+ const limit = this.options.cache?.messages ?? 0;
3282
+ if (limit > 0 && cache.size >= limit && !cache.has(data.id)) {
3283
+ const firstKey = cache.keys().next().value;
3284
+ if (firstKey !== void 0) cache.delete(firstKey);
3285
+ }
3286
+ cache.set(data.id, { ...data });
3287
+ }
3288
+ /**
3289
+ * Remove a message from the channel cache.
3290
+ * @internal
3291
+ */
3292
+ _removeMessageFromCache(channelId, messageId) {
3293
+ this._messageCaches?.get(channelId)?.delete(messageId);
3294
+ }
3234
3295
  /**
3235
3296
  * Get or create a User from API data. Caches in client.users.
3236
3297
  * Updates existing user's username, avatar, etc. when fresh data is provided.
@@ -3410,8 +3471,8 @@ import { EmbedBuilder as EmbedBuilder3, MessagePayload, AttachmentBuilder } from
3410
3471
  import { Routes as Routes21, GatewayOpcodes, MessageAttachmentFlags } from "@fluxerjs/types";
3411
3472
  import { resolveTenorToImageUrl, parseUserMention, parsePrefixCommand } from "@fluxerjs/util";
3412
3473
  import {
3413
- PermissionsBitField,
3414
- PermissionFlags as PermissionFlags3,
3474
+ PermissionsBitField as PermissionsBitField2,
3475
+ PermissionFlags as PermissionFlags5,
3415
3476
  resolvePermissionsToBitfield as resolvePermissionsToBitfield3,
3416
3477
  UserFlagsBitField,
3417
3478
  UserFlagsBits
@@ -3447,8 +3508,8 @@ export {
3447
3508
  MessageManager,
3448
3509
  MessagePayload,
3449
3510
  MessageReaction,
3450
- PermissionFlags3 as PermissionFlags,
3451
- PermissionsBitField,
3511
+ PermissionFlags5 as PermissionFlags,
3512
+ PermissionsBitField2 as PermissionsBitField,
3452
3513
  ReactionCollector,
3453
3514
  Role,
3454
3515
  Routes21 as Routes,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.2.2",
6
+ "version": "1.2.3",
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.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"
37
+ "@fluxerjs/types": "1.2.3",
38
+ "@fluxerjs/rest": "1.2.3",
39
+ "@fluxerjs/collection": "1.2.3",
40
+ "@fluxerjs/builders": "1.2.3",
41
+ "@fluxerjs/ws": "1.2.3",
42
+ "@fluxerjs/util": "1.2.3"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^20.0.0",
@@ -55,6 +55,6 @@
55
55
  "lint": "eslint src --max-warnings 0 --config ../../eslint.config.js",
56
56
  "lint:fix": "eslint src --fix --config ../../eslint.config.js",
57
57
  "test": "vitest run --passWithNoTests",
58
- "test:coverage": "vitest run --coverage --passWithNoTests"
58
+ "test:coverage": "vitest run --coverage"
59
59
  }
60
60
  }