djs-selfbot-v13 3.1.7 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +18 -35
  2. package/package.json +85 -100
  3. package/src/client/BaseClient.js +3 -4
  4. package/src/client/Client.js +249 -530
  5. package/src/client/actions/Action.js +18 -13
  6. package/src/client/actions/ActionsManager.js +7 -1
  7. package/src/client/actions/AutoModerationActionExecution.js +1 -0
  8. package/src/client/actions/AutoModerationRuleCreate.js +1 -0
  9. package/src/client/actions/AutoModerationRuleDelete.js +1 -0
  10. package/src/client/actions/AutoModerationRuleUpdate.js +1 -0
  11. package/src/client/actions/GuildMemberRemove.js +0 -1
  12. package/src/client/actions/GuildMemberUpdate.js +0 -1
  13. package/src/client/actions/MessageCreate.js +0 -4
  14. package/src/client/actions/PresenceUpdate.js +17 -16
  15. package/src/client/websocket/WebSocketManager.js +11 -31
  16. package/src/client/websocket/WebSocketShard.js +39 -38
  17. package/src/client/websocket/handlers/CALL_CREATE.js +3 -3
  18. package/src/client/websocket/handlers/CALL_DELETE.js +2 -2
  19. package/src/client/websocket/handlers/CALL_UPDATE.js +2 -2
  20. package/src/client/websocket/handlers/CHANNEL_RECIPIENT_ADD.js +16 -13
  21. package/src/client/websocket/handlers/CHANNEL_RECIPIENT_REMOVE.js +11 -11
  22. package/src/client/websocket/handlers/GUILD_CREATE.js +19 -13
  23. package/src/client/websocket/handlers/GUILD_MEMBER_ADD.js +0 -1
  24. package/src/client/websocket/handlers/INTERACTION_MODAL_CREATE.js +1 -0
  25. package/src/client/websocket/handlers/MESSAGE_POLL_VOTE_ADD.js +22 -0
  26. package/src/client/websocket/handlers/MESSAGE_POLL_VOTE_REMOVE.js +12 -0
  27. package/src/client/websocket/handlers/READY.js +90 -140
  28. package/src/client/websocket/handlers/RELATIONSHIP_ADD.js +7 -5
  29. package/src/client/websocket/handlers/RELATIONSHIP_REMOVE.js +7 -5
  30. package/src/client/websocket/handlers/RELATIONSHIP_UPDATE.js +32 -9
  31. package/src/client/websocket/handlers/USER_GUILD_SETTINGS_UPDATE.js +2 -8
  32. package/src/client/websocket/handlers/USER_NOTE_UPDATE.js +1 -1
  33. package/src/client/websocket/handlers/USER_REQUIRED_ACTION_UPDATE.js +78 -0
  34. package/src/client/websocket/handlers/USER_SETTINGS_UPDATE.js +1 -5
  35. package/src/client/websocket/handlers/VOICE_CHANNEL_STATUS_UPDATE.js +12 -0
  36. package/src/client/websocket/handlers/index.js +17 -20
  37. package/src/errors/Messages.js +25 -69
  38. package/src/index.js +13 -43
  39. package/src/managers/ApplicationCommandManager.js +9 -12
  40. package/src/managers/ApplicationCommandPermissionsManager.js +3 -11
  41. package/src/managers/ChannelManager.js +2 -3
  42. package/src/managers/ClientUserSettingManager.js +162 -280
  43. package/src/managers/GuildBanManager.js +47 -1
  44. package/src/managers/GuildChannelManager.js +2 -16
  45. package/src/managers/GuildForumThreadManager.js +24 -30
  46. package/src/managers/GuildManager.js +1 -1
  47. package/src/managers/GuildMemberManager.js +50 -222
  48. package/src/managers/GuildSettingManager.js +22 -15
  49. package/src/managers/MessageManager.js +42 -44
  50. package/src/managers/PermissionOverwriteManager.js +1 -1
  51. package/src/managers/ReactionUserManager.js +5 -5
  52. package/src/managers/RelationshipManager.js +83 -76
  53. package/src/managers/ThreadManager.js +12 -45
  54. package/src/managers/ThreadMemberManager.js +1 -1
  55. package/src/managers/UserManager.js +6 -10
  56. package/src/managers/UserNoteManager.js +53 -0
  57. package/src/rest/APIRequest.js +48 -20
  58. package/src/rest/DiscordAPIError.js +17 -16
  59. package/src/rest/RESTManager.js +1 -21
  60. package/src/rest/RequestHandler.js +35 -21
  61. package/src/structures/ApplicationCommand.js +19 -456
  62. package/src/structures/ApplicationRoleConnectionMetadata.js +3 -0
  63. package/src/structures/AutoModerationRule.js +5 -5
  64. package/src/structures/AutocompleteInteraction.js +1 -0
  65. package/src/structures/BaseGuildTextChannel.js +10 -12
  66. package/src/structures/BaseGuildVoiceChannel.js +16 -18
  67. package/src/structures/{Call.js → CallState.js} +17 -12
  68. package/src/structures/CategoryChannel.js +2 -0
  69. package/src/structures/Channel.js +2 -3
  70. package/src/structures/ClientPresence.js +20 -19
  71. package/src/structures/ClientUser.js +117 -338
  72. package/src/structures/ContextMenuInteraction.js +1 -1
  73. package/src/structures/DMChannel.js +29 -92
  74. package/src/structures/ForumChannel.js +0 -10
  75. package/src/structures/GroupDMChannel.js +387 -0
  76. package/src/structures/Guild.js +135 -271
  77. package/src/structures/GuildAuditLogs.js +0 -5
  78. package/src/structures/GuildChannel.js +16 -2
  79. package/src/structures/GuildMember.js +27 -145
  80. package/src/structures/Interaction.js +1 -62
  81. package/src/structures/Invite.js +35 -52
  82. package/src/structures/Message.js +220 -203
  83. package/src/structures/MessageAttachment.js +11 -0
  84. package/src/structures/MessageButton.js +1 -67
  85. package/src/structures/MessageEmbed.js +1 -1
  86. package/src/structures/MessageMentions.js +3 -2
  87. package/src/structures/MessagePayload.js +6 -46
  88. package/src/structures/MessagePoll.js +238 -0
  89. package/src/structures/MessageReaction.js +1 -1
  90. package/src/structures/MessageSelectMenu.js +1 -252
  91. package/src/structures/Modal.js +70 -188
  92. package/src/structures/Presence.js +787 -129
  93. package/src/structures/Role.js +18 -2
  94. package/src/structures/SelectMenuInteraction.js +2 -151
  95. package/src/structures/Team.js +0 -49
  96. package/src/structures/TextInputComponent.js +0 -70
  97. package/src/structures/ThreadChannel.js +0 -19
  98. package/src/structures/User.js +145 -339
  99. package/src/structures/UserContextMenuInteraction.js +2 -2
  100. package/src/structures/VoiceState.js +74 -39
  101. package/src/structures/WebEmbed.js +38 -52
  102. package/src/structures/Webhook.js +17 -11
  103. package/src/structures/interfaces/Application.js +146 -23
  104. package/src/structures/interfaces/TextBasedChannel.js +409 -256
  105. package/src/util/ApplicationFlags.js +1 -1
  106. package/src/util/AttachmentFlags.js +38 -0
  107. package/src/util/Constants.js +120 -285
  108. package/src/util/Formatters.js +16 -2
  109. package/src/util/InviteFlags.js +29 -0
  110. package/src/util/LimitedCollection.js +1 -1
  111. package/src/util/Options.js +48 -74
  112. package/src/util/Permissions.js +15 -0
  113. package/src/util/PurchasedFlags.js +2 -0
  114. package/src/util/RemoteAuth.js +221 -356
  115. package/src/util/RoleFlags.js +37 -0
  116. package/src/util/Sweepers.js +1 -1
  117. package/src/util/Util.js +158 -32
  118. package/typings/enums.d.ts +24 -73
  119. package/typings/index.d.ts +978 -1288
  120. package/typings/rawDataTypes.d.ts +68 -9
  121. package/src/client/actions/InteractionCreate.js +0 -115
  122. package/src/client/websocket/handlers/APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE.js +0 -23
  123. package/src/client/websocket/handlers/GUILD_APPLICATION_COMMANDS_UPDATE.js +0 -11
  124. package/src/client/websocket/handlers/GUILD_MEMBER_LIST_UPDATE.js +0 -55
  125. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUNDS_UPDATE.js +0 -0
  126. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_CREATE.js +0 -0
  127. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_DELETE.js +0 -0
  128. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_UPDATE.js +0 -0
  129. package/src/client/websocket/handlers/INTERACTION_CREATE.js +0 -16
  130. package/src/client/websocket/handlers/INTERACTION_FAILURE.js +0 -18
  131. package/src/client/websocket/handlers/INTERACTION_SUCCESS.js +0 -30
  132. package/src/client/websocket/handlers/MESSAGE_ACK.js +0 -16
  133. package/src/client/websocket/handlers/SOUNDBOARD_SOUNDS.js +0 -0
  134. package/src/client/websocket/handlers/VOICE_CHANNEL_EFFECT_SEND.js +0 -0
  135. package/src/managers/DeveloperPortalManager.js +0 -104
  136. package/src/managers/GuildApplicationCommandManager.js +0 -28
  137. package/src/managers/GuildFolderManager.js +0 -24
  138. package/src/managers/SessionManager.js +0 -57
  139. package/src/rest/CaptchaSolver.js +0 -132
  140. package/src/structures/ClientApplication.js +0 -204
  141. package/src/structures/DeveloperPortalApplication.js +0 -520
  142. package/src/structures/GuildFolder.js +0 -75
  143. package/src/structures/InteractionResponse.js +0 -114
  144. package/src/structures/PartialGroupDMChannel.js +0 -433
  145. package/src/structures/RichPresence.js +0 -722
  146. package/src/structures/Session.js +0 -81
  147. package/src/util/Voice.js +0 -1456
  148. package/src/util/arRPC/index.js +0 -229
  149. package/src/util/arRPC/process/detectable.json +0 -1
  150. package/src/util/arRPC/process/index.js +0 -102
  151. package/src/util/arRPC/process/native/index.js +0 -5
  152. package/src/util/arRPC/process/native/linux.js +0 -37
  153. package/src/util/arRPC/process/native/win32.js +0 -25
  154. package/src/util/arRPC/transports/ipc.js +0 -281
  155. package/src/util/arRPC/transports/websocket.js +0 -128
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { randomUUID } = require('node:crypto');
3
4
  const Base = require('./Base');
4
- const { Emoji } = require('./Emoji');
5
- const { CustomStatus, SpotifyRPC, RichPresence } = require('./RichPresence');
6
5
  const ActivityFlags = require('../util/ActivityFlags');
7
6
  const { ActivityTypes } = require('../util/Constants');
8
7
  const Util = require('../util/Util');
@@ -77,7 +76,7 @@ class Presence extends Base {
77
76
  return this.guild.members.resolve(this.userId);
78
77
  }
79
78
 
80
- _patch(data, fromClient) {
79
+ _patch(data) {
81
80
  if ('status' in data) {
82
81
  /**
83
82
  * The status of this presence
@@ -90,17 +89,17 @@ class Presence extends Base {
90
89
 
91
90
  if ('activities' in data) {
92
91
  /**
93
- * The activities of this presence
94
- * @type {Activity[]}
92
+ * The activities of this presence (Always `Activity[]` if not ClientUser)
93
+ * @type {CustomStatus[]|RichPresence[]|SpotifyRPC[]|Activity[]}
95
94
  */
96
95
  this.activities = data.activities.map(activity => {
97
- if (fromClient === true) {
96
+ if (this.userId == this.client.user.id) {
98
97
  if ([ActivityTypes.CUSTOM, 'CUSTOM'].includes(activity.type)) {
99
- return new CustomStatus(activity, this);
98
+ return new CustomStatus(this.client, activity);
100
99
  } else if (activity.id == 'spotify:1') {
101
- return new SpotifyRPC(this.client, activity, this);
100
+ return new SpotifyRPC(this.client, activity);
102
101
  } else {
103
- return new RichPresence(this.client, activity, false, this);
102
+ return new RichPresence(this.client, activity);
104
103
  }
105
104
  } else {
106
105
  return new Activity(this, activity);
@@ -168,6 +167,11 @@ class Presence extends Base {
168
167
  * * **`desktop`**
169
168
  * * **`samsung`** - playing on Samsung Galaxy
170
169
  * * **`xbox`** - playing on Xbox Live
170
+ * * **`ios`**
171
+ * * **`android`**
172
+ * * **`embedded`**
173
+ * * **`ps4`**
174
+ * * **`ps5`**
171
175
  * @typedef {string} ActivityPlatform
172
176
  */
173
177
 
@@ -176,6 +180,9 @@ class Presence extends Base {
176
180
  */
177
181
  class Activity {
178
182
  constructor(presence, data) {
183
+ if (!(presence instanceof Presence)) {
184
+ throw new Error("Class constructor Activity cannot be invoked without 'presence'");
185
+ }
179
186
  /**
180
187
  * The presence of the Activity
181
188
  * @type {Presence}
@@ -184,126 +191,184 @@ class Activity {
184
191
  */
185
192
  Object.defineProperty(this, 'presence', { value: presence });
186
193
 
187
- /**
188
- * The activity's id
189
- * @type {string}
190
- */
191
- this.id = data.id;
194
+ this._patch(data);
195
+ }
192
196
 
193
- /**
194
- * The activity's name
195
- * @type {string}
196
- */
197
- this.name = data.name;
197
+ _patch(data = {}) {
198
+ if ('id' in data) {
199
+ /**
200
+ * The activity's id
201
+ * @type {string}
202
+ */
203
+ this.id = data.id;
204
+ }
198
205
 
199
- /**
200
- * The activity status's type
201
- * @type {ActivityType}
202
- */
203
- this.type = typeof data.type === 'number' ? ActivityTypes[data.type] : data.type;
206
+ if ('name' in data) {
207
+ /**
208
+ * The activity's name
209
+ * @type {string}
210
+ */
211
+ this.name = data.name;
212
+ }
204
213
 
205
- /**
206
- * If the activity is being streamed, a link to the stream
207
- * @type {?string}
208
- */
209
- this.url = data.url ?? null;
214
+ if ('type' in data) {
215
+ /**
216
+ * The activity status's type
217
+ * @type {ActivityType}
218
+ */
219
+ this.type = typeof data.type === 'number' ? ActivityTypes[data.type] : data.type;
220
+ }
210
221
 
211
- /**
212
- * Details about the activity
213
- * @type {?string}
214
- */
215
- this.details = data.details ?? null;
222
+ if ('url' in data) {
223
+ /**
224
+ * If the activity is being streamed, a link to the stream
225
+ * @type {?string}
226
+ */
227
+ this.url = data.url;
228
+ } else {
229
+ this.url = null;
230
+ }
216
231
 
217
- /**
218
- * State of the activity
219
- * @type {?string}
220
- */
221
- this.state = data.state ?? null;
232
+ if ('created_at' in data || 'createdTimestamp' in data) {
233
+ /**
234
+ * Creation date of the activity
235
+ * @type {number}
236
+ */
237
+ this.createdTimestamp = data.created_at || data.createdTimestamp;
238
+ }
222
239
 
223
- /**
224
- * The id of the application associated with this activity
225
- * @type {?Snowflake}
226
- */
227
- this.applicationId = data.application_id ?? null;
240
+ if ('session_id' in data) {
241
+ /**
242
+ * The game's or Spotify session's id
243
+ * @type {?string}
244
+ */
245
+ this.sessionId = data.session_id;
246
+ } else {
247
+ this.sessionId = this.presence.client?.sessionId;
248
+ }
228
249
 
229
- /**
230
- * Represents timestamps of an activity
231
- * @typedef {Object} ActivityTimestamps
232
- * @property {?Date} start When the activity started
233
- * @property {?Date} end When the activity will end
234
- */
250
+ if ('platform' in data) {
251
+ /**
252
+ * The platform the game is being played on
253
+ * @type {?ActivityPlatform}
254
+ */
255
+ this.platform = data.platform;
256
+ } else {
257
+ this.platform = null;
258
+ }
235
259
 
236
- /**
237
- * Timestamps for the activity
238
- * @type {?ActivityTimestamps}
239
- */
240
- this.timestamps = data.timestamps
241
- ? {
242
- start: data.timestamps.start ? new Date(Number(data.timestamps.start)) : null,
243
- end: data.timestamps.end ? new Date(Number(data.timestamps.end)) : null,
244
- }
245
- : null;
260
+ if ('timestamps' in data && data.timestamps) {
261
+ /**
262
+ * Represents timestamps of an activity
263
+ * @typedef {Object} ActivityTimestamps
264
+ * @property {?number} start When the activity started
265
+ * @property {?number} end When the activity will end
266
+ */
246
267
 
247
- /**
248
- * The Spotify song's id
249
- * @type {?string}
250
- */
251
- this.syncId = data.sync_id ?? null;
268
+ /**
269
+ * Timestamps for the activity
270
+ * @type {?ActivityTimestamps}
271
+ */
272
+ this.timestamps = {
273
+ start: data.timestamps.start ? new Date(data.timestamps.start).getTime() : null,
274
+ end: data.timestamps.end ? new Date(data.timestamps.end).getTime() : null,
275
+ };
276
+ } else {
277
+ this.timestamps = null;
278
+ }
252
279
 
253
- /**
254
- * The platform the game is being played on
255
- * @type {?ActivityPlatform}
256
- */
257
- this.platform = data.platform ?? null;
280
+ if ('application_id' in data || 'applicationId' in data) {
281
+ /**
282
+ * The id of the application associated with this activity
283
+ * @type {?Snowflake}
284
+ */
285
+ this.applicationId = data.application_id || data.applicationId;
286
+ } else {
287
+ this.applicationId = null;
288
+ }
258
289
 
259
- /**
260
- * Represents a party of an activity
261
- * @typedef {Object} ActivityParty
262
- * @property {?string} id The party's id
263
- * @property {number[]} size Size of the party as `[current, max]`
264
- */
290
+ if ('details' in data) {
291
+ /**
292
+ * Details about the activity
293
+ * @type {?string}
294
+ */
295
+ this.details = data.details;
296
+ } else {
297
+ this.details = null;
298
+ }
265
299
 
266
- /**
267
- * Party of the activity
268
- * @type {?ActivityParty}
269
- */
270
- this.party = data.party ?? null;
300
+ if ('state' in data) {
301
+ /**
302
+ * State of the activity
303
+ * @type {?string}
304
+ */
305
+ this.state = data.state;
306
+ } else {
307
+ this.state = null;
308
+ }
271
309
 
272
- /**
273
- * Assets for rich presence
274
- * @type {?RichPresenceAssets}
275
- */
276
- this.assets = data.assets ? new RichPresenceAssets(this, data.assets) : null;
310
+ if ('sync_id' in data || 'syncId' in data) {
311
+ /**
312
+ * The Spotify song's id
313
+ * @type {?string}
314
+ */
315
+ this.syncId = data.sync_id || data.syncId;
316
+ } else {
317
+ this.syncId = null;
318
+ }
277
319
 
278
- /**
279
- * Flags that describe the activity
280
- * @type {Readonly<ActivityFlags>}
281
- */
282
- this.flags = new ActivityFlags(data.flags).freeze();
320
+ if ('flags' in data) {
321
+ /**
322
+ * Flags that describe the activity
323
+ * @type {Readonly<ActivityFlags>}
324
+ */
325
+ this.flags = new ActivityFlags(data.flags).freeze();
326
+ } else {
327
+ this.flags = new ActivityFlags().freeze();
328
+ }
283
329
 
284
- /**
285
- * Emoji for a custom activity
286
- * @type {?Emoji}
287
- */
288
- this.emoji = data.emoji ? new Emoji(presence.client, data.emoji) : null;
330
+ if ('buttons' in data) {
331
+ /**
332
+ * The labels of the buttons of this rich presence
333
+ * @type {string[]}
334
+ */
335
+ this.buttons = data.buttons;
336
+ } else {
337
+ this.buttons = [];
338
+ }
289
339
 
290
- /**
291
- * The game's or Spotify session's id
292
- * @type {?string}
293
- */
294
- this.sessionId = data.session_id ?? null;
340
+ if ('emoji' in data && data.emoji) {
341
+ /**
342
+ * Emoji for a custom activity
343
+ * @type {?EmojiIdentifierResolvable}
344
+ */
345
+ this.emoji = Util.resolvePartialEmoji(data.emoji);
346
+ } else {
347
+ this.emoji = null;
348
+ }
295
349
 
296
- /**
297
- * The labels of the buttons of this rich presence
298
- * @type {string[]}
299
- */
300
- this.buttons = data.buttons ?? [];
350
+ if ('party' in data) {
351
+ /**
352
+ * Represents a party of an activity
353
+ * @typedef {Object} ActivityParty
354
+ * @property {?string} id The party's id
355
+ * @property {number[]} size Size of the party as `[current, max]`
356
+ */
357
+
358
+ /**
359
+ * Party of the activity
360
+ * @type {?ActivityParty}
361
+ */
362
+ this.party = data.party;
363
+ } else {
364
+ this.party = null;
365
+ }
301
366
 
302
367
  /**
303
- * Creation date of the activity
304
- * @type {number}
368
+ * Assets for rich presence
369
+ * @type {?RichPresenceAssets}
305
370
  */
306
- this.createdTimestamp = data.created_at;
371
+ this.assets = new RichPresenceAssets(this, data.assets);
307
372
  }
308
373
 
309
374
  /**
@@ -345,6 +410,13 @@ class Activity {
345
410
  _clone() {
346
411
  return Object.assign(Object.create(this), this);
347
412
  }
413
+
414
+ toJSON(...props) {
415
+ return Util.clearNullOrUndefinedObject({
416
+ ...Util.flatten(this, ...props),
417
+ type: typeof this.type === 'number' ? this.type : ActivityTypes[this.type],
418
+ });
419
+ }
348
420
  }
349
421
 
350
422
  /**
@@ -360,29 +432,49 @@ class RichPresenceAssets {
360
432
  */
361
433
  Object.defineProperty(this, 'activity', { value: activity });
362
434
 
363
- /**
364
- * Hover text for the large image
365
- * @type {?string}
366
- */
367
- this.largeText = assets.large_text ?? null;
435
+ this._patch(assets);
436
+ }
368
437
 
369
- /**
370
- * Hover text for the small image
371
- * @type {?string}
372
- */
373
- this.smallText = assets.small_text ?? null;
438
+ _patch(assets = {}) {
439
+ if ('large_text' in assets || 'largeText' in assets) {
440
+ /**
441
+ * Hover text for the large image
442
+ * @type {?string}
443
+ */
444
+ this.largeText = assets.large_text || assets.largeText;
445
+ } else {
446
+ this.largeText = null;
447
+ }
374
448
 
375
- /**
376
- * The large image asset's id
377
- * @type {?Snowflake}
378
- */
379
- this.largeImage = assets.large_image ?? null;
449
+ if ('small_text' in assets || 'smallText' in assets) {
450
+ /**
451
+ * Hover text for the small image
452
+ * @type {?string}
453
+ */
454
+ this.smallText = assets.small_text || assets.smallText;
455
+ } else {
456
+ this.smallText = null;
457
+ }
380
458
 
381
- /**
382
- * The small image asset's id
383
- * @type {?Snowflake}
384
- */
385
- this.smallImage = assets.small_image ?? null;
459
+ if ('large_image' in assets || 'largeImage' in assets) {
460
+ /**
461
+ * The large image asset's id
462
+ * @type {?Snowflake}
463
+ */
464
+ this.largeImage = assets.large_image || assets.largeImage;
465
+ } else {
466
+ this.largeImage = null;
467
+ }
468
+
469
+ if ('small_image' in assets || 'smallImage' in assets) {
470
+ /**
471
+ * The small image asset's id
472
+ * @type {?Snowflake}
473
+ */
474
+ this.smallImage = assets.small_image || assets.smallImage;
475
+ } else {
476
+ this.smallImage = null;
477
+ }
386
478
  }
387
479
 
388
480
  /**
@@ -397,6 +489,12 @@ class RichPresenceAssets {
397
489
  switch (platform) {
398
490
  case 'mp':
399
491
  return `https://media.discordapp.net/${id}`;
492
+ case 'spotify':
493
+ return `https://i.scdn.co/image/${id}`;
494
+ case 'youtube':
495
+ return `https://i.ytimg.com/vi/${id}/hqdefault_live.jpg`;
496
+ case 'twitch':
497
+ return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${id}.png`;
400
498
  default:
401
499
  return null;
402
500
  }
@@ -436,8 +534,568 @@ class RichPresenceAssets {
436
534
  size,
437
535
  });
438
536
  }
537
+
538
+ static parseImage(image) {
539
+ if (typeof image != 'string') {
540
+ image = null;
541
+ } else if (URL.canParse(image) && ['http:', 'https:'].includes(new URL(image).protocol)) {
542
+ // Discord URL:
543
+ image = image
544
+ .replace('https://cdn.discordapp.com/', 'mp:')
545
+ .replace('http://cdn.discordapp.com/', 'mp:')
546
+ .replace('https://media.discordapp.net/', 'mp:')
547
+ .replace('http://media.discordapp.net/', 'mp:');
548
+ //
549
+ if (!image.startsWith('mp:')) {
550
+ throw new Error('INVALID_URL');
551
+ }
552
+ } else if (/^[0-9]{17,19}$/.test(image)) {
553
+ // ID Assets
554
+ } else if (['mp:', 'youtube:', 'spotify:', 'twitch:'].some(v => image.startsWith(v))) {
555
+ // Image
556
+ } else if (image.startsWith('external/')) {
557
+ image = `mp:${image}`;
558
+ }
559
+ return image;
560
+ }
561
+
562
+ toJSON() {
563
+ if (!this.largeImage && !this.largeText && !this.smallImage && !this.smallText) return null;
564
+ return {
565
+ large_image: RichPresenceAssets.parseImage(this.largeImage),
566
+ large_text: this.largeText,
567
+ small_image: RichPresenceAssets.parseImage(this.smallImage),
568
+ small_text: this.smallText,
569
+ };
570
+ }
571
+
572
+ /**
573
+ * @typedef {string} RichPresenceImage
574
+ * Support:
575
+ * - cdn.discordapp.com
576
+ * - media.discordapp.net
577
+ * - Assets ID (https://discord.com/api/v9/oauth2/applications/{application_id}/assets)
578
+ * - Media Proxy (mp:external/{hash})
579
+ * - Twitch (twitch:{username})
580
+ * - YouTube (youtube:{video_id})
581
+ * - Spotify (spotify:{image_id})
582
+ */
583
+
584
+ /**
585
+ * Set the large image of this activity
586
+ * @param {?RichPresenceImage} image The large image asset's id
587
+ * @returns {RichPresenceAssets}
588
+ */
589
+ setLargeImage(image) {
590
+ image = RichPresenceAssets.parseImage(image);
591
+ this.largeImage = image;
592
+ return this;
593
+ }
594
+
595
+ /**
596
+ * Set the small image of this activity
597
+ * @param {?RichPresenceImage} image The small image asset's id
598
+ * @returns {RichPresenceAssets}
599
+ */
600
+ setSmallImage(image) {
601
+ image = RichPresenceAssets.parseImage(image);
602
+ this.smallImage = image;
603
+ return this;
604
+ }
605
+
606
+ /**
607
+ * Hover text for the large image
608
+ * @param {string} text Assets text
609
+ * @returns {RichPresenceAssets}
610
+ */
611
+ setLargeText(text) {
612
+ this.largeText = text;
613
+ return this;
614
+ }
615
+
616
+ /**
617
+ * Hover text for the small image
618
+ * @param {string} text Assets text
619
+ * @returns {RichPresenceAssets}
620
+ */
621
+ setSmallText(text) {
622
+ this.smallText = text;
623
+ return this;
624
+ }
625
+ }
626
+
627
+ class CustomStatus extends Activity {
628
+ /**
629
+ * @typedef {Object} CustomStatusOptions
630
+ * @property {string} [state] The state to be displayed
631
+ * @property {EmojiIdentifierResolvable} [emoji] The emoji to be displayed
632
+ */
633
+
634
+ /**
635
+ * @param {Client} client Discord Client
636
+ * @param {CustomStatus|CustomStatusOptions} [data={}] CustomStatus to clone or raw data
637
+ */
638
+ constructor(client, data = {}) {
639
+ if (!client) throw new Error("Class constructor CustomStatus cannot be invoked without 'client'");
640
+ super('presence' in client ? client.presence : client, {
641
+ name: 'Custom Status',
642
+ type: ActivityTypes.CUSTOM,
643
+ ...data,
644
+ });
645
+ }
646
+
647
+ /**
648
+ * Set the emoji of this activity
649
+ * @param {EmojiIdentifierResolvable} emoji The emoji to be displayed
650
+ * @returns {CustomStatus}
651
+ */
652
+ setEmoji(emoji) {
653
+ this.emoji = Util.resolvePartialEmoji(emoji);
654
+ return this;
655
+ }
656
+
657
+ /**
658
+ * Set state of this activity
659
+ * @param {string | null} state The state to be displayed
660
+ * @returns {CustomStatus}
661
+ */
662
+ setState(state) {
663
+ if (typeof state == 'string' && state.length > 128) throw new Error('State must be less than 128 characters');
664
+ this.state = state;
665
+ return this;
666
+ }
667
+
668
+ /**
669
+ * Returns an object that can be used to set the status
670
+ * @returns {CustomStatus}
671
+ */
672
+ toJSON() {
673
+ if (!this.emoji & !this.state) throw new Error('CustomStatus must have at least one of emoji or state');
674
+ return {
675
+ name: this.name,
676
+ emoji: this.emoji,
677
+ type: this.type,
678
+ state: this.state,
679
+ };
680
+ }
681
+ }
682
+
683
+ class RichPresence extends Activity {
684
+ /**
685
+ * @param {Client} client Discord client
686
+ * @param {RichPresence} [data={}] RichPresence to clone or raw data
687
+ */
688
+ constructor(client, data = {}) {
689
+ if (!client) throw new Error("Class constructor RichPresence cannot be invoked without 'client'");
690
+ super('presence' in client ? client.presence : client, { type: 0, ...data });
691
+ this.setup(data);
692
+ }
693
+
694
+ /**
695
+ * Sets the status from a JSON object
696
+ * @param {RichPresence} data data
697
+ * @private
698
+ */
699
+ setup(data = {}) {
700
+ this.secrets = 'secrets' in data ? data.secrets : {};
701
+ this.metadata = 'metadata' in data ? data.metadata : {};
702
+ }
703
+
704
+ /**
705
+ * Set the large image of this activity
706
+ * @param {?RichPresenceImage} image The large image asset's id
707
+ * @returns {RichPresence}
708
+ */
709
+ setAssetsLargeImage(image) {
710
+ this.assets.setLargeImage(image);
711
+ return this;
712
+ }
713
+
714
+ /**
715
+ * Set the small image of this activity
716
+ * @param {?RichPresenceImage} image The small image asset's id
717
+ * @returns {RichPresence}
718
+ */
719
+ setAssetsSmallImage(image) {
720
+ this.assets.setSmallImage(image);
721
+ return this;
722
+ }
723
+
724
+ /**
725
+ * Hover text for the large image
726
+ * @param {string} text Assets text
727
+ * @returns {RichPresence}
728
+ */
729
+ setAssetsLargeText(text) {
730
+ this.assets.setLargeText(text);
731
+ return this;
732
+ }
733
+
734
+ /**
735
+ * Hover text for the small image
736
+ * @param {string} text Assets text
737
+ * @returns {RichPresence}
738
+ */
739
+ setAssetsSmallText(text) {
740
+ this.assets.setSmallText(text);
741
+ return this;
742
+ }
743
+
744
+ /**
745
+ * Set the name of the activity
746
+ * @param {?string} name The activity's name
747
+ * @returns {RichPresence}
748
+ */
749
+ setName(name) {
750
+ this.name = name;
751
+ return this;
752
+ }
753
+
754
+ /**
755
+ * If the activity is being streamed, a link to the stream
756
+ * @param {?string} url URL of the stream
757
+ * @returns {RichPresence}
758
+ */
759
+ setURL(url) {
760
+ if (typeof url == 'string' && !URL.canParse(url)) throw new Error('URL must be a valid URL');
761
+ this.url = url;
762
+ return this;
763
+ }
764
+
765
+ /**
766
+ * The activity status's type
767
+ * @param {?ActivityTypes} type The type of activity
768
+ * @returns {RichPresence}
769
+ */
770
+ setType(type) {
771
+ this.type = typeof type == 'number' ? type : ActivityTypes[type];
772
+ return this;
773
+ }
774
+
775
+ /**
776
+ * Set the application id of this activity
777
+ * @param {?Snowflake} id Bot's id
778
+ * @returns {RichPresence}
779
+ */
780
+ setApplicationId(id) {
781
+ this.applicationId = id;
782
+ return this;
783
+ }
784
+
785
+ /**
786
+ * Set the state of the activity
787
+ * @param {?string} state The state of the activity
788
+ * @returns {RichPresence}
789
+ */
790
+ setState(state) {
791
+ this.state = state;
792
+ return this;
793
+ }
794
+
795
+ /**
796
+ * Set the details of the activity
797
+ * @param {?string} details The details of the activity
798
+ * @returns {RichPresence}
799
+ */
800
+ setDetails(details) {
801
+ this.details = details;
802
+ return this;
803
+ }
804
+
805
+ /**
806
+ * @typedef {Object} RichParty
807
+ * @property {string} id The id of the party
808
+ * @property {number} max The maximum number of members in the party
809
+ * @property {number} current The current number of members in the party
810
+ */
811
+
812
+ /**
813
+ * Set the party of this activity
814
+ * @param {?RichParty} party The party to be displayed
815
+ * @returns {RichPresence}
816
+ */
817
+ setParty(party) {
818
+ if (typeof party == 'object') {
819
+ if (!party.max || typeof party.max != 'number') throw new Error('Party must have max number');
820
+ if (!party.current || typeof party.current != 'number') throw new Error('Party must have current');
821
+ if (party.current > party.max) throw new Error('Party current must be less than max number');
822
+ if (!party.id || typeof party.id != 'string') party.id = randomUUID();
823
+ this.party = {
824
+ size: [party.current, party.max],
825
+ id: party.id,
826
+ };
827
+ } else {
828
+ this.party = null;
829
+ }
830
+ return this;
831
+ }
832
+
833
+ /**
834
+ * Sets the start timestamp of the activity
835
+ * @param {Date|number|null} timestamp The timestamp of the start of the activity
836
+ * @returns {RichPresence}
837
+ */
838
+ setStartTimestamp(timestamp) {
839
+ if (!this.timestamps) this.timestamps = {};
840
+ if (timestamp instanceof Date) timestamp = timestamp.getTime();
841
+ this.timestamps.start = timestamp;
842
+ return this;
843
+ }
844
+
845
+ /**
846
+ * Sets the end timestamp of the activity
847
+ * @param {Date|number|null} timestamp The timestamp of the end of the activity
848
+ * @returns {RichPresence}
849
+ */
850
+ setEndTimestamp(timestamp) {
851
+ if (!this.timestamps) this.timestamps = {};
852
+ if (timestamp instanceof Date) timestamp = timestamp.getTime();
853
+ this.timestamps.end = timestamp;
854
+ return this;
855
+ }
856
+
857
+ /**
858
+ * @typedef {object} RichButton
859
+ * @property {string} name The name of the button
860
+ * @property {string} url The url of the button
861
+ */
862
+ /**
863
+ * Set the buttons of the rich presence
864
+ * @param {...?RichButton} button A list of buttons to set
865
+ * @returns {RichPresence}
866
+ */
867
+ setButtons(...button) {
868
+ if (button.length == 0) {
869
+ this.buttons = [];
870
+ delete this.metadata.button_urls;
871
+ return this;
872
+ } else if (button.length > 2) {
873
+ throw new Error('RichPresence can only have up to 2 buttons');
874
+ }
875
+
876
+ this.buttons = [];
877
+ this.metadata.button_urls = [];
878
+
879
+ button.flat(2).forEach(b => {
880
+ if (b.name && b.url) {
881
+ this.buttons.push(b.name);
882
+ if (!URL.canParse(b.url)) throw new Error('Button url must be a valid url');
883
+ this.metadata.button_urls.push(b.url);
884
+ } else {
885
+ throw new Error('Button must have name and url');
886
+ }
887
+ });
888
+ return this;
889
+ }
890
+
891
+ /**
892
+ * The platform the activity is being played on
893
+ * @param {ActivityPlatform | null} platform Any platform
894
+ * @returns {RichPresence}
895
+ */
896
+ setPlatform(platform) {
897
+ this.platform = platform;
898
+ return this;
899
+ }
900
+
901
+ /**
902
+ * Secrets for rich presence joining and spectating (send-only)
903
+ * @param {?string} join Secrets for rich presence joining
904
+ * @returns {RichPresence}
905
+ */
906
+ setJoinSecret(join) {
907
+ this.secrets.join = join;
908
+ return this;
909
+ }
910
+
911
+ /**
912
+ * Add a button to the rich presence
913
+ * @param {string} name The name of the button
914
+ * @param {string} url The url of the button
915
+ * @returns {RichPresence}
916
+ */
917
+ addButton(name, url) {
918
+ if (!name || !url) {
919
+ throw new Error('Button must have name and url');
920
+ }
921
+ if (typeof name !== 'string') throw new Error('Button name must be a string');
922
+ if (!URL.canParse(url)) throw new Error('Button url must be a valid url');
923
+ this.buttons.push(name);
924
+ if (Array.isArray(this.metadata.button_urls)) this.metadata.button_urls.push(url);
925
+ else this.metadata.button_urls = [url];
926
+ return this;
927
+ }
928
+
929
+ /**
930
+ * Convert the rich presence to a JSON object
931
+ * @returns {Object}
932
+ */
933
+ toJSON(...props) {
934
+ return super.toJSON(
935
+ {
936
+ applicationId: 'application_id',
937
+ sessionId: 'session_id',
938
+ syncId: 'sync_id',
939
+ createdTimestamp: 'created_at',
940
+ },
941
+ ...props,
942
+ );
943
+ }
944
+
945
+ /**
946
+ * @typedef {Object} ExternalAssets
947
+ * @property {?string} url Orginal url of the image
948
+ * @property {?string} external_asset_path Proxy url of the image (Using to RPC)
949
+ */
950
+
951
+ /**
952
+ * Get Assets from a RichPresence (Util)
953
+ * @param {Client} client Discord Client
954
+ * @param {Snowflake} applicationId Application id
955
+ * @param {string} image1 URL image 1 (not from Discord)
956
+ * @param {string} image2 URL image 2 (not from Discord)
957
+ * @returns {ExternalAssets[]}
958
+ */
959
+ static async getExternal(client, applicationId, image1 = '', image2 = '') {
960
+ if (!client || !client.token || !client.api) throw new Error('Client must be set');
961
+ // Check if applicationId is discord snowflake (17 , 18, 19 numbers)
962
+ if (!/^[0-9]{17,19}$/.test(applicationId)) {
963
+ throw new Error('Application id must be a Discord Snowflake');
964
+ }
965
+ // Check if large_image is a valid url
966
+ if (image1 && image1.length > 0 && !URL.canParse(image1)) {
967
+ throw new Error('Image 1 must be a valid url');
968
+ }
969
+ // Check if small_image is a valid url
970
+ if (image2 && image2.length > 0 && !URL.canParse(image2)) {
971
+ throw new Error('Image 2 must be a valid url');
972
+ }
973
+ const data_ = [];
974
+ if (image1) data_.push(image1);
975
+ if (image2) data_.push(image2);
976
+ const res = await client.api.applications[applicationId]['external-assets'].post({
977
+ data: {
978
+ urls: data_,
979
+ },
980
+ });
981
+ return res;
982
+ }
983
+
984
+ /**
985
+ * When concatenated with a string, this automatically returns the activities' name instead of the Activity object.
986
+ * @returns {string}
987
+ */
988
+ toString() {
989
+ return this.name;
990
+ }
991
+
992
+ _clone() {
993
+ return Object.assign(Object.create(this), this);
994
+ }
995
+ }
996
+
997
+ /**
998
+ * @extends {RichPresence}
999
+ */
1000
+ class SpotifyRPC extends RichPresence {
1001
+ /**
1002
+ * Create a new RichPresence (Spotify style)
1003
+ * @param {Client} client Discord Client
1004
+ * @param {SpotifyRPC} [options] Options for the Spotify RPC
1005
+ */
1006
+ constructor(client, options = {}) {
1007
+ if (!client) throw new Error("Class constructor SpotifyRPC cannot be invoked without 'client'");
1008
+ super(client, {
1009
+ name: 'Spotify',
1010
+ type: ActivityTypes.LISTENING,
1011
+ party: {
1012
+ id: `spotify:${client.user.id}`,
1013
+ },
1014
+ id: 'spotify:1',
1015
+ flags: 48, // Sync + Play (ActivityFlags)
1016
+ ...options,
1017
+ });
1018
+ this.setup(options);
1019
+ }
1020
+ /**
1021
+ * Sets the status from a JSON object
1022
+ * @param {SpotifyRPC} options data
1023
+ * @private
1024
+ */
1025
+ setup(options) {
1026
+ /**
1027
+ * @typedef {Object} SpotifyMetadata
1028
+ * @property {string} album_id The Spotify ID of the album of the song being played
1029
+ * @property {Array<string>} artist_ids The Spotify IDs of the artists of the song being played
1030
+ * @property {string} context_uri The Spotify URI of the current player context
1031
+ */
1032
+
1033
+ /**
1034
+ * Spotify metadata
1035
+ * @type {SpotifyMetadata}
1036
+ */
1037
+ this.metadata = {
1038
+ album_id: options.metadata?.album_id || null,
1039
+ artist_ids: options.metadata?.artist_ids || [],
1040
+ context_uri: options.metadata?.context_uri || null,
1041
+ };
1042
+ }
1043
+
1044
+ /**
1045
+ * Set Spotify song id to sync with
1046
+ * @param {string} id Song id
1047
+ * @returns {SpotifyRPC}
1048
+ */
1049
+ setSongId(id) {
1050
+ this.syncId = id;
1051
+ return this;
1052
+ }
1053
+
1054
+ /**
1055
+ * Add the artist id
1056
+ * @param {string} id Artist id
1057
+ * @returns {SpotifyRPC}
1058
+ */
1059
+ addArtistId(id) {
1060
+ if (!this.metadata.artist_ids) this.metadata.artist_ids = [];
1061
+ this.metadata.artist_ids.push(id);
1062
+ return this;
1063
+ }
1064
+
1065
+ /**
1066
+ * Set the artist ids
1067
+ * @param {string | Array<string>} ids Artist ids
1068
+ * @returns {SpotifyRPC}
1069
+ */
1070
+ setArtistIds(...ids) {
1071
+ if (!ids?.length) {
1072
+ this.metadata.artist_ids = [];
1073
+ return this;
1074
+ }
1075
+ if (!this.metadata.artist_ids) this.metadata.artist_ids = [];
1076
+ ids.flat(2).forEach(id => this.metadata.artist_ids.push(id));
1077
+ return this;
1078
+ }
1079
+
1080
+ /**
1081
+ * Set the album id
1082
+ * @param {string} id Album id
1083
+ * @returns {SpotifyRPC}
1084
+ */
1085
+ setAlbumId(id) {
1086
+ this.metadata.album_id = id;
1087
+ this.metadata.context_uri = `spotify:album:${id}`;
1088
+ return this;
1089
+ }
1090
+
1091
+ toJSON() {
1092
+ return super.toJSON({ id: false, emoji: false, platform: false, buttons: false });
1093
+ }
439
1094
  }
440
1095
 
441
1096
  exports.Presence = Presence;
442
1097
  exports.Activity = Activity;
443
1098
  exports.RichPresenceAssets = RichPresenceAssets;
1099
+ exports.CustomStatus = CustomStatus;
1100
+ exports.RichPresence = RichPresence;
1101
+ exports.SpotifyRPC = SpotifyRPC;