@fluxerjs/core 1.2.1 → 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.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
@@ -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
 
@@ -313,7 +314,10 @@ var Message = class _Message extends Base {
313
314
  mentionRoles;
314
315
  /** Client-side nonce for acknowledgment. Null if not provided. */
315
316
  nonce;
316
- /** Channel where this message was sent. Resolved from cache; null if not cached (e.g. DM channel not in cache). */
317
+ /**
318
+ * Channel where this message was sent. Resolved from cache; null if not cached.
319
+ * Messages can only exist in text-based channels (text, DM, announcement), so this always has send() when non-null.
320
+ */
317
321
  get channel() {
318
322
  return this.client.channels.get(this.channelId) ?? null;
319
323
  }
@@ -375,12 +379,8 @@ var Message = class _Message extends Base {
375
379
  * await message.send({ content: 'File', files: [{ name: 'data.txt', data }] });
376
380
  */
377
381
  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);
382
+ const payload = await _Message._createMessageBody(options);
383
+ return this._send(payload);
384
384
  }
385
385
  /**
386
386
  * Send a message to a specific channel. Use for logging, forwarding, or sending to another channel in the guild.
@@ -395,25 +395,46 @@ var Message = class _Message extends Base {
395
395
  }
396
396
  /**
397
397
  * Reply to this message (shows as a reply in the client).
398
- * @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)
399
399
  * @example
400
400
  * await message.reply('Pong!');
401
401
  * await message.reply({ embeds: [embed] });
402
+ * await message.reply('No ping!', { ping: false });
403
+ * await message.reply({ content: 'Reply to other', replyTo: otherMessage });
402
404
  */
403
- async reply(options) {
405
+ async reply(options, replyOptions) {
404
406
  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
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);
411
+ return this._send(payload);
412
+ }
413
+ /** Exposed for testing purposes, use Message.reply() or send() for normal use */
414
+ static async _createMessageBody(content, referenced_message, ping) {
415
+ if (typeof content === "string") {
416
+ if (content.length === 0) {
417
+ throw new RangeError("Cannot send an empty message");
412
418
  }
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);
419
+ content = { content };
420
+ }
421
+ const base = buildSendBody(content);
422
+ const files = content.files?.length ? await resolveMessageFiles(content.files) : void 0;
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 };
431
+ }
432
+ async _send(payload) {
433
+ const data = await this.client.rest.post(
434
+ import_types.Routes.channelMessages(this.channelId),
435
+ payload
436
+ );
437
+ this.client._addMessageToCache(this.channelId, data);
417
438
  return new _Message(this.client, data);
418
439
  }
419
440
  /**
@@ -550,8 +571,18 @@ var MessageManager = class {
550
571
  this.client = client;
551
572
  this.channelId = channelId;
552
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
+ }
553
583
  /**
554
584
  * Fetch a message by ID from this channel.
585
+ * When message caching is enabled, the fetched message is added to the cache.
555
586
  * @param messageId - Snowflake of the message
556
587
  * @returns The message
557
588
  * @throws FluxerError with MESSAGE_NOT_FOUND if the message does not exist
@@ -561,6 +592,7 @@ var MessageManager = class {
561
592
  const data = await this.client.rest.get(
562
593
  import_types2.Routes.channelMessage(this.channelId, messageId)
563
594
  );
595
+ this.client._addMessageToCache(this.channelId, data);
564
596
  return new Message(this.client, data);
565
597
  } catch (err) {
566
598
  if (err instanceof import_rest.RateLimitError) throw err;
@@ -630,6 +662,7 @@ var MessageCollector = class extends import_events2.EventEmitter {
630
662
  // src/structures/Channel.ts
631
663
  var import_types5 = require("@fluxerjs/types");
632
664
  var import_util = require("@fluxerjs/util");
665
+ var import_util2 = require("@fluxerjs/util");
633
666
 
634
667
  // src/structures/Webhook.ts
635
668
  var import_types3 = require("@fluxerjs/types");
@@ -764,7 +797,7 @@ var Webhook = class _Webhook extends Base {
764
797
  );
765
798
  }
766
799
  const opts = typeof options === "string" ? { content: options } : options;
767
- const body = buildSendBody(options);
800
+ const body = buildSendBody(opts);
768
801
  if (opts.username !== void 0) body.username = opts.username;
769
802
  if (opts.avatar_url !== void 0) body.avatar_url = opts.avatar_url;
770
803
  if (opts.tts !== void 0) body.tts = opts.tts;
@@ -865,7 +898,7 @@ var Invite = class extends Base {
865
898
  // src/structures/Channel.ts
866
899
  var Channel = class _Channel extends Base {
867
900
  /** Whether this channel has a send method (TextChannel, DMChannel). */
868
- isSendable() {
901
+ isTextBased() {
869
902
  return "send" in this;
870
903
  }
871
904
  /** Whether this channel is a DM or Group DM. */
@@ -876,6 +909,9 @@ var Channel = class _Channel extends Base {
876
909
  isVoice() {
877
910
  return "bitrate" in this;
878
911
  }
912
+ isLink() {
913
+ return "url" in this;
914
+ }
879
915
  /** Create a DM channel from API data (type DM or GroupDM). */
880
916
  static createDM(client, data) {
881
917
  return new DMChannel(client, data);
@@ -939,6 +975,15 @@ var Channel = class _Channel extends Base {
939
975
  async sendTyping() {
940
976
  await this.client.rest.post(import_types5.Routes.channelTyping(this.id), { auth: true });
941
977
  }
978
+ /**
979
+ * Whether the bot can send messages in this channel.
980
+ * For DMs: always true (when the channel exists).
981
+ * For guild channels: checks ViewChannel and SendMessages permissions via guild.members.me.
982
+ */
983
+ canSendMessage() {
984
+ if (this.isDM()) return true;
985
+ return false;
986
+ }
942
987
  };
943
988
  var GuildChannel = class extends Channel {
944
989
  guildId;
@@ -1022,6 +1067,32 @@ var GuildChannel = class extends Channel {
1022
1067
  if (idx >= 0) this.permissionOverwrites[idx] = entry;
1023
1068
  else this.permissionOverwrites.push(entry);
1024
1069
  }
1070
+ /**
1071
+ * Whether the bot can send messages in this channel.
1072
+ * Checks ViewChannel and SendMessages via guild.members.me permissions.
1073
+ * Returns false if guild or bot member not cached.
1074
+ */
1075
+ canSendMessage() {
1076
+ const guild = this.client.guilds.get(this.guildId);
1077
+ if (!guild) return false;
1078
+ const me = guild.members.me;
1079
+ if (!me) return false;
1080
+ const perms = me.permissionsIn(this);
1081
+ return perms.has(import_util.PermissionFlags.ViewChannel) && perms.has(import_util.PermissionFlags.SendMessages);
1082
+ }
1083
+ /**
1084
+ * Send a message to this guild channel.
1085
+ * Works for text and announcement channels. Voice/category/link channels will fail at the API.
1086
+ */
1087
+ async send(options) {
1088
+ const opts = typeof options === "string" ? { content: options } : options;
1089
+ const body = buildSendBody(options);
1090
+ const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1091
+ const postOptions = files?.length ? { body, files } : { body };
1092
+ const data = await this.client.rest.post(import_types5.Routes.channelMessages(this.id), postOptions);
1093
+ this.client._addMessageToCache(this.id, data);
1094
+ return new Message(this.client, data);
1095
+ }
1025
1096
  /**
1026
1097
  * Remove a permission overwrite. DELETE /channels/{id}/permissions/{overwriteId}.
1027
1098
  */
@@ -1085,6 +1156,7 @@ var TextChannel = class extends GuildChannel {
1085
1156
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1086
1157
  const postOptions = files?.length ? { body, files } : { body };
1087
1158
  const data = await this.client.rest.post(import_types5.Routes.channelMessages(this.id), postOptions);
1159
+ this.client._addMessageToCache(this.id, data);
1088
1160
  return new Message(this.client, data);
1089
1161
  }
1090
1162
  /** Message manager for this channel. Use channel.messages.fetch(messageId). */
@@ -1122,7 +1194,7 @@ var TextChannel = class extends GuildChannel {
1122
1194
  * @deprecated Use channel.messages.fetch(messageId) instead.
1123
1195
  */
1124
1196
  async fetchMessage(messageId) {
1125
- (0, import_util.emitDeprecationWarning)(
1197
+ (0, import_util2.emitDeprecationWarning)(
1126
1198
  "Channel.fetchMessage()",
1127
1199
  "Use channel.messages.fetch(messageId) instead."
1128
1200
  );
@@ -1176,6 +1248,7 @@ var DMChannel = class extends Channel {
1176
1248
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1177
1249
  const postOptions = files?.length ? { body, files } : { body };
1178
1250
  const data = await this.client.rest.post(import_types5.Routes.channelMessages(this.id), postOptions);
1251
+ this.client._addMessageToCache(this.id, data);
1179
1252
  return new Message(this.client, data);
1180
1253
  }
1181
1254
  /** Message manager for this channel. Use channel.messages.fetch(messageId). */
@@ -1208,7 +1281,7 @@ var DMChannel = class extends Channel {
1208
1281
  * @deprecated Use channel.messages.fetch(messageId) instead.
1209
1282
  */
1210
1283
  async fetchMessage(messageId) {
1211
- (0, import_util.emitDeprecationWarning)(
1284
+ (0, import_util2.emitDeprecationWarning)(
1212
1285
  "Channel.fetchMessage()",
1213
1286
  "Use channel.messages.fetch(messageId) instead."
1214
1287
  );
@@ -1259,7 +1332,7 @@ var ChannelManager = class extends import_collection4.Collection {
1259
1332
  * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1260
1333
  * @example
1261
1334
  * const channel = await client.channels.resolve(message.channelId);
1262
- * if (channel?.isSendable()) await channel.send('Hello!');
1335
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1263
1336
  */
1264
1337
  async resolve(channelId) {
1265
1338
  return this.get(channelId) ?? this.fetch(channelId);
@@ -1271,7 +1344,7 @@ var ChannelManager = class extends import_collection4.Collection {
1271
1344
  * @throws FluxerError with CHANNEL_NOT_FOUND if the channel does not exist
1272
1345
  * @example
1273
1346
  * const channel = await client.channels.fetch(channelId);
1274
- * if (channel?.isSendable()) await channel.send('Hello!');
1347
+ * if (channel?.isTextBased()) await channel.send('Hello!');
1275
1348
  */
1276
1349
  async fetch(channelId) {
1277
1350
  const cached = this.get(channelId);
@@ -1313,7 +1386,7 @@ var ChannelManager = class extends import_collection4.Collection {
1313
1386
  * const message = await channel?.messages?.fetch(messageId);
1314
1387
  */
1315
1388
  async fetchMessage(channelId, messageId) {
1316
- (0, import_util2.emitDeprecationWarning)(
1389
+ (0, import_util3.emitDeprecationWarning)(
1317
1390
  "ChannelManager.fetchMessage()",
1318
1391
  "Use channel.messages.fetch(messageId). Prefer (await client.channels.resolve(channelId))?.messages?.fetch(messageId)."
1319
1392
  );
@@ -1350,6 +1423,7 @@ var ChannelManager = class extends import_collection4.Collection {
1350
1423
  const files = opts.files?.length ? await resolveMessageFiles(opts.files) : void 0;
1351
1424
  const postOptions = files?.length ? { body, files } : { body };
1352
1425
  const data = await this.client.rest.post(import_types6.Routes.channelMessages(channelId), postOptions);
1426
+ this.client._addMessageToCache(channelId, data);
1353
1427
  return new Message(this.client, data);
1354
1428
  }
1355
1429
  };
@@ -1359,7 +1433,7 @@ var import_collection8 = require("@fluxerjs/collection");
1359
1433
  var import_types16 = require("@fluxerjs/types");
1360
1434
 
1361
1435
  // src/structures/Guild.ts
1362
- var import_util6 = require("@fluxerjs/util");
1436
+ var import_util7 = require("@fluxerjs/util");
1363
1437
  var import_rest3 = require("@fluxerjs/rest");
1364
1438
  var import_collection7 = require("@fluxerjs/collection");
1365
1439
 
@@ -1368,14 +1442,14 @@ var import_collection6 = require("@fluxerjs/collection");
1368
1442
  var import_types10 = require("@fluxerjs/types");
1369
1443
 
1370
1444
  // src/structures/GuildMember.ts
1371
- var import_util4 = require("@fluxerjs/util");
1445
+ var import_util5 = require("@fluxerjs/util");
1372
1446
  var import_types9 = require("@fluxerjs/types");
1373
1447
 
1374
1448
  // src/util/permissions.ts
1375
1449
  var import_types7 = require("@fluxerjs/types");
1376
- var import_util3 = require("@fluxerjs/util");
1450
+ var import_util4 = require("@fluxerjs/util");
1377
1451
  function computePermissions(basePermissions, overwrites, memberRoles, memberId, isOwner) {
1378
- if (isOwner) return import_util3.ALL_PERMISSIONS_BIGINT;
1452
+ if (isOwner) return import_util4.ALL_PERMISSIONS_BIGINT;
1379
1453
  let perms = basePermissions;
1380
1454
  for (const overwrite of overwrites ?? []) {
1381
1455
  const applies = overwrite.type === import_types7.OverwriteType.Role && memberRoles.includes(overwrite.id) || overwrite.type === import_types7.OverwriteType.Member && overwrite.id === memberId;
@@ -1384,12 +1458,7 @@ function computePermissions(basePermissions, overwrites, memberRoles, memberId,
1384
1458
  const deny = BigInt(overwrite.deny || "0");
1385
1459
  perms = perms & ~deny | allow;
1386
1460
  }
1387
- return perms;
1388
- }
1389
- function hasPermission(bitfield, permission) {
1390
- const Administrator = 1n << 3n;
1391
- if ((bitfield & Administrator) !== 0n) return true;
1392
- return (bitfield & permission) === permission;
1461
+ return (perms & import_util4.PermissionFlags.Administrator) !== 0n ? import_util4.ALL_PERMISSIONS_BIGINT : perms;
1393
1462
  }
1394
1463
 
1395
1464
  // src/structures/GuildMemberRoleManager.ts
@@ -1591,13 +1660,7 @@ var GuildMember = class extends Base {
1591
1660
  const ownerId = this.guild.ownerId;
1592
1661
  const isOwner = ownerId != null && ownerId !== "" && String(ownerId) === String(this.id);
1593
1662
  const perms = computePermissions(base, [], [], this.id, isOwner);
1594
- return {
1595
- has(permission) {
1596
- const perm = typeof permission === "number" ? permission : import_util4.PermissionFlagsMap[String(permission)];
1597
- if (perm === void 0) return false;
1598
- return hasPermission(perms, BigInt(perm));
1599
- }
1600
- };
1663
+ return new import_util5.BitField(perms);
1601
1664
  }
1602
1665
  /**
1603
1666
  * Compute the member's effective permissions in a guild channel.
@@ -1619,22 +1682,16 @@ var GuildMember = class extends Base {
1619
1682
  this.id,
1620
1683
  isOwner
1621
1684
  );
1622
- return {
1623
- has(permission) {
1624
- const perm = typeof permission === "number" ? permission : import_util4.PermissionFlagsMap[String(permission)];
1625
- if (perm === void 0) return false;
1626
- return hasPermission(perms, BigInt(perm));
1627
- }
1628
- };
1685
+ return new import_util5.BitField(perms);
1629
1686
  }
1630
1687
  _computeBasePermissions() {
1631
1688
  let base = 0n;
1632
1689
  const everyone = this.guild.roles.get(this.guild.id);
1633
- if (everyone) base |= BigInt(everyone.permissions);
1690
+ if (everyone) base |= everyone.permissions.bitfield;
1634
1691
  for (const roleId of this.roles.roleIds) {
1635
1692
  if (roleId === this.guild.id) continue;
1636
1693
  const role = this.guild.roles.get(roleId);
1637
- if (role) base |= BigInt(role.permissions);
1694
+ if (role) base |= role.permissions.bitfield;
1638
1695
  }
1639
1696
  return base;
1640
1697
  }
@@ -1718,7 +1775,7 @@ var GuildMemberManager = class extends import_collection6.Collection {
1718
1775
 
1719
1776
  // src/structures/Role.ts
1720
1777
  var import_types11 = require("@fluxerjs/types");
1721
- var import_util5 = require("@fluxerjs/util");
1778
+ var import_util6 = require("@fluxerjs/util");
1722
1779
  var Role = class extends Base {
1723
1780
  client;
1724
1781
  id;
@@ -1726,7 +1783,7 @@ var Role = class extends Base {
1726
1783
  name;
1727
1784
  color;
1728
1785
  position;
1729
- permissions;
1786
+ _permissions;
1730
1787
  hoist;
1731
1788
  mentionable;
1732
1789
  unicodeEmoji;
@@ -1743,18 +1800,24 @@ var Role = class extends Base {
1743
1800
  this.name = data.name;
1744
1801
  this.color = data.color;
1745
1802
  this.position = data.position;
1746
- this.permissions = data.permissions;
1803
+ this._permissions = data.permissions;
1747
1804
  this.hoist = !!data.hoist;
1748
1805
  this.mentionable = !!data.mentionable;
1749
1806
  this.unicodeEmoji = data.unicode_emoji ?? null;
1750
1807
  this.hoistPosition = data.hoist_position ?? null;
1751
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
+ }
1752
1815
  /** Update mutable fields from fresh API data. Used by edit and gateway events. */
1753
1816
  _patch(data) {
1754
1817
  if (data.name !== void 0) this.name = data.name;
1755
1818
  if (data.color !== void 0) this.color = data.color;
1756
1819
  if (data.position !== void 0) this.position = data.position;
1757
- if (data.permissions !== void 0) this.permissions = data.permissions;
1820
+ if (data.permissions !== void 0) this._permissions = data.permissions;
1758
1821
  if (data.hoist !== void 0) this.hoist = !!data.hoist;
1759
1822
  if (data.mentionable !== void 0) this.mentionable = !!data.mentionable;
1760
1823
  if (data.unicode_emoji !== void 0) this.unicodeEmoji = data.unicode_emoji ?? null;
@@ -1764,24 +1827,6 @@ var Role = class extends Base {
1764
1827
  toString() {
1765
1828
  return `<@&${this.id}>`;
1766
1829
  }
1767
- /**
1768
- * Check if this role has a permission. Administrator grants all permissions.
1769
- * @param permission - Permission flag, name, or resolvable
1770
- * @returns true if the role has the permission
1771
- * @example
1772
- * if (role.has(PermissionFlags.BanMembers)) { ... }
1773
- * if (role.has('ManageChannels')) { ... }
1774
- */
1775
- has(permission) {
1776
- const perm = typeof permission === "number" ? permission : import_util5.PermissionFlags[permission];
1777
- if (perm === void 0) return false;
1778
- const permNum = Number(perm);
1779
- const rolePerms = BigInt(this.permissions);
1780
- const permBig = BigInt(permNum);
1781
- if (permBig < 0) return false;
1782
- if ((rolePerms & BigInt(import_util5.PermissionFlags.Administrator)) !== 0n) return true;
1783
- return (rolePerms & permBig) === permBig;
1784
- }
1785
1830
  /**
1786
1831
  * Edit this role.
1787
1832
  * Requires Manage Roles permission.
@@ -1794,7 +1839,7 @@ var Role = class extends Base {
1794
1839
  const body = {};
1795
1840
  if (options.name !== void 0) body.name = options.name;
1796
1841
  if (options.permissions !== void 0) {
1797
- body.permissions = typeof options.permissions === "string" ? options.permissions : (0, import_util5.resolvePermissionsToBitfield)(options.permissions);
1842
+ body.permissions = typeof options.permissions === "string" ? options.permissions : (0, import_util6.resolvePermissionsToBitfield)(options.permissions);
1798
1843
  }
1799
1844
  if (options.color !== void 0) body.color = options.color;
1800
1845
  if (options.hoist !== void 0) body.hoist = options.hoist;
@@ -2065,7 +2110,7 @@ var Guild = class extends Base {
2065
2110
  const body = {};
2066
2111
  if (options.name !== void 0) body.name = options.name;
2067
2112
  if (options.permissions !== void 0) {
2068
- body.permissions = typeof options.permissions === "string" ? options.permissions : (0, import_util6.resolvePermissionsToBitfield)(options.permissions);
2113
+ body.permissions = typeof options.permissions === "string" ? options.permissions : (0, import_util7.resolvePermissionsToBitfield)(options.permissions);
2069
2114
  }
2070
2115
  if (options.color !== void 0) body.color = options.color;
2071
2116
  if (options.hoist !== void 0) body.hoist = options.hoist;
@@ -2128,7 +2173,7 @@ var Guild = class extends Base {
2128
2173
  * @returns The role ID, or null if not found
2129
2174
  */
2130
2175
  async resolveRoleId(arg) {
2131
- const parsed = (0, import_util6.parseRoleMention)(arg);
2176
+ const parsed = (0, import_util7.parseRoleMention)(arg);
2132
2177
  if (parsed) return parsed;
2133
2178
  if (/^\d{17,19}$/.test(arg.trim())) return arg.trim();
2134
2179
  const cached = this.roles.find(
@@ -2665,7 +2710,7 @@ var ClientUser = class extends User {
2665
2710
  };
2666
2711
 
2667
2712
  // src/client/Client.ts
2668
- var import_util7 = require("@fluxerjs/util");
2713
+ var import_util8 = require("@fluxerjs/util");
2669
2714
 
2670
2715
  // src/client/UsersManager.ts
2671
2716
  var import_collection9 = require("@fluxerjs/collection");
@@ -2821,13 +2866,28 @@ handlers.set("MESSAGE_CREATE", async (client, d) => {
2821
2866
  guild.members.set(member.id, member);
2822
2867
  }
2823
2868
  }
2869
+ client._addMessageToCache(data.channel_id, data);
2824
2870
  client.emit(Events.MessageCreate, new Message(client, data));
2825
2871
  });
2826
2872
  handlers.set("MESSAGE_UPDATE", async (client, d) => {
2827
- 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);
2828
2887
  });
2829
2888
  handlers.set("MESSAGE_DELETE", async (client, d) => {
2830
2889
  const data = d;
2890
+ client._removeMessageFromCache(data.channel_id, data.id);
2831
2891
  const channel = client.channels.get(data.channel_id) ?? null;
2832
2892
  client.emit(Events.MessageDelete, {
2833
2893
  id: data.id,
@@ -2890,6 +2950,7 @@ handlers.set("GUILD_CREATE", async (client, d) => {
2890
2950
  if (g.voice_states?.length) {
2891
2951
  client.emit(Events.VoiceStatesSync, { guildId: guild.id, voiceStates: g.voice_states });
2892
2952
  }
2953
+ client._onGuildReceived(guild.id);
2893
2954
  });
2894
2955
  handlers.set("GUILD_UPDATE", async (client, d) => {
2895
2956
  const guildData = normalizeGuildPayload(d);
@@ -2996,7 +3057,11 @@ handlers.set("VOICE_SERVER_UPDATE", async (client, d) => {
2996
3057
  client.emit(Events.VoiceServerUpdate, d);
2997
3058
  });
2998
3059
  handlers.set("MESSAGE_DELETE_BULK", async (client, d) => {
2999
- 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);
3000
3065
  });
3001
3066
  handlers.set("GUILD_BAN_ADD", async (client, d) => {
3002
3067
  const data = d;
@@ -3022,7 +3087,11 @@ handlers.set("GUILD_EMOJIS_UPDATE", async (client, d) => {
3022
3087
  if (!e.id || e.name == null) continue;
3023
3088
  guild.emojis.set(
3024
3089
  e.id,
3025
- new GuildEmoji(client, { id: e.id, name: e.name, animated: e.animated ?? false, guild_id: guild.id }, guild.id)
3090
+ new GuildEmoji(
3091
+ client,
3092
+ { id: e.id, name: e.name, animated: e.animated ?? false, guild_id: guild.id },
3093
+ guild.id
3094
+ )
3026
3095
  );
3027
3096
  }
3028
3097
  }
@@ -3148,6 +3217,10 @@ var Client = class extends import_events3.EventEmitter {
3148
3217
  /** Timestamp when the client became ready. Null until READY is received. */
3149
3218
  readyAt = null;
3150
3219
  _ws = null;
3220
+ /** When waitForGuilds, set of guild IDs we're waiting for GUILD_CREATE on. Null when not waiting. */
3221
+ _pendingGuildIds = null;
3222
+ /** Per-channel message cache (channelId -> messageId -> APIMessage). Used when options.cache.messages > 0. */
3223
+ _messageCaches = null;
3151
3224
  /**
3152
3225
  * Resolve an emoji argument to the API format (unicode or "name:id").
3153
3226
  * Supports: <:name:id>, :name:, name:id, { name, id }, unicode.
@@ -3162,9 +3235,9 @@ var Client = class extends import_events3.EventEmitter {
3162
3235
  if (guildId) {
3163
3236
  await this.assertEmojiInGuild(emoji.id, guildId);
3164
3237
  }
3165
- return (0, import_util7.formatEmoji)({ name: emoji.name, id: emoji.id, animated: emoji.animated });
3238
+ return (0, import_util8.formatEmoji)({ name: emoji.name, id: emoji.id, animated: emoji.animated });
3166
3239
  }
3167
- const parsed = (0, import_util7.parseEmoji)(
3240
+ const parsed = (0, import_util8.parseEmoji)(
3168
3241
  typeof emoji === "string" ? emoji : emoji.id ? `:${emoji.name}:` : emoji.name
3169
3242
  );
3170
3243
  if (!parsed) throw new Error("Invalid emoji");
@@ -3172,16 +3245,16 @@ var Client = class extends import_events3.EventEmitter {
3172
3245
  if (guildId) {
3173
3246
  await this.assertEmojiInGuild(parsed.id, guildId);
3174
3247
  }
3175
- return (0, import_util7.formatEmoji)(parsed);
3248
+ return (0, import_util8.formatEmoji)(parsed);
3176
3249
  }
3177
3250
  if (!/^\w+$/.test(parsed.name)) return parsed.name;
3178
- const unicodeFromShortcode = (0, import_util7.getUnicodeFromShortcode)(parsed.name);
3251
+ const unicodeFromShortcode = (0, import_util8.getUnicodeFromShortcode)(parsed.name);
3179
3252
  if (unicodeFromShortcode) return unicodeFromShortcode;
3180
3253
  if (guildId) {
3181
3254
  const emojis = await this.rest.get(import_types21.Routes.guildEmojis(guildId));
3182
3255
  const list = Array.isArray(emojis) ? emojis : Object.values(emojis ?? {});
3183
3256
  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 });
3257
+ if (found) return (0, import_util8.formatEmoji)({ ...parsed, id: found.id, animated: found.animated });
3185
3258
  throw new Error(
3186
3259
  `Custom emoji ":${parsed.name}:" not found in guild. Use name:id or <:name:id> format.`
3187
3260
  );
@@ -3227,7 +3300,7 @@ var Client = class extends import_events3.EventEmitter {
3227
3300
  * const message = await channel?.messages?.fetch(messageId);
3228
3301
  */
3229
3302
  async fetchMessage(channelId, messageId) {
3230
- (0, import_util7.emitDeprecationWarning)(
3303
+ (0, import_util8.emitDeprecationWarning)(
3231
3304
  "Client.fetchMessage()",
3232
3305
  "Use channel.messages.fetch(messageId). For IDs-only: (await client.channels.resolve(channelId))?.messages?.fetch(messageId)"
3233
3306
  );
@@ -3236,10 +3309,52 @@ var Client = class extends import_events3.EventEmitter {
3236
3309
  /**
3237
3310
  * Send a message to any channel by ID. Shorthand for client.channels.send().
3238
3311
  * Works even when the channel is not cached.
3312
+ * @deprecated Use client.channels.send(channelId, payload).
3239
3313
  */
3240
3314
  async sendToChannel(channelId, payload) {
3315
+ (0, import_util8.emitDeprecationWarning)(
3316
+ "Client.sendToChannel()",
3317
+ "Use client.channels.send(channelId, payload)."
3318
+ );
3241
3319
  return this.channels.send(channelId, payload);
3242
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
+ }
3243
3358
  /**
3244
3359
  * Get or create a User from API data. Caches in client.users.
3245
3360
  * Updates existing user's username, avatar, etc. when fresh data is provided.
@@ -3320,7 +3435,13 @@ var Client = class extends import_events3.EventEmitter {
3320
3435
  data
3321
3436
  }) => {
3322
3437
  this.user = new ClientUser(this, data.user);
3438
+ const waitForGuilds = this.options.waitForGuilds === true;
3439
+ const pending = waitForGuilds ? /* @__PURE__ */ new Set() : null;
3323
3440
  for (const g of data.guilds ?? []) {
3441
+ if (g.unavailable === true) {
3442
+ if (pending !== null && g.id) pending.add(g.id);
3443
+ continue;
3444
+ }
3324
3445
  const guildData = normalizeGuildPayload(g);
3325
3446
  if (!guildData) continue;
3326
3447
  const guild = new Guild(this, guildData);
@@ -3340,8 +3461,11 @@ var Client = class extends import_events3.EventEmitter {
3340
3461
  });
3341
3462
  }
3342
3463
  }
3343
- this.readyAt = /* @__PURE__ */ new Date();
3344
- this.emit(Events.Ready);
3464
+ if (pending !== null && pending.size > 0) {
3465
+ this._pendingGuildIds = pending;
3466
+ return;
3467
+ }
3468
+ this._finalizeReady();
3345
3469
  }
3346
3470
  );
3347
3471
  this._ws.on("error", ({ error }) => this.emit(Events.Error, error));
@@ -3349,6 +3473,25 @@ var Client = class extends import_events3.EventEmitter {
3349
3473
  await this._ws.connect();
3350
3474
  return token;
3351
3475
  }
3476
+ /**
3477
+ * Called when all guilds have been received (or immediately if not waiting).
3478
+ * Sets readyAt, emits Ready, clears pending state.
3479
+ */
3480
+ _finalizeReady() {
3481
+ this._pendingGuildIds = null;
3482
+ this.readyAt = /* @__PURE__ */ new Date();
3483
+ this.emit(Events.Ready);
3484
+ }
3485
+ /**
3486
+ * Called by GUILD_CREATE handler when waitForGuilds is enabled.
3487
+ * Removes guild from pending set; when empty, finalizes ready.
3488
+ */
3489
+ _onGuildReceived(guildId) {
3490
+ const pending = this._pendingGuildIds;
3491
+ if (pending === null) return;
3492
+ pending.delete(guildId);
3493
+ if (pending.size === 0) this._finalizeReady();
3494
+ }
3352
3495
  /** Disconnect from the gateway and clear cached data. */
3353
3496
  async destroy() {
3354
3497
  if (this._ws) {
@@ -3358,6 +3501,7 @@ var Client = class extends import_events3.EventEmitter {
3358
3501
  this.rest.setToken(null);
3359
3502
  this.user = null;
3360
3503
  this.readyAt = null;
3504
+ this._pendingGuildIds = null;
3361
3505
  this.guilds.clear();
3362
3506
  this.channels.clear();
3363
3507
  this.users.clear();
@@ -3388,8 +3532,8 @@ var Client = class extends import_events3.EventEmitter {
3388
3532
  // src/index.ts
3389
3533
  var import_builders3 = require("@fluxerjs/builders");
3390
3534
  var import_types22 = require("@fluxerjs/types");
3391
- var import_util8 = require("@fluxerjs/util");
3392
3535
  var import_util9 = require("@fluxerjs/util");
3536
+ var import_util10 = require("@fluxerjs/util");
3393
3537
  // Annotate the CommonJS export names for ESM import in node:
3394
3538
  0 && (module.exports = {
3395
3539
  AttachmentBuilder,