djs-selfbot-v13 3.7.22 → 3.7.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "djs-selfbot-v13",
3
- "version": "3.7.22",
3
+ "version": "3.7.24",
4
4
  "description": "An unofficial discord.js fork for creating selfbots",
5
5
  "main": "./src/index.js",
6
6
  "types": "./typings/index.d.ts",
@@ -606,6 +606,15 @@ class Message extends Base {
606
606
  * @readonly
607
607
  */
608
608
  get editable() {
609
+ const permissions = this.channel?.permissionsFor(this.client.user);
610
+ if (!permissions) return false;
611
+
612
+ const hasPermission = this.channel?.isThread?.()
613
+ ? permissions.has(Permissions.FLAGS.SEND_MESSAGES_IN_THREADS, false)
614
+ : permissions.has(Permissions.FLAGS.SEND_MESSAGES, false);
615
+
616
+ if (!hasPermission) return false;
617
+
609
618
  const precheck = Boolean(
610
619
  this.author.id === this.client.user.id &&
611
620
  !deletedMessages.has(this) &&
@@ -618,7 +627,6 @@ class Message extends Base {
618
627
  if (this.channel?.isThread()) {
619
628
  if (this.channel.archived) return false;
620
629
  if (this.channel.locked) {
621
- const permissions = this.channel.permissionsFor(this.client.user);
622
630
  if (!permissions?.has(Permissions.FLAGS.MANAGE_THREADS, true)) return false;
623
631
  }
624
632
  }
@@ -172,6 +172,16 @@ class MessagePayload {
172
172
  allowedMentions = Util.cloneObject(allowedMentions);
173
173
  allowedMentions.replied_user = allowedMentions.repliedUser;
174
174
  delete allowedMentions.repliedUser;
175
+
176
+ const guild = this.target.guild ?? this.target.channel?.guild;
177
+ if (guild && !this.isWebhook) {
178
+ const permissions = this.target.channel?.permissionsFor(this.target.client.user);
179
+ if (permissions) {
180
+ if (allowedMentions.parse?.includes('everyone') && !permissions.has(Permissions.FLAGS.MENTION_EVERYONE, false)) {
181
+ allowedMentions.parse = allowedMentions.parse.filter(p => p !== 'everyone');
182
+ }
183
+ }
184
+ }
175
185
  }
176
186
 
177
187
  let message_reference;
@@ -25,9 +25,7 @@ class User extends Base {
25
25
  this.id = data.id;
26
26
 
27
27
  this.bot = null;
28
-
29
28
  this.system = null;
30
-
31
29
  this.flags = null;
32
30
 
33
31
  this._patch(data);
@@ -98,7 +96,7 @@ class User extends Base {
98
96
 
99
97
  if ('banner_color' in data) {
100
98
  /**
101
- * The user banner's hex
99
+ * The user banner's hex color
102
100
  * <info>The user must be force fetched for this property to be present or be updated</info>
103
101
  * @type {?string}
104
102
  */
@@ -136,16 +134,25 @@ class User extends Base {
136
134
  this.flags = new UserFlags(data.public_flags);
137
135
  }
138
136
 
139
- if (data.display_name_styles) {
137
+ // ─── Display Name Styles ────────────────────────────────────────────────
138
+
139
+ /**
140
+ * @typedef {Object} DisplayNameStyles
141
+ * @property {number} fontId The font ID used for the display name
142
+ * @property {number} effectId The effect ID applied to the display name
143
+ * @property {number[]} colors Array of decimal color values (1 for solid, 2 for gradient)
144
+ */
145
+
146
+ if ('display_name_styles' in data) {
140
147
  if (data.display_name_styles) {
141
148
  /**
142
- * The user avatar decoration's data
143
- * @type {?AvatarDecorationData}
149
+ * The display name style of this user (font, effect, colors)
150
+ * @type {?DisplayNameStyles}
144
151
  */
145
152
  this.displayNameStyles = {
146
- fontId: data.display_name_styles.fontId,
147
- effectId: data.display_name_styles.effectId,
148
- colors: data.display_name_styles.colors
153
+ fontId: data.display_name_styles.font_id ?? data.display_name_styles.fontId ?? null,
154
+ effectId: data.display_name_styles.effect_id ?? data.display_name_styles.effectId ?? null,
155
+ colors: data.display_name_styles.colors ?? [],
149
156
  };
150
157
  } else {
151
158
  this.displayNameStyles = null;
@@ -154,13 +161,15 @@ class User extends Base {
154
161
  this.displayNameStyles ??= null;
155
162
  }
156
163
 
164
+ // ─── Avatar Decoration ──────────────────────────────────────────────────
165
+
157
166
  /**
158
167
  * @typedef {Object} AvatarDecorationData
159
- * @property {string} asset The avatar decoration hash
168
+ * @property {string} asset The avatar decoration hash
160
169
  * @property {Snowflake} skuId The id of the avatar decoration's SKU
161
170
  */
162
171
 
163
- if (data.avatar_decoration_data) {
172
+ if ('avatar_decoration_data' in data) {
164
173
  if (data.avatar_decoration_data) {
165
174
  /**
166
175
  * The user avatar decoration's data
@@ -177,12 +186,14 @@ class User extends Base {
177
186
  this.avatarDecorationData ??= null;
178
187
  }
179
188
 
189
+ // ─── Primary Guild / Clan ───────────────────────────────────────────────
190
+
180
191
  /**
181
192
  * @typedef {Object} UserPrimaryGuild
182
- * @property {?Snowflake} identityGuildId The id of the user's primary guild
183
- * @property {?boolean} identityEnabled Whether the user is displaying the primary guild's tag
184
- * @property {?string} tag The user's guild tag. Limited to 4 characters
185
- * @property {?string} badge The guild tag badge hash
193
+ * @property {?Snowflake} identityGuildId The id of the user's primary guild
194
+ * @property {?boolean} identityEnabled Whether the user is displaying the primary guild's tag
195
+ * @property {?string} tag The user's guild tag (max 4 characters)
196
+ * @property {?string} badge The guild tag badge hash
186
197
  */
187
198
 
188
199
  if ('primary_guild' in data) {
@@ -194,8 +205,8 @@ class User extends Base {
194
205
  this.primaryGuild = {
195
206
  identityGuildId: data.primary_guild.identity_guild_id,
196
207
  identityEnabled: data.primary_guild.identity_enabled,
197
- tag: data.primary_guild.tag,
198
- badge: data.primary_guild.badge,
208
+ tag: data.primary_guild.tag,
209
+ badge: data.primary_guild.badge,
199
210
  };
200
211
  } else {
201
212
  this.primaryGuild = null;
@@ -204,12 +215,23 @@ class User extends Base {
204
215
  this.primaryGuild ??= null;
205
216
  }
206
217
 
218
+ // ─── Collectibles ───────────────────────────────────────────────────────
219
+
220
+ /**
221
+ * @typedef {Object} NameplatePalette
222
+ * @property {string} [backgroundPrimary] Primary background color
223
+ * @property {string} [backgroundSecondary] Secondary background color
224
+ * @property {string} [bodyTextNormal] Normal body text color
225
+ * @property {string} [bodyTextMuted] Muted body text color
226
+ * @property {string} [bodyTextStrong] Strong body text color
227
+ */
228
+
207
229
  /**
208
230
  * @typedef {Object} NameplateData
209
- * @property {Snowflake} skuId The id of the nameplate's SKU
210
- * @property {string} asset The nameplate's asset path
211
- * @property {string} label The nameplate's label
212
- * @property {NameplatePalette} palette Background color of the nameplate
231
+ * @property {Snowflake} skuId The id of the nameplate's SKU
232
+ * @property {string} asset The nameplate's asset path
233
+ * @property {string} label The nameplate's label
234
+ * @property {NameplatePalette} palette Background color palette of the nameplate
213
235
  */
214
236
 
215
237
  /**
@@ -217,31 +239,225 @@ class User extends Base {
217
239
  * @property {?NameplateData} nameplate The user's nameplate data
218
240
  */
219
241
 
220
- if (data.collectibles) {
221
- if (data.collectibles.nameplate) {
242
+ if ('collectibles' in data) {
243
+ if (data.collectibles?.nameplate) {
222
244
  /**
223
245
  * The user's collectibles
224
246
  * @type {?Collectibles}
225
247
  */
226
248
  this.collectibles = {
227
249
  nameplate: {
228
- skuId: data.collectibles.nameplate.sku_id,
229
- asset: data.collectibles.nameplate.asset,
230
- label: data.collectibles.nameplate.label,
250
+ skuId: data.collectibles.nameplate.sku_id,
251
+ asset: data.collectibles.nameplate.asset,
252
+ label: data.collectibles.nameplate.label,
231
253
  palette: data.collectibles.nameplate.palette,
232
254
  },
233
255
  };
234
256
  } else {
235
- this.collectibles = { nameplate: null };
257
+ this.collectibles = data.collectibles ? { nameplate: null } : null;
236
258
  }
237
259
  } else {
238
260
  this.collectibles ??= null;
239
261
  }
262
+
263
+ // ─── Bio ────────────────────────────────────────────────────────────────
264
+
265
+ if ('bio' in data) {
266
+ /**
267
+ * The bio of the user (self-introduction)
268
+ * @type {?string}
269
+ */
270
+ this.bio = data.bio ?? null;
271
+ } else {
272
+ this.bio ??= null;
273
+ }
274
+
275
+ // ─── Pronouns ───────────────────────────────────────────────────────────
276
+
277
+ if ('pronouns' in data) {
278
+ /**
279
+ * The pronouns of the user
280
+ * @type {?string}
281
+ */
282
+ this.pronouns = data.pronouns ?? null;
283
+ } else {
284
+ this.pronouns ??= null;
285
+ }
286
+
287
+ // ─── Premium (Nitro) ────────────────────────────────────────────────────
288
+
289
+ /**
290
+ * @typedef {Object} PremiumInfo
291
+ * @property {?number} type The Nitro subscription type (1 = Classic, 2 = Nitro, 3 = Basic)
292
+ * @property {?Date} since The date the subscription started
293
+ */
294
+
295
+ if ('premium_type' in data) {
296
+ /**
297
+ * The Nitro subscription type of the user
298
+ * 0 = None, 1 = Classic, 2 = Nitro, 3 = Basic
299
+ * @type {?number}
300
+ */
301
+ this.premiumType = data.premium_type ?? null;
302
+ } else {
303
+ this.premiumType ??= null;
304
+ }
305
+
306
+ if ('premium_since' in data) {
307
+ /**
308
+ * The date the user's Nitro subscription started
309
+ * @type {?Date}
310
+ */
311
+ this.premiumSince = data.premium_since ? new Date(data.premium_since) : null;
312
+ } else {
313
+ this.premiumSince ??= null;
314
+ }
315
+
316
+ if ('premium_guild_since' in data) {
317
+ /**
318
+ * The date the user boosted a guild
319
+ * @type {?Date}
320
+ */
321
+ this.premiumGuildSince = data.premium_guild_since ? new Date(data.premium_guild_since) : null;
322
+ } else {
323
+ this.premiumGuildSince ??= null;
324
+ }
325
+
326
+ // ─── Profile Effect ─────────────────────────────────────────────────────
327
+
328
+ /**
329
+ * @typedef {Object} ProfileEffect
330
+ * @property {Snowflake} id The effect's id
331
+ * @property {Snowflake} skuId The SKU id of the effect
332
+ * @property {?Date} expiresAt The expiration date of the effect, or null if permanent
333
+ */
334
+
335
+ if (data.user_profile?.profile_effect) {
336
+ /**
337
+ * The profile effect of the user
338
+ * @type {?ProfileEffect}
339
+ */
340
+ this.profileEffect = {
341
+ id: data.user_profile.profile_effect.id,
342
+ skuId: data.user_profile.profile_effect.sku_id,
343
+ expiresAt: data.user_profile.profile_effect.expires_at
344
+ ? new Date(data.user_profile.profile_effect.expires_at)
345
+ : null,
346
+ };
347
+ } else {
348
+ this.profileEffect ??= null;
349
+ }
350
+
351
+ // ─── Theme Colors ────────────────────────────────────────────────────────
352
+
353
+ if (data.user_profile && 'theme_colors' in data.user_profile) {
354
+ /**
355
+ * The theme colors used on this user's profile (array of 2 decimal color ints)
356
+ * @type {?number[]}
357
+ */
358
+ this.themeColors = data.user_profile.theme_colors ?? null;
359
+ } else {
360
+ this.themeColors ??= null;
361
+ }
362
+
363
+ // ─── Badges ─────────────────────────────────────────────────────────────
364
+
365
+ /**
366
+ * @typedef {Object} Badge
367
+ * @property {string} id The badge identifier (e.g. `'premium_tenure_1_month_v2'`)
368
+ * @property {string} description Human-readable description of the badge
369
+ * @property {string} icon The badge icon hash
370
+ * @property {?string} link An optional URL linked to the badge
371
+ */
372
+
373
+ if ('badges' in data) {
374
+ /**
375
+ * The profile badges of the user
376
+ * @type {Badge[]}
377
+ */
378
+ this.badges = data.badges ?? [];
379
+ } else {
380
+ this.badges ??= [];
381
+ }
382
+
383
+ if ('guild_badges' in data) {
384
+ /**
385
+ * The guild-specific badges of the user
386
+ * @type {Object[]}
387
+ */
388
+ this.guildBadges = data.guild_badges ?? [];
389
+ } else {
390
+ this.guildBadges ??= [];
391
+ }
392
+
393
+ // ─── Mutual guilds & friends ─────────────────────────────────────────────
394
+
395
+ /**
396
+ * @typedef {Object} MutualGuild
397
+ * @property {Snowflake} id The guild's id
398
+ * @property {?string} nick The user's nickname in that guild
399
+ */
400
+
401
+ if ('mutual_guilds' in data) {
402
+ /**
403
+ * Mutual guilds shared with the client user (only populated after `getProfile()`)
404
+ * @type {MutualGuild[]}
405
+ */
406
+ this.mutualGuilds = data.mutual_guilds ?? [];
407
+ } else {
408
+ this.mutualGuilds ??= [];
409
+ }
410
+
411
+ if ('mutual_friends_count' in data) {
412
+ /**
413
+ * Number of mutual friends with the client user
414
+ * @type {?number}
415
+ */
416
+ this.mutualFriendsCount = data.mutual_friends_count ?? null;
417
+ } else {
418
+ this.mutualFriendsCount ??= null;
419
+ }
420
+
421
+ // ─── Connected Accounts ──────────────────────────────────────────────────
422
+
423
+ /**
424
+ * @typedef {Object} ConnectedAccount
425
+ * @property {string} type The service type (e.g. `'spotify'`, `'github'`)
426
+ * @property {string} id The account id on that service
427
+ * @property {string} name The account name/username on that service
428
+ * @property {boolean} verified Whether the connection is verified
429
+ */
430
+
431
+ if ('connected_accounts' in data) {
432
+ /**
433
+ * The connected accounts of the user (only populated after `getProfile()`)
434
+ * @type {ConnectedAccount[]}
435
+ */
436
+ this.connectedAccounts = data.connected_accounts ?? [];
437
+ } else {
438
+ this.connectedAccounts ??= [];
439
+ }
440
+
441
+ // ─── Legacy username ─────────────────────────────────────────────────────
442
+
443
+ if ('legacy_username' in data) {
444
+ /**
445
+ * The user's legacy username (before the username migration), if any
446
+ * @type {?string}
447
+ */
448
+ this.legacyUsername = data.legacy_username ?? null;
449
+ } else {
450
+ this.legacyUsername ??= null;
451
+ }
240
452
  }
241
453
 
454
+ // ═══════════════════════════════════════════════════════════════════════════
455
+ // Getters
456
+ // ═══════════════════════════════════════════════════════════════════════════
457
+
242
458
  /**
243
459
  * The primary clan the user is in
244
- * @type {?PrimaryGuild}
460
+ * @type {?UserPrimaryGuild}
245
461
  * @deprecated Use `primaryGuild` instead
246
462
  */
247
463
  get clan() {
@@ -252,10 +468,9 @@ class User extends Base {
252
468
  * The user avatar decoration's hash
253
469
  * @type {?string}
254
470
  * @deprecated Use `avatarDecorationData` instead
255
- * Removed in v4
256
471
  */
257
472
  get avatarDecoration() {
258
- return this.avatarDecorationData?.asset || null;
473
+ return this.avatarDecorationData?.asset ?? null;
259
474
  }
260
475
 
261
476
  /**
@@ -286,40 +501,96 @@ class User extends Base {
286
501
  }
287
502
 
288
503
  /**
289
- * A link to the user's avatar.
290
- * @param {ImageURLOptions} [options={}] Options for the Image URL
291
- * @returns {?string}
504
+ * The hexadecimal version of the user accent color, with a leading hash
505
+ * @type {?string}
506
+ * @readonly
292
507
  */
293
- avatarURL({ format, size, dynamic } = {}) {
294
- if (!this.avatar) return null;
295
- return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size, dynamic);
508
+ get hexAccentColor() {
509
+ if (typeof this.accentColor !== 'number') return this.accentColor;
510
+ return `#${this.accentColor.toString(16).padStart(6, '0')}`;
296
511
  }
297
512
 
298
513
  /**
299
- * A link to the user's avatar decoration.
300
- * @returns {?string}
514
+ * The tag of this user
515
+ * <info>This user's username, or their legacy tag (e.g. `hydrabolt#0001`)
516
+ * if they're using the legacy username system</info>
517
+ * @type {?string}
518
+ * @readonly
301
519
  */
302
- avatarDecorationURL() {
303
- if (!this.avatarDecorationData) return null;
304
- return this.client.rest.cdn.AvatarDecoration(this.avatarDecorationData.asset);
520
+ get tag() {
521
+ return typeof this.username === 'string'
522
+ ? this.discriminator === '0' || this.discriminator === '0000'
523
+ ? this.username
524
+ : `${this.username}#${this.discriminator}`
525
+ : null;
305
526
  }
306
527
 
307
528
  /**
308
- * A link to the user's guild tag badge.
309
- * @returns {?string}
310
- * @deprecated
529
+ * The global name of this user, or their username if they don't have one
530
+ * @type {?string}
531
+ * @readonly
311
532
  */
312
- clanBadgeURL() {
313
- return this.guildTagBadgeURL();
533
+ get displayName() {
534
+ return this.globalName ?? this.username;
314
535
  }
315
536
 
316
537
  /**
317
- * A link to the user's guild tag badge.
318
- * @returns {?string}
538
+ * Whether the user has an active Nitro subscription
539
+ * @type {boolean}
540
+ * @readonly
319
541
  */
320
- guildTagBadgeURL() {
321
- if (!this.primaryGuild || !this.primaryGuild.identityGuildId || !this.primaryGuild.badge) return null;
322
- return this.client.rest.cdn.GuildTagBadge(this.primaryGuild.identityGuildId, this.primaryGuild.badge);
542
+ get nitro() {
543
+ return this.premiumType !== null && this.premiumType > 0;
544
+ }
545
+
546
+ /**
547
+ * The DM between the client's user and this user
548
+ * @type {?DMChannel}
549
+ * @readonly
550
+ */
551
+ get dmChannel() {
552
+ return this.client.users.dmChannel(this.id);
553
+ }
554
+
555
+ /**
556
+ * The note set for this user by the client
557
+ * @type {?string}
558
+ * @readonly
559
+ */
560
+ get note() {
561
+ return this.client.notes.cache.get(this.id) ?? null;
562
+ }
563
+
564
+ /**
565
+ * The voice state of this user
566
+ * @type {VoiceState}
567
+ * @readonly
568
+ */
569
+ get voice() {
570
+ return (
571
+ this.client.voiceStates.cache.get(this.id) ??
572
+ this.client.guilds.cache.find(g => g?.voiceStates?.cache?.get(this.id))?.voiceStates?.cache?.get(this.id) ??
573
+ new VoiceState({ client: this.client }, { user_id: this.id })
574
+ );
575
+ }
576
+
577
+ /**
578
+ * Check relationship status (Client → User)
579
+ * @type {RelationshipType}
580
+ * @readonly
581
+ */
582
+ get relationship() {
583
+ const i = this.client.relationships.cache.get(this.id) ?? 0;
584
+ return RelationshipTypes[parseInt(i)];
585
+ }
586
+
587
+ /**
588
+ * Get friend nickname
589
+ * @type {?string}
590
+ * @readonly
591
+ */
592
+ get friendNickname() {
593
+ return this.client.relationships.friendNicknames.get(this.id) ?? null;
323
594
  }
324
595
 
325
596
  /**
@@ -335,9 +606,22 @@ class User extends Base {
335
606
  return this.client.rest.cdn.DefaultAvatar(index);
336
607
  }
337
608
 
609
+ // ═══════════════════════════════════════════════════════════════════════════
610
+ // URL helpers
611
+ // ═══════════════════════════════════════════════════════════════════════════
612
+
613
+ /**
614
+ * A link to the user's avatar.
615
+ * @param {ImageURLOptions} [options={}] Options for the Image URL
616
+ * @returns {?string}
617
+ */
618
+ avatarURL({ format, size, dynamic } = {}) {
619
+ if (!this.avatar) return null;
620
+ return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size, dynamic);
621
+ }
622
+
338
623
  /**
339
- * A link to the user's avatar if they have one.
340
- * Otherwise a link to their default avatar will be returned.
624
+ * A link to the user's avatar if they have one, otherwise their default avatar.
341
625
  * @param {ImageURLOptions} [options={}] Options for the Image URL
342
626
  * @returns {string}
343
627
  */
@@ -346,20 +630,17 @@ class User extends Base {
346
630
  }
347
631
 
348
632
  /**
349
- * The hexadecimal version of the user accent color, with a leading hash
350
- * <info>The user must be force fetched for this property to be present</info>
351
- * @type {?string}
352
- * @readonly
633
+ * A link to the user's avatar decoration.
634
+ * @returns {?string}
353
635
  */
354
- get hexAccentColor() {
355
- if (typeof this.accentColor !== 'number') return this.accentColor;
356
- return `#${this.accentColor.toString(16).padStart(6, '0')}`;
636
+ avatarDecorationURL() {
637
+ if (!this.avatarDecorationData) return null;
638
+ return this.client.rest.cdn.AvatarDecoration(this.avatarDecorationData.asset);
357
639
  }
358
640
 
359
641
  /**
360
642
  * A link to the user's banner.
361
- * <info>This method will throw an error if called before the user is force fetched.
362
- * See {@link User#banner} for more info</info>
643
+ * <info>This method will throw an error if called before the user is force fetched.</info>
363
644
  * @param {ImageURLOptions} [options={}] Options for the Image URL
364
645
  * @returns {?string}
365
646
  */
@@ -370,37 +651,24 @@ class User extends Base {
370
651
  }
371
652
 
372
653
  /**
373
- * The tag of this user
374
- * <info>This user's username, or their legacy tag (e.g. `hydrabolt#0001`)
375
- * if they're using the legacy username system</info>
376
- * @type {?string}
377
- * @readonly
654
+ * A link to the user's guild tag badge.
655
+ * @returns {?string}
378
656
  */
379
- get tag() {
380
- return typeof this.username === 'string'
381
- ? this.discriminator === '0' || this.discriminator === '0000'
382
- ? this.username
383
- : `${this.username}#${this.discriminator}`
384
- : null;
657
+ guildTagBadgeURL() {
658
+ if (!this.primaryGuild?.identityGuildId || !this.primaryGuild?.badge) return null;
659
+ return this.client.rest.cdn.GuildTagBadge(this.primaryGuild.identityGuildId, this.primaryGuild.badge);
385
660
  }
386
661
 
387
662
  /**
388
- * The global name of this user, or their username if they don't have one
389
- * @type {?string}
390
- * @readonly
663
+ * @deprecated Use `guildTagBadgeURL()` instead
391
664
  */
392
- get displayName() {
393
- return this.globalName ?? this.username;
665
+ clanBadgeURL() {
666
+ return this.guildTagBadgeURL();
394
667
  }
395
668
 
396
- /**
397
- * The DM between the client's user and this user
398
- * @type {?DMChannel}
399
- * @readonly
400
- */
401
- get dmChannel() {
402
- return this.client.users.dmChannel(this.id);
403
- }
669
+ // ═══════════════════════════════════════════════════════════════════════════
670
+ // Actions
671
+ // ═══════════════════════════════════════════════════════════════════════════
404
672
 
405
673
  /**
406
674
  * Creates a DM channel between the client and the user.
@@ -412,80 +680,13 @@ class User extends Base {
412
680
  }
413
681
 
414
682
  /**
415
- * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful.
683
+ * Deletes a DM channel (if one exists) between the client and the user.
416
684
  * @returns {Promise<DMChannel>}
417
685
  */
418
686
  deleteDM() {
419
687
  return this.client.users.deleteDM(this.id);
420
688
  }
421
689
 
422
- /**
423
- * Checks if the user is equal to another.
424
- * It compares id, username, discriminator, avatar, banner, accent color, and bot flags.
425
- * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.
426
- * @param {User} user User to compare with
427
- * @returns {boolean}
428
- */
429
- equals(user) {
430
- return (
431
- user &&
432
- this.id === user.id &&
433
- this.username === user.username &&
434
- this.discriminator === user.discriminator &&
435
- this.globalName === user.globalName &&
436
- this.avatar === user.avatar &&
437
- this.flags?.bitfield === user.flags?.bitfield &&
438
- this.banner === user.banner &&
439
- this.accentColor === user.accentColor &&
440
- this.avatarDecorationData?.asset === user.avatarDecorationData?.asset &&
441
- this.avatarDecorationData?.skuId === user.avatarDecorationData?.skuId &&
442
- this.collectibles?.nameplate?.skuId === user.collectibles?.nameplate?.skuId &&
443
- this.collectibles?.nameplate?.asset === user.collectibles?.nameplate?.asset &&
444
- this.collectibles?.nameplate?.label === user.collectibles?.nameplate?.label &&
445
- this.collectibles?.nameplate?.palette === user.collectibles?.nameplate?.palette &&
446
- this.primaryGuild?.identityGuildId === user.primaryGuild?.identityGuildId &&
447
- this.primaryGuild?.identityEnabled === user.primaryGuild?.identityEnabled &&
448
- this.primaryGuild?.tag === user.primaryGuild?.tag &&
449
- this.primaryGuild?.badge === user.primaryGuild?.badge
450
- );
451
- }
452
-
453
- /**
454
- * Compares the user with an API user object
455
- * @param {APIUser} user The API user object to compare
456
- * @returns {boolean}
457
- * @private
458
- */
459
- _equals(user) {
460
- return (
461
- user &&
462
- this.id === user.id &&
463
- this.username === user.username &&
464
- this.discriminator === user.discriminator &&
465
- this.globalName === user.global_name &&
466
- this.avatar === user.avatar &&
467
- this.flags?.bitfield === user.public_flags &&
468
- ('banner' in user ? this.banner === user.banner : true) &&
469
- ('accent_color' in user ? this.accentColor === user.accent_color : true) &&
470
- ('avatar_decoration_data' in user
471
- ? this.avatarDecorationData?.asset === user.avatar_decoration_data?.asset &&
472
- this.avatarDecorationData?.skuId === user.avatar_decoration_data?.sku_id
473
- : true) &&
474
- ('collectibles' in user
475
- ? this.collectibles?.nameplate?.skuId === user.collectibles?.nameplate?.sku_id &&
476
- this.collectibles?.nameplate?.asset === user.collectibles?.nameplate?.asset &&
477
- this.collectibles?.nameplate?.label === user.collectibles?.nameplate?.label &&
478
- this.collectibles?.nameplate?.palette === user.collectibles?.nameplate?.palette
479
- : true) &&
480
- ('primary_guild' in user
481
- ? this.primaryGuild?.identityGuildId === user.primary_guild?.identity_guild_id &&
482
- this.primaryGuild?.identityEnabled === user.primary_guild?.identity_enabled &&
483
- this.primaryGuild?.tag === user.primary_guild?.tag &&
484
- this.primaryGuild?.badge === user.primary_guild?.badge
485
- : true)
486
- );
487
- }
488
-
489
690
  /**
490
691
  * Fetches this user.
491
692
  * @param {boolean} [force=true] Whether to skip the cache check and request the API
@@ -510,109 +711,205 @@ class User extends Base {
510
711
  getProfile(guildId) {
511
712
  return this.client.api.users(this.id).profile.get({
512
713
  query: {
513
- with_mutual_guilds: true,
514
- with_mutual_friends: true,
714
+ with_mutual_guilds: true,
715
+ with_mutual_friends: true,
515
716
  with_mutual_friends_count: true,
516
- guild_id: guildId,
717
+ guild_id: guildId,
517
718
  },
518
719
  });
519
720
  }
520
721
 
521
722
  /**
522
- * When concatenated with a string, this automatically returns the user's mention instead of the User object.
523
- * @returns {string}
524
- * @example
525
- * // Logs: Hello from <@123456789012345678>!
526
- * console.log(`Hello from ${user}!`);
723
+ * Updates the note of this user.
724
+ * @param {string|null} [note=null] The new note value
725
+ * @returns {Promise<User>}
527
726
  */
528
- toString() {
529
- return `<@${this.id}>`;
727
+ async setNote(note = null) {
728
+ await this.client.notes.updateNote(this.id, note);
729
+ return this;
530
730
  }
531
731
 
532
- toJSON(...props) {
533
- const json = super.toJSON(
534
- {
535
- createdTimestamp: true,
536
- defaultAvatarURL: true,
537
- hexAccentColor: true,
538
- tag: true,
539
- },
540
- ...props,
541
- );
542
- json.avatarURL = this.avatarURL();
543
- json.displayAvatarURL = this.displayAvatarURL();
544
- json.bannerURL = this.banner ? this.bannerURL() : this.banner;
545
- json.guildTagBadgeURL = this.guildTagBadgeURL();
546
- return json;
732
+ /**
733
+ * Send a friend request to this user.
734
+ * @returns {Promise<boolean>}
735
+ */
736
+ sendFriendRequest() {
737
+ return this.client.relationships.sendFriendRequest(this);
547
738
  }
548
739
 
549
740
  /**
550
- * The function updates the note of a user and returns the updated user.
551
- * @param {string|null|undefined} [note=null] - The `note` parameter is the new value that you want to set for the note of the
552
- * user. It is an optional parameter and its default value is `null`.
553
- * @returns {Promise<User>} The `setNote` method is returning the `User` object.
741
+ * Unblock / unfriend / cancel a friend request for this user.
742
+ * @returns {Promise<boolean>}
554
743
  */
555
- async setNote(note = null) {
556
- await this.client.notes.updateNote(this.id, note);
557
- return this;
744
+ deleteRelationship() {
745
+ return this.client.relationships.deleteRelationship(this);
558
746
  }
559
747
 
748
+ // ═══════════════════════════════════════════════════════════════════════════
749
+ // Display Name Style
750
+ // ═══════════════════════════════════════════════════════════════════════════
751
+
560
752
  /**
561
- * The function returns the note associated with a specific client ID from a cache.
562
- * @type {?string} The note that corresponds to the given id.
753
+ * Font name ID mapping for display name styles.
754
+ * @type {Object<string, number>}
755
+ * @readonly
756
+ * @static
563
757
  */
564
- get note() {
565
- return this.client.notes.cache.get(this.id);
758
+ static get FONT_MAP() {
759
+ return {
760
+ Sans: 11,
761
+ Tempo: 12,
762
+ Sakura: 3,
763
+ JellyBean: 4,
764
+ Modern: 6,
765
+ Medieval: 7,
766
+ '8Bit': 8,
767
+ Vampire: 10,
768
+ };
566
769
  }
567
770
 
568
771
  /**
569
- * The voice state of this member
570
- * @type {VoiceState}
772
+ * Effect name ID mapping for display name styles.
773
+ * @type {Object<string, number>}
571
774
  * @readonly
775
+ * @static
572
776
  */
573
- get voice() {
574
- return (
575
- this.client.voiceStates.cache.get(this.id) ??
576
- this.client.guilds.cache.find(g => g?.voiceStates?.cache?.get(this.id))?.voiceStates?.cache?.get(this.id) ??
577
- new VoiceState({ client: this.client }, { user_id: this.id })
578
- );
777
+ static get EFFECT_MAP() {
778
+ return {
779
+ Solid: 1,
780
+ Gradient: 2,
781
+ Neon: 3,
782
+ Toon: 4,
783
+ Pop: 5,
784
+ };
579
785
  }
580
786
 
581
787
  /**
582
- * Send Friend Request to the user
583
- * @type {boolean}
584
- * @returns {Promise<boolean>}
788
+ * Resolves a decimal color integer from a hex string or number.
789
+ * @param {string|number} color Hex string (`'#RRGGBB'` / `'RRGGBB'`) or decimal number
790
+ * @returns {number}
791
+ * @static
585
792
  */
586
- sendFriendRequest() {
587
- return this.client.relationships.sendFriendRequest(this);
793
+ static resolveColor(color) {
794
+ if (typeof color === 'string') {
795
+ return parseInt(color.startsWith('#') ? color.slice(1) : color, 16);
796
+ }
797
+ return color;
588
798
  }
589
799
 
800
+ // ═══════════════════════════════════════════════════════════════════════════
801
+ // Equality / serialisation
802
+ // ═══════════════════════════════════════════════════════════════════════════
803
+
590
804
  /**
591
- * Unblock / Unfriend / Cancels a friend request
592
- * @type {boolean}
593
- * @returns {Promise<boolean>}
805
+ * Checks if the user is equal to another User instance.
806
+ * @param {User} user User to compare with
807
+ * @returns {boolean}
594
808
  */
595
- deleteRelationship() {
596
- return this.client.relationships.deleteRelationship(this);
809
+ equals(user) {
810
+ return (
811
+ user &&
812
+ this.id === user.id &&
813
+ this.username === user.username &&
814
+ this.discriminator === user.discriminator &&
815
+ this.globalName === user.globalName &&
816
+ this.avatar === user.avatar &&
817
+ this.flags?.bitfield === user.flags?.bitfield &&
818
+ this.banner === user.banner &&
819
+ this.accentColor === user.accentColor &&
820
+ this.bio === user.bio &&
821
+ this.pronouns === user.pronouns &&
822
+ this.premiumType === user.premiumType &&
823
+ this.avatarDecorationData?.asset === user.avatarDecorationData?.asset &&
824
+ this.avatarDecorationData?.skuId === user.avatarDecorationData?.skuId &&
825
+ this.collectibles?.nameplate?.skuId === user.collectibles?.nameplate?.skuId &&
826
+ this.collectibles?.nameplate?.asset === user.collectibles?.nameplate?.asset &&
827
+ this.collectibles?.nameplate?.label === user.collectibles?.nameplate?.label &&
828
+ this.collectibles?.nameplate?.palette === user.collectibles?.nameplate?.palette &&
829
+ this.primaryGuild?.identityGuildId === user.primaryGuild?.identityGuildId &&
830
+ this.primaryGuild?.identityEnabled === user.primaryGuild?.identityEnabled &&
831
+ this.primaryGuild?.tag === user.primaryGuild?.tag &&
832
+ this.primaryGuild?.badge === user.primaryGuild?.badge &&
833
+ this.displayNameStyles?.fontId === user.displayNameStyles?.fontId &&
834
+ this.displayNameStyles?.effectId === user.displayNameStyles?.effectId &&
835
+ JSON.stringify(this.displayNameStyles?.colors) === JSON.stringify(user.displayNameStyles?.colors) &&
836
+ this.profileEffect?.id === user.profileEffect?.id &&
837
+ JSON.stringify(this.themeColors) === JSON.stringify(user.themeColors)
838
+ );
597
839
  }
598
840
 
599
841
  /**
600
- * Check relationship status (Client -> User)
601
- * @type {RelationshipType}
602
- * @readonly
842
+ * Compares the user with a raw API user object.
843
+ * @param {APIUser} user The API user object to compare
844
+ * @returns {boolean}
845
+ * @private
603
846
  */
604
- get relationship() {
605
- const i = this.client.relationships.cache.get(this.id) ?? 0;
606
- return RelationshipTypes[parseInt(i)];
847
+ _equals(user) {
848
+ return (
849
+ user &&
850
+ this.id === user.id &&
851
+ this.username === user.username &&
852
+ this.discriminator === user.discriminator &&
853
+ this.globalName === user.global_name &&
854
+ this.avatar === user.avatar &&
855
+ this.flags?.bitfield === user.public_flags &&
856
+ ('banner' in user ? this.banner === user.banner : true) &&
857
+ ('accent_color' in user ? this.accentColor === user.accent_color : true) &&
858
+ ('bio' in user ? this.bio === user.bio : true) &&
859
+ ('pronouns' in user ? this.pronouns === user.pronouns : true) &&
860
+ ('premium_type' in user ? this.premiumType === user.premium_type : true) &&
861
+ ('avatar_decoration_data' in user
862
+ ? this.avatarDecorationData?.asset === user.avatar_decoration_data?.asset &&
863
+ this.avatarDecorationData?.skuId === user.avatar_decoration_data?.sku_id
864
+ : true) &&
865
+ ('collectibles' in user
866
+ ? this.collectibles?.nameplate?.skuId === user.collectibles?.nameplate?.sku_id &&
867
+ this.collectibles?.nameplate?.asset === user.collectibles?.nameplate?.asset &&
868
+ this.collectibles?.nameplate?.label === user.collectibles?.nameplate?.label &&
869
+ this.collectibles?.nameplate?.palette === user.collectibles?.nameplate?.palette
870
+ : true) &&
871
+ ('primary_guild' in user
872
+ ? this.primaryGuild?.identityGuildId === user.primary_guild?.identity_guild_id &&
873
+ this.primaryGuild?.identityEnabled === user.primary_guild?.identity_enabled &&
874
+ this.primaryGuild?.tag === user.primary_guild?.tag &&
875
+ this.primaryGuild?.badge === user.primary_guild?.badge
876
+ : true) &&
877
+ ('display_name_styles' in user
878
+ ? this.displayNameStyles?.fontId === (user.display_name_styles?.font_id ?? user.display_name_styles?.fontId) &&
879
+ this.displayNameStyles?.effectId === (user.display_name_styles?.effect_id ?? user.display_name_styles?.effectId) &&
880
+ JSON.stringify(this.displayNameStyles?.colors) === JSON.stringify(user.display_name_styles?.colors)
881
+ : true)
882
+ );
607
883
  }
608
884
 
609
885
  /**
610
- * Get friend nickname
611
- * @type {?string}
612
- * @readonly
886
+ * When concatenated with a string, returns the user's mention.
887
+ * @returns {string}
888
+ * @example
889
+ * console.log(`Hello from ${user}!`); // Hello from <@123456789012345678>!
613
890
  */
614
- get friendNickname() {
615
- return this.client.relationships.friendNicknames.get(this.id);
891
+ toString() {
892
+ return `<@${this.id}>`;
893
+ }
894
+
895
+ toJSON(...props) {
896
+ const json = super.toJSON(
897
+ {
898
+ createdTimestamp: true,
899
+ defaultAvatarURL: true,
900
+ hexAccentColor: true,
901
+ tag: true,
902
+ displayName: true,
903
+ nitro: true,
904
+ },
905
+ ...props,
906
+ );
907
+ json.avatarURL = this.avatarURL();
908
+ json.displayAvatarURL = this.displayAvatarURL();
909
+ json.bannerURL = this.banner ? this.bannerURL() : this.banner;
910
+ json.guildTagBadgeURL = this.guildTagBadgeURL();
911
+ json.avatarDecorationURL = this.avatarDecorationURL();
912
+ return json;
616
913
  }
617
914
  }
618
915
 
@@ -624,12 +921,10 @@ class User extends Base {
624
921
  * @param {string|MessagePayload|MessageOptions} options The options to provide
625
922
  * @returns {Promise<Message>}
626
923
  * @example
627
- * // Send a direct message
628
924
  * user.send('Hello!')
629
925
  * .then(message => console.log(`Sent message: ${message.content} to ${user.tag}`))
630
926
  * .catch(console.error);
631
927
  */
632
-
633
928
  TextBasedChannel.applyToClass(User);
634
929
 
635
930
  module.exports = User;
@@ -637,4 +932,4 @@ module.exports = User;
637
932
  /**
638
933
  * @external APIUser
639
934
  * @see {@link https://discord.com/developers/docs/resources/user#user-object}
640
- */
935
+ */
@@ -3919,6 +3919,37 @@ export interface Collectibles {
3919
3919
  nameplate: NameplateData | null;
3920
3920
  }
3921
3921
 
3922
+ export interface DisplayNameStyles {
3923
+ fontId: number | null;
3924
+ effectId: number | null;
3925
+ colors: number[];
3926
+ }
3927
+
3928
+ export interface ProfileEffect {
3929
+ id: Snowflake;
3930
+ skuId: Snowflake;
3931
+ expiresAt: Date | null;
3932
+ }
3933
+
3934
+ export interface UserBadge {
3935
+ id: string;
3936
+ description: string;
3937
+ icon: string;
3938
+ link?: string;
3939
+ }
3940
+
3941
+ export interface MutualGuild {
3942
+ id: Snowflake;
3943
+ nick: string | null;
3944
+ }
3945
+
3946
+ export interface ConnectedAccount {
3947
+ type: string;
3948
+ id: string;
3949
+ name: string;
3950
+ verified: boolean;
3951
+ }
3952
+
3922
3953
  export type WidgetType = 'favorite_games' | 'current_games' | 'played_games' | 'want_to_play_games';
3923
3954
 
3924
3955
  export interface WidgetGameData {
@@ -4062,6 +4093,20 @@ export class User extends PartialTextBasedChannel(Base) {
4062
4093
  public readonly voice?: VoiceState;
4063
4094
  public readonly relationship: RelationshipTypes;
4064
4095
  public readonly friendNickname: string | null | undefined;
4096
+ public displayNameStyles: DisplayNameStyles | null;
4097
+ public pronouns: string | null;
4098
+ public themeColors: number[] | null;
4099
+ public profileEffect: ProfileEffect | null;
4100
+ public premiumType: number | null;
4101
+ public premiumSince: Date | null;
4102
+ public premiumGuildSince: Date | null;
4103
+ public badges: UserBadge[];
4104
+ public guildBadges: unknown[];
4105
+ public mutualGuilds: MutualGuild[];
4106
+ public mutualFriendsCount: number | null;
4107
+ public connectedAccounts: ConnectedAccount[];
4108
+ public legacyUsername: string | null;
4109
+ public readonly nitro: boolean;
4065
4110
  public primaryGuild: UserPrimaryGuild | null;
4066
4111
  /** @deprecated Use {@link User.primaryGuild} instead */
4067
4112
  public clan: PrimaryGuild | null;
@@ -4078,7 +4123,7 @@ export class User extends PartialTextBasedChannel(Base) {
4078
4123
  public fetch(force?: boolean): Promise<User>;
4079
4124
  public setNote(note: string | null | undefined): Promise<this>;
4080
4125
  public toString(): UserMention;
4081
- public getProfile(guildId?: Snowflake): Promise<any>;
4126
+ public getProfile(guildId?: Snowflake): Promise<UserProfile>;
4082
4127
  public sendFriendRequest(): Promise<boolean>;
4083
4128
  public deleteRelationship(): Promise<boolean>;
4084
4129
  }
@@ -4130,7 +4175,9 @@ export class Util extends null {
4130
4175
  public static mergeDefault(def: unknown, given: unknown): unknown;
4131
4176
  public static moveElementInArray(array: unknown[], element: unknown, newIndex: number, offset?: boolean): number;
4132
4177
  public static parseEmoji(text: string): { animated: boolean; name: string; id: Snowflake | null } | null;
4133
- public static resolveColor(color: ColorResolvable): number;
4178
+ public static readonly FONT_MAP: Record<FontName, number>;
4179
+ public static readonly EFFECT_MAP: Record<EffectName, number>;
4180
+ public static resolveColor(color: string | number): number;
4134
4181
  public static resolvePartialEmoji(emoji: EmojiIdentifierResolvable): Partial<APIPartialEmoji> | null;
4135
4182
  public static verifyString(data: string, error?: typeof Error, errorMessage?: string, allowEmpty?: boolean): string;
4136
4183
  public static setPosition<T extends AnyChannel | Role>(