djs-selfbot-v13 3.1.8 → 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.
- package/LICENSE +1 -1
- package/README.md +18 -8
- package/package.json +85 -71
- package/src/client/BaseClient.js +1 -1
- package/src/client/Client.js +81 -10
- package/src/client/actions/GuildMemberRemove.js +0 -1
- package/src/client/actions/GuildMemberUpdate.js +0 -1
- package/src/client/websocket/WebSocketShard.js +3 -3
- package/src/client/websocket/handlers/GUILD_CREATE.js +13 -14
- package/src/client/websocket/handlers/GUILD_MEMBER_ADD.js +0 -1
- package/src/client/websocket/handlers/MESSAGE_POLL_VOTE_ADD.js +22 -0
- package/src/client/websocket/handlers/MESSAGE_POLL_VOTE_REMOVE.js +12 -0
- package/src/client/websocket/handlers/READY.js +61 -21
- package/src/client/websocket/handlers/RELATIONSHIP_REMOVE.js +1 -1
- package/src/client/websocket/handlers/RELATIONSHIP_UPDATE.js +1 -1
- package/src/client/websocket/handlers/index.js +2 -0
- package/src/errors/Messages.js +1 -0
- package/src/index.js +4 -3
- package/src/managers/ChannelManager.js +1 -1
- package/src/managers/ClientUserSettingManager.js +2 -2
- package/src/managers/GuildBanManager.js +46 -0
- package/src/managers/GuildChannelManager.js +0 -16
- package/src/managers/GuildForumThreadManager.js +3 -3
- package/src/managers/GuildManager.js +1 -1
- package/src/managers/GuildMemberManager.js +11 -7
- package/src/managers/RelationshipManager.js +3 -3
- package/src/rest/APIRequest.js +13 -7
- package/src/structures/ClientPresence.js +9 -12
- package/src/structures/ClientUser.js +0 -2
- package/src/structures/Message.js +67 -76
- package/src/structures/MessagePayload.js +2 -0
- package/src/structures/MessagePoll.js +238 -0
- package/src/structures/Modal.js +11 -24
- package/src/structures/Presence.js +786 -128
- package/src/structures/User.js +35 -1
- package/src/structures/interfaces/TextBasedChannel.js +21 -23
- package/src/util/Constants.js +17 -4
- package/src/util/Options.js +1 -7
- package/src/util/Permissions.js +10 -0
- package/src/util/Util.js +88 -2
- package/typings/enums.d.ts +7 -1
- package/typings/index.d.ts +112 -70
- package/src/structures/RichPresence.js +0 -702
|
@@ -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
|
|
79
|
+
_patch(data) {
|
|
81
80
|
if ('status' in data) {
|
|
82
81
|
/**
|
|
83
82
|
* The status of this presence
|
|
@@ -91,16 +90,16 @@ class Presence extends Base {
|
|
|
91
90
|
if ('activities' in data) {
|
|
92
91
|
/**
|
|
93
92
|
* The activities of this presence (Always `Activity[]` if not ClientUser)
|
|
94
|
-
* @type {
|
|
93
|
+
* @type {CustomStatus[]|RichPresence[]|SpotifyRPC[]|Activity[]}
|
|
95
94
|
*/
|
|
96
95
|
this.activities = data.activities.map(activity => {
|
|
97
|
-
if (
|
|
96
|
+
if (this.userId == this.client.user.id) {
|
|
98
97
|
if ([ActivityTypes.CUSTOM, 'CUSTOM'].includes(activity.type)) {
|
|
99
|
-
return new CustomStatus(
|
|
98
|
+
return new CustomStatus(this.client, activity);
|
|
100
99
|
} else if (activity.id == 'spotify:1') {
|
|
101
|
-
return new SpotifyRPC(this.client, activity
|
|
100
|
+
return new SpotifyRPC(this.client, activity);
|
|
102
101
|
} else {
|
|
103
|
-
return new RichPresence(this.client, activity
|
|
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
|
-
|
|
189
|
-
* @type {string}
|
|
190
|
-
*/
|
|
191
|
-
this.id = data.id;
|
|
194
|
+
this._patch(data);
|
|
195
|
+
}
|
|
192
196
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
*
|
|
304
|
-
* @type {
|
|
368
|
+
* Assets for rich presence
|
|
369
|
+
* @type {?RichPresenceAssets}
|
|
305
370
|
*/
|
|
306
|
-
this.
|
|
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
|
-
|
|
365
|
-
* @type {?string}
|
|
366
|
-
*/
|
|
367
|
-
this.largeText = assets.large_text ?? null;
|
|
435
|
+
this._patch(assets);
|
|
436
|
+
}
|
|
368
437
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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;
|