djs-selfbot-v13 3.7.1 → 3.7.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/README.md +28 -2
- package/package.json +1 -1
- package/src/client/Client.js +7 -0
- package/src/managers/QuestManager.js +376 -0
- package/src/structures/ClientUser.js +178 -4
- package/src/structures/Guild.js +54 -0
- package/src/structures/Presence.js +32 -0
- package/typings/index.d.ts +145 -0
package/README.md
CHANGED
|
@@ -14,6 +14,32 @@
|
|
|
14
14
|
|
|
15
15
|
- discord.js-selfbot-v13 is a [Node.js](https://nodejs.org) module that allows user accounts to interact with the Discord API v9.
|
|
16
16
|
|
|
17
|
+
## Nouveautés
|
|
18
|
+
|
|
19
|
+
> **Nouvelles fonctionnalités ajoutées :**
|
|
20
|
+
- `guild.mute()` - Supprime completement les pings
|
|
21
|
+
- `guild.unmute()` - Remet les pings sur le serveur
|
|
22
|
+
|
|
23
|
+
- `rpc.setDetailsURL(url)` - Met une URL pour les details du RPC
|
|
24
|
+
- `rpc.setStateURL(url)` - Met une URL pour le state du RPC
|
|
25
|
+
|
|
26
|
+
- `client.user.setNameStyle(fontName, effectName, color1, color2?)` - Définir le style du nom d'affichage avec police, effet et couleurs
|
|
27
|
+
- `client.user.setClan(GuildID)` - Changer votre TAG de serveur
|
|
28
|
+
- `client.user.deleteClan()` - Retire le TAG de serveur
|
|
29
|
+
|
|
30
|
+
- `client.user.addWidget(type, gameId, comment?, tags?)` - Ajouter un widget de jeu au profil
|
|
31
|
+
- `client.user.delWidget(type, gameId?)` - Supprimer un widget ou un jeu spécifique
|
|
32
|
+
- `client.user.widgetsList()` - Obtenir la liste de tous les widgets
|
|
33
|
+
|
|
34
|
+
- `client.quests.get()` - Récupérer toutes les quêtes disponibles
|
|
35
|
+
- `client.quests.orbs()` - Obtenir le solde de la monnaie virtuelle
|
|
36
|
+
- `client.quests.acceptQuest(questId, options?)` - S'inscrire à une quête
|
|
37
|
+
- `client.quests.doingQuest(quest)` - Compléter automatiquement une quête
|
|
38
|
+
- `client.quests.autoCompleteAll()` - Compléter automatiquement toutes les quêtes valides
|
|
39
|
+
- `client.quests.getCompleted()` - Obtenir les quêtes terminées
|
|
40
|
+
- `client.quests.getClaimable()` - Obtenir les quêtes réclamables
|
|
41
|
+
- `client.quests.filterQuestsValid()` - Filtrer les quêtes valides
|
|
42
|
+
|
|
17
43
|
|
|
18
44
|
<div align="center">
|
|
19
45
|
<p>
|
|
@@ -49,13 +75,13 @@
|
|
|
49
75
|
> **Node.js 20.18.0 or newer is required**
|
|
50
76
|
|
|
51
77
|
```sh-session
|
|
52
|
-
npm install
|
|
78
|
+
npm install djs-selfbot-v13@latest
|
|
53
79
|
```
|
|
54
80
|
|
|
55
81
|
## Example
|
|
56
82
|
|
|
57
83
|
```js
|
|
58
|
-
const { Client } = require('
|
|
84
|
+
const { Client } = require('djs-selfbot-v13');
|
|
59
85
|
const client = new Client();
|
|
60
86
|
|
|
61
87
|
client.on('ready', async () => {
|
package/package.json
CHANGED
package/src/client/Client.js
CHANGED
|
@@ -17,6 +17,7 @@ const ChannelManager = require('../managers/ChannelManager');
|
|
|
17
17
|
const ClientUserSettingManager = require('../managers/ClientUserSettingManager');
|
|
18
18
|
const GuildManager = require('../managers/GuildManager');
|
|
19
19
|
const PresenceManager = require('../managers/PresenceManager');
|
|
20
|
+
const QuestManager = require('../managers/QuestManager');
|
|
20
21
|
const RelationshipManager = require('../managers/RelationshipManager');
|
|
21
22
|
const SessionManager = require('../managers/SessionManager');
|
|
22
23
|
const UserManager = require('../managers/UserManager');
|
|
@@ -158,6 +159,12 @@ class Client extends BaseClient {
|
|
|
158
159
|
*/
|
|
159
160
|
this.billing = new BillingManager(this);
|
|
160
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Manages quest-related API methods
|
|
164
|
+
* @type {QuestManager}
|
|
165
|
+
*/
|
|
166
|
+
this.quests = new QuestManager(this);
|
|
167
|
+
|
|
161
168
|
/**
|
|
162
169
|
* All of the sessions of the client
|
|
163
170
|
* @type {SessionManager}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Collection } = require('@discordjs/collection');
|
|
4
|
+
const BaseManager = require('./BaseManager');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents a single quest
|
|
8
|
+
*/
|
|
9
|
+
class Quest {
|
|
10
|
+
constructor(data) {
|
|
11
|
+
this.id = data.id;
|
|
12
|
+
this.config = data.config;
|
|
13
|
+
this.userStatus = data.user_status;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if quest is expired
|
|
18
|
+
* @param {Date} [date=new Date()] Date to check against
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
isExpired(date = new Date()) {
|
|
22
|
+
if (!this.config.expires_at) return false;
|
|
23
|
+
return new Date(this.config.expires_at) < date;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if quest is completed
|
|
28
|
+
* @returns {boolean}
|
|
29
|
+
*/
|
|
30
|
+
isCompleted() {
|
|
31
|
+
return this.userStatus?.completed_at != null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if quest rewards have been claimed
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
hasClaimedRewards() {
|
|
39
|
+
return this.userStatus?.claimed_at != null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if user is enrolled in quest
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
isEnrolledQuest() {
|
|
47
|
+
return this.userStatus?.enrolled_at != null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Update user status for this quest
|
|
52
|
+
* @param {Object} status New status data
|
|
53
|
+
*/
|
|
54
|
+
updateUserStatus(status) {
|
|
55
|
+
this.userStatus = { ...this.userStatus, ...status };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Manages API methods for Discord quests
|
|
61
|
+
* @extends {BaseManager}
|
|
62
|
+
*/
|
|
63
|
+
class QuestManager extends BaseManager {
|
|
64
|
+
constructor(client) {
|
|
65
|
+
super(client);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Collection of cached quests
|
|
69
|
+
* @type {Collection<string, Quest>}
|
|
70
|
+
*/
|
|
71
|
+
this.cache = new Collection();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get all available quests for the user
|
|
76
|
+
* @returns {Promise<Object>} Quest data
|
|
77
|
+
*/
|
|
78
|
+
async get() {
|
|
79
|
+
const data = await this.client.api.users('@me').quests.get();
|
|
80
|
+
|
|
81
|
+
// Cache quests
|
|
82
|
+
if (data.quests) {
|
|
83
|
+
this.cache.clear();
|
|
84
|
+
data.quests.forEach(questData => {
|
|
85
|
+
const quest = new Quest(questData);
|
|
86
|
+
this.cache.set(quest.id, quest);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return data;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get user's orb balance (virtual currency)
|
|
95
|
+
* @returns {Promise<Object>} Balance data
|
|
96
|
+
*/
|
|
97
|
+
async orbs() {
|
|
98
|
+
const data = await this.client.api.users['@me']['virtual-currency'].balance.get();
|
|
99
|
+
return data;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get quest by ID from cache
|
|
104
|
+
* @param {string} id Quest ID
|
|
105
|
+
* @returns {Quest|undefined}
|
|
106
|
+
*/
|
|
107
|
+
getQuest(id) {
|
|
108
|
+
return this.cache.get(id);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get all cached quests as array
|
|
113
|
+
* @returns {Quest[]}
|
|
114
|
+
*/
|
|
115
|
+
list() {
|
|
116
|
+
return Array.from(this.cache.values());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get expired quests
|
|
121
|
+
* @param {Date} [date=new Date()] Date to check against
|
|
122
|
+
* @returns {Quest[]}
|
|
123
|
+
*/
|
|
124
|
+
getExpired(date = new Date()) {
|
|
125
|
+
return this.list().filter(quest => quest.isExpired(date));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get completed quests
|
|
130
|
+
* @returns {Quest[]}
|
|
131
|
+
*/
|
|
132
|
+
getCompleted() {
|
|
133
|
+
return this.list().filter(quest => quest.isCompleted());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get claimable quests (completed but not claimed)
|
|
138
|
+
* @returns {Quest[]}
|
|
139
|
+
*/
|
|
140
|
+
getClaimable() {
|
|
141
|
+
return this.list().filter(quest => quest.isCompleted() && !quest.hasClaimedRewards());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get valid quests (not completed, not expired, not blacklisted)
|
|
146
|
+
* @returns {Quest[]}
|
|
147
|
+
*/
|
|
148
|
+
filterQuestsValid() {
|
|
149
|
+
return this.list().filter(quest =>
|
|
150
|
+
quest.id !== '1412491570820812933' &&
|
|
151
|
+
!quest.isCompleted() &&
|
|
152
|
+
!quest.isExpired()
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Check if quest exists in cache
|
|
158
|
+
* @param {string} id Quest ID
|
|
159
|
+
* @returns {boolean}
|
|
160
|
+
*/
|
|
161
|
+
hasQuest(id) {
|
|
162
|
+
return this.cache.has(id);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get application data for given IDs
|
|
167
|
+
* @param {string[]} ids Application IDs
|
|
168
|
+
* @returns {Promise<Object[]>}
|
|
169
|
+
*/
|
|
170
|
+
async getApplicationData(ids) {
|
|
171
|
+
const query = new URLSearchParams();
|
|
172
|
+
ids.forEach(id => query.append('application_ids', id));
|
|
173
|
+
|
|
174
|
+
return this.client.api.applications.public.get({ query: query.toString() });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Enroll in a specific quest
|
|
179
|
+
* @param {string} questId The quest ID to enroll in
|
|
180
|
+
* @param {Object} [options] Enrollment options
|
|
181
|
+
* @param {number} [options.location=11] Location parameter
|
|
182
|
+
* @param {boolean} [options.isTargeted=false] Whether the quest is targeted
|
|
183
|
+
* @param {*} [options.metadataRaw=null] Raw metadata
|
|
184
|
+
* @returns {Promise<Quest|undefined>} Updated quest or undefined
|
|
185
|
+
*/
|
|
186
|
+
async acceptQuest(questId, options = {}) {
|
|
187
|
+
const { location = 11, isTargeted = false, metadataRaw = null } = options;
|
|
188
|
+
|
|
189
|
+
const data = await this.client.api.quests(questId).enroll.post({
|
|
190
|
+
data: {
|
|
191
|
+
location,
|
|
192
|
+
is_targeted: isTargeted,
|
|
193
|
+
metadata_raw: metadataRaw
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const quest = this.getQuest(questId);
|
|
198
|
+
if (quest) {
|
|
199
|
+
quest.updateUserStatus(data);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return quest;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Update progress for a video quest
|
|
207
|
+
* @param {string} questId The quest ID
|
|
208
|
+
* @param {number} timestamp Current progress timestamp
|
|
209
|
+
* @returns {Promise<Object>} Progress update result
|
|
210
|
+
*/
|
|
211
|
+
async videoProgress(questId, timestamp) {
|
|
212
|
+
const data = await this.client.api.quests(questId)['video-progress'].post({
|
|
213
|
+
data: { timestamp }
|
|
214
|
+
});
|
|
215
|
+
return data;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Send heartbeat for desktop quests
|
|
220
|
+
* @param {string} questId The quest ID
|
|
221
|
+
* @param {string} applicationId Application ID
|
|
222
|
+
* @param {boolean} [terminal=false] Whether this is a terminal heartbeat
|
|
223
|
+
* @returns {Promise<Object>} Heartbeat result
|
|
224
|
+
*/
|
|
225
|
+
async heartbeat(questId, applicationId, terminal = false) {
|
|
226
|
+
const data = await this.client.api.quests(questId).heartbeat.post({
|
|
227
|
+
data: {
|
|
228
|
+
application_id: applicationId,
|
|
229
|
+
terminal
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return data;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Helper function for timeout
|
|
237
|
+
* @param {number} ms Milliseconds to wait
|
|
238
|
+
* @returns {Promise<void>}
|
|
239
|
+
* @private
|
|
240
|
+
*/
|
|
241
|
+
async timeout(ms) {
|
|
242
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Complete a quest automatically
|
|
247
|
+
* @param {Quest} quest Quest to complete
|
|
248
|
+
* @returns {Promise<void>}
|
|
249
|
+
*/
|
|
250
|
+
async doingQuest(quest) {
|
|
251
|
+
const questName = quest.config.messages?.quest_name || 'Unknown Quest';
|
|
252
|
+
|
|
253
|
+
if (!quest.isEnrolledQuest()) {
|
|
254
|
+
console.log(`Enrolling in quest "${questName}"...`);
|
|
255
|
+
await this.acceptQuest(quest.id);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const applicationName = quest.config.application?.name || 'Unknown App';
|
|
259
|
+
const taskConfig = quest.config.task_config;
|
|
260
|
+
|
|
261
|
+
const taskName = [
|
|
262
|
+
'WATCH_VIDEO',
|
|
263
|
+
'PLAY_ON_DESKTOP',
|
|
264
|
+
'STREAM_ON_DESKTOP',
|
|
265
|
+
'PLAY_ACTIVITY',
|
|
266
|
+
'WATCH_VIDEO_ON_MOBILE'
|
|
267
|
+
].find(x => taskConfig.tasks?.[x] != null);
|
|
268
|
+
|
|
269
|
+
if (!taskName) {
|
|
270
|
+
console.log(`Unknown task type for quest "${questName}"`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const secondsNeeded = taskConfig.tasks[taskName].target;
|
|
275
|
+
let secondsDone = quest.userStatus?.progress?.[taskName]?.value ?? 0;
|
|
276
|
+
|
|
277
|
+
if (taskName === 'WATCH_VIDEO' || taskName === 'WATCH_VIDEO_ON_MOBILE') {
|
|
278
|
+
const maxFuture = 10;
|
|
279
|
+
const speed = 7;
|
|
280
|
+
const interval = 1;
|
|
281
|
+
const enrolledAt = new Date(quest.userStatus?.enrolled_at).getTime();
|
|
282
|
+
let completed = false;
|
|
283
|
+
|
|
284
|
+
console.log(`Spoofing video for ${questName}.`);
|
|
285
|
+
|
|
286
|
+
while (true) {
|
|
287
|
+
const maxAllowed = Math.floor((Date.now() - enrolledAt) / 1000) + maxFuture;
|
|
288
|
+
const diff = maxAllowed - secondsDone;
|
|
289
|
+
const timestamp = secondsDone + speed;
|
|
290
|
+
|
|
291
|
+
if (diff >= speed) {
|
|
292
|
+
const res = await this.videoProgress(quest.id, Math.min(secondsNeeded, timestamp + Math.random()));
|
|
293
|
+
completed = res.completed_at != null;
|
|
294
|
+
secondsDone = Math.min(secondsNeeded, timestamp);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (timestamp >= secondsNeeded) {
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
await this.timeout(interval * 1000);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!completed) {
|
|
305
|
+
await this.videoProgress(quest.id, secondsNeeded);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
console.log(`Quest "${questName}" completed!`);
|
|
309
|
+
|
|
310
|
+
} else if (taskName === 'PLAY_ON_DESKTOP') {
|
|
311
|
+
const interval = 60;
|
|
312
|
+
|
|
313
|
+
while (!quest.isCompleted()) {
|
|
314
|
+
const secondsDone = quest.userStatus?.progress?.[taskName]?.value || 0;
|
|
315
|
+
const res = await this.heartbeat(quest.id, quest.config.application.id, false);
|
|
316
|
+
quest.updateUserStatus(res);
|
|
317
|
+
|
|
318
|
+
console.log(`Spoofed your game to ${applicationName}. Wait for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`);
|
|
319
|
+
await this.timeout(interval * 1000);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const res = await this.heartbeat(quest.id, quest.config.application.id, true);
|
|
323
|
+
quest.updateUserStatus(res);
|
|
324
|
+
console.log(`Quest "${questName}" completed!`);
|
|
325
|
+
|
|
326
|
+
} else if (taskName === 'STREAM_ON_DESKTOP') {
|
|
327
|
+
console.log('This no longer works in node for non-video quests. Use the discord desktop app to complete the', questName, 'quest!');
|
|
328
|
+
} else if (taskName === 'PLAY_ACTIVITY') {
|
|
329
|
+
console.log('This quest not supported. Use the discord desktop app to complete the', questName, 'quest!');
|
|
330
|
+
} else {
|
|
331
|
+
console.log('Unknown quest type. Use the discord desktop app to complete the', questName, 'quest!');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Auto-complete all valid quests
|
|
337
|
+
* @returns {Promise<void>}
|
|
338
|
+
*/
|
|
339
|
+
async autoCompleteAll() {
|
|
340
|
+
await this.get(); // Refresh quest data
|
|
341
|
+
const validQuests = this.filterQuestsValid();
|
|
342
|
+
|
|
343
|
+
for (const quest of validQuests) {
|
|
344
|
+
try {
|
|
345
|
+
await this.doingQuest(quest);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error(`Failed to complete quest ${quest.id}:`, error);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get cache size
|
|
354
|
+
* @returns {number}
|
|
355
|
+
*/
|
|
356
|
+
get size() {
|
|
357
|
+
return this.cache.size;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Clear quest cache
|
|
362
|
+
*/
|
|
363
|
+
clear() {
|
|
364
|
+
this.cache.clear();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Make QuestManager iterable
|
|
369
|
+
* @returns {IterableIterator<Quest>}
|
|
370
|
+
*/
|
|
371
|
+
[Symbol.iterator]() {
|
|
372
|
+
return this.cache.values();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
module.exports = QuestManager;
|
|
@@ -447,12 +447,186 @@ class ClientUser extends User {
|
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
/**
|
|
450
|
-
*
|
|
451
|
-
* @param {
|
|
450
|
+
* Add a widget to the user's profile
|
|
451
|
+
* @param {string} type Widget type (favorite_games, current_games, played_games, want_to_play_games)
|
|
452
|
+
* @param {string} gameId The game ID to add
|
|
453
|
+
* @param {string} [comment] Optional comment for the game
|
|
454
|
+
* @param {string[]} [tags] Optional tags for the game
|
|
455
|
+
* @returns {Promise<Object>}
|
|
456
|
+
*/
|
|
457
|
+
async addWidget(type, gameId, comment = null, tags = []) {
|
|
458
|
+
if (!type || !gameId) {
|
|
459
|
+
throw new TypeError('Widget type and game ID are required');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const validTypes = ['favorite_games', 'current_games', 'played_games', 'want_to_play_games'];
|
|
463
|
+
if (!validTypes.includes(type)) {
|
|
464
|
+
throw new TypeError(`Invalid widget type. Must be one of: ${validTypes.join(', ')}`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Get current widgets first
|
|
468
|
+
const currentWidgets = await this.widgetsList();
|
|
469
|
+
|
|
470
|
+
// Find existing widget of this type or create new one
|
|
471
|
+
let targetWidget = currentWidgets.widgets.find(w => w.data.type === type);
|
|
472
|
+
|
|
473
|
+
if (!targetWidget) {
|
|
474
|
+
// Create new widget if it doesn't exist
|
|
475
|
+
targetWidget = {
|
|
476
|
+
id: Date.now().toString(), // Generate temporary ID
|
|
477
|
+
data: {
|
|
478
|
+
type: type,
|
|
479
|
+
games: []
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
currentWidgets.widgets.push(targetWidget);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Add the game if it doesn't already exist
|
|
486
|
+
const existingGame = targetWidget.data.games.find(g => g.game_id === gameId);
|
|
487
|
+
if (!existingGame) {
|
|
488
|
+
const gameData = { game_id: gameId };
|
|
489
|
+
if (comment !== null) gameData.comment = comment;
|
|
490
|
+
if (tags.length > 0) gameData.tags = tags;
|
|
491
|
+
|
|
492
|
+
targetWidget.data.games.push(gameData);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Update widgets via API
|
|
496
|
+
return this.client.api.users['@me'].profile.patch({
|
|
497
|
+
data: { widgets: currentWidgets.widgets }
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Delete a widget or remove a game from a widget
|
|
503
|
+
* @param {string} type Widget type to modify
|
|
504
|
+
* @param {string} [gameId] Optional game ID to remove (if not provided, removes entire widget)
|
|
505
|
+
* @returns {Promise<Object>}
|
|
506
|
+
*/
|
|
507
|
+
async delWidget(type, gameId = null) {
|
|
508
|
+
if (!type) {
|
|
509
|
+
throw new TypeError('Widget type is required');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const validTypes = ['favorite_games', 'current_games', 'played_games', 'want_to_play_games'];
|
|
513
|
+
if (!validTypes.includes(type)) {
|
|
514
|
+
throw new TypeError(`Invalid widget type. Must be one of: ${validTypes.join(', ')}`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Get current widgets
|
|
518
|
+
const currentWidgets = await this.widgetsList();
|
|
519
|
+
|
|
520
|
+
if (gameId) {
|
|
521
|
+
// Remove specific game from widget
|
|
522
|
+
const targetWidget = currentWidgets.widgets.find(w => w.data.type === type);
|
|
523
|
+
if (targetWidget) {
|
|
524
|
+
targetWidget.data.games = targetWidget.data.games.filter(g => g.game_id !== gameId);
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
// Remove entire widget
|
|
528
|
+
currentWidgets.widgets = currentWidgets.widgets.filter(w => w.data.type !== type);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Update widgets via API
|
|
532
|
+
return this.client.api.users['@me'].profile.patch({
|
|
533
|
+
data: { widgets: currentWidgets.widgets }
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get the list of all widgets for the user
|
|
539
|
+
* @returns {Promise<Object>} Object containing widgets array
|
|
540
|
+
*/
|
|
541
|
+
async widgetsList() {
|
|
542
|
+
try {
|
|
543
|
+
const data = await this.client.api.users['@me'].profile.get();
|
|
544
|
+
return data.widgets ? { widgets: data.widgets } : { widgets: [] };
|
|
545
|
+
} catch (error) {
|
|
546
|
+
// If profile endpoint doesn't exist or fails, return empty widgets
|
|
547
|
+
return { widgets: [] };
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Set display name style with font, effect, and colors
|
|
553
|
+
* @param {string|number} fontName Font name or ID
|
|
554
|
+
* @param {string|number} effectName Effect name or ID
|
|
555
|
+
* @param {number|string} color1 Primary color (hex or decimal)
|
|
556
|
+
* @param {number|string} [color2] Secondary color for gradient effects (hex or decimal)
|
|
452
557
|
* @returns {Promise<ClientUser>}
|
|
558
|
+
* @example
|
|
559
|
+
* // Set Sans font with gradient effect
|
|
560
|
+
* client.user.setNameStyle('Sans', 'Gradient', 7183099, 6082490);
|
|
561
|
+
* // Set Tempo font with solid effect
|
|
562
|
+
* client.user.setNameStyle('Tempo', 'Solid', 7183099);
|
|
563
|
+
* // Using IDs directly
|
|
564
|
+
* client.user.setNameStyle(11, 2, 7183099, 6082490);
|
|
453
565
|
*/
|
|
454
|
-
|
|
455
|
-
|
|
566
|
+
async setNameStyle(fontName, effectName, color1, color2 = null) {
|
|
567
|
+
// Font name/ID mapping
|
|
568
|
+
const fontMap = {
|
|
569
|
+
'Sans': 11,
|
|
570
|
+
'Tempo': 12,
|
|
571
|
+
'Sakura': 3,
|
|
572
|
+
'JellyBean': 4,
|
|
573
|
+
'Modern': 6,
|
|
574
|
+
'Medieval': 7,
|
|
575
|
+
'8Bit': 8,
|
|
576
|
+
'Vampire': 10
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// Effect name/ID mapping
|
|
580
|
+
const effectMap = {
|
|
581
|
+
'Solid': 1,
|
|
582
|
+
'Gradient': 2,
|
|
583
|
+
'Neon': 3,
|
|
584
|
+
'Toon': 4,
|
|
585
|
+
'Pop': 5
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
// Resolve font ID
|
|
589
|
+
let fontId = typeof fontName === 'string' ? fontMap[fontName] : fontName;
|
|
590
|
+
if (!fontId) {
|
|
591
|
+
throw new TypeError(`Invalid font name. Must be one of: ${Object.keys(fontMap).join(', ')} or a valid font ID`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Resolve effect ID
|
|
595
|
+
let effectId = typeof effectName === 'string' ? effectMap[effectName] : effectName;
|
|
596
|
+
if (!effectId) {
|
|
597
|
+
throw new TypeError(`Invalid effect name. Must be one of: ${Object.keys(effectMap).join(', ')} or a valid effect ID`);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Resolve colors
|
|
601
|
+
const resolveColor = (color) => {
|
|
602
|
+
if (typeof color === 'string') {
|
|
603
|
+
// Handle hex colors
|
|
604
|
+
if (color.startsWith('#')) {
|
|
605
|
+
return parseInt(color.slice(1), 16);
|
|
606
|
+
}
|
|
607
|
+
return parseInt(color, 16);
|
|
608
|
+
}
|
|
609
|
+
return color;
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const primaryColor = resolveColor(color1);
|
|
613
|
+
const colors = [primaryColor];
|
|
614
|
+
|
|
615
|
+
if (color2 !== null) {
|
|
616
|
+
const secondaryColor = resolveColor(color2);
|
|
617
|
+
colors.push(secondaryColor);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Build the data object
|
|
621
|
+
const data = {
|
|
622
|
+
display_name_font_id: fontId,
|
|
623
|
+
display_name_effect_id: effectId,
|
|
624
|
+
display_name_colors: colors
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
// Send PATCH request to Discord API
|
|
628
|
+
await this.client.api.users('@me').patch({ data });
|
|
629
|
+
return this;
|
|
456
630
|
}
|
|
457
631
|
|
|
458
632
|
/**
|
package/src/structures/Guild.js
CHANGED
|
@@ -1605,6 +1605,60 @@ class Guild extends AnonymousGuild {
|
|
|
1605
1605
|
};
|
|
1606
1606
|
}
|
|
1607
1607
|
|
|
1608
|
+
/**
|
|
1609
|
+
* Mute this guild
|
|
1610
|
+
* @param {GuildMuteOptions} [options] Options for muting the guild
|
|
1611
|
+
* @returns {Promise<Object>} The updated guild settings
|
|
1612
|
+
* @example
|
|
1613
|
+
* // Mute the guild with default settings
|
|
1614
|
+
* guild.mute();
|
|
1615
|
+
* @example
|
|
1616
|
+
* // Mute the guild with custom options
|
|
1617
|
+
* guild.mute({
|
|
1618
|
+
* muted: true,
|
|
1619
|
+
* suppressRoles: false,
|
|
1620
|
+
* suppressEveryone: true,
|
|
1621
|
+
* muteScheduledEvents: false
|
|
1622
|
+
* });
|
|
1623
|
+
*/
|
|
1624
|
+
async mute(options = {}) {
|
|
1625
|
+
const {
|
|
1626
|
+
muted = true,
|
|
1627
|
+
suppressRoles = true,
|
|
1628
|
+
suppressEveryone = true,
|
|
1629
|
+
muteScheduledEvents = true
|
|
1630
|
+
} = options;
|
|
1631
|
+
|
|
1632
|
+
const data = await this.client.api.users('@me').guilds(this.id).settings.patch({
|
|
1633
|
+
data: {
|
|
1634
|
+
muted,
|
|
1635
|
+
suppress_roles: suppressRoles,
|
|
1636
|
+
suppress_everyone: suppressEveryone,
|
|
1637
|
+
mute_scheduled_events: muteScheduledEvents
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
return data;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/**
|
|
1644
|
+
* Unmute this guild
|
|
1645
|
+
* @returns {Promise<Object>} The updated guild settings
|
|
1646
|
+
* @example
|
|
1647
|
+
* // Unmute the guild
|
|
1648
|
+
* guild.unmute();
|
|
1649
|
+
*/
|
|
1650
|
+
async unmute() {
|
|
1651
|
+
const data = await this.client.api.users('@me').guilds(this.id).settings.patch({
|
|
1652
|
+
data: {
|
|
1653
|
+
muted: false,
|
|
1654
|
+
suppress_roles: false,
|
|
1655
|
+
suppress_everyone: false,
|
|
1656
|
+
mute_scheduled_events: false
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1659
|
+
return data;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1608
1662
|
/**
|
|
1609
1663
|
* Creates a collection of this guild's roles, sorted by their position and ids.
|
|
1610
1664
|
* @returns {Collection<Snowflake, Role>}
|
|
@@ -800,6 +800,22 @@ class RichPresence extends Activity {
|
|
|
800
800
|
return this;
|
|
801
801
|
}
|
|
802
802
|
|
|
803
|
+
/**
|
|
804
|
+
* Set the URL of the state of the activity
|
|
805
|
+
* @param {?string} url The url of the state
|
|
806
|
+
* @returns {RichPresence}
|
|
807
|
+
*/
|
|
808
|
+
setStateURL(url){
|
|
809
|
+
if (!url)
|
|
810
|
+
throw new Error('Detail URL must be a url');
|
|
811
|
+
|
|
812
|
+
if (typeof url !== 'string') throw new Error('Detail URL must be a url');
|
|
813
|
+
if (!URL.canParse(url)) throw new Error('Detail URL must be a valid url');
|
|
814
|
+
|
|
815
|
+
this.state_url = url;
|
|
816
|
+
return this;
|
|
817
|
+
}
|
|
818
|
+
|
|
803
819
|
/**
|
|
804
820
|
* Set the details of the activity
|
|
805
821
|
* @param {?string} details The details of the activity
|
|
@@ -810,6 +826,22 @@ class RichPresence extends Activity {
|
|
|
810
826
|
return this;
|
|
811
827
|
}
|
|
812
828
|
|
|
829
|
+
/**
|
|
830
|
+
* Set the URL of the details of the activity
|
|
831
|
+
* @param {?string} url The url of the details
|
|
832
|
+
* @returns {RichPresence}
|
|
833
|
+
*/
|
|
834
|
+
setDetailsURL(url){
|
|
835
|
+
if (!url)
|
|
836
|
+
throw new Error('Detail URL must be a url');
|
|
837
|
+
|
|
838
|
+
if (typeof url !== 'string') throw new Error('Detail URL must be a url');
|
|
839
|
+
if (!URL.canParse(url)) throw new Error('Detail URL must be a valid url');
|
|
840
|
+
|
|
841
|
+
this.details_url = url;
|
|
842
|
+
return this;
|
|
843
|
+
}
|
|
844
|
+
|
|
813
845
|
/**
|
|
814
846
|
* @typedef {Object} RichParty
|
|
815
847
|
* @property {string} id The id of the party
|
package/typings/index.d.ts
CHANGED
|
@@ -216,7 +216,9 @@ export class RichPresence extends Activity {
|
|
|
216
216
|
public setType(type?: ActivityType): this;
|
|
217
217
|
public setApplicationId(id?: Snowflake): this;
|
|
218
218
|
public setDetails(details?: string): this;
|
|
219
|
+
public setDetailsURL(url?: string): this;
|
|
219
220
|
public setState(state?: string): this;
|
|
221
|
+
public setStateURL(url?: string): this;
|
|
220
222
|
public setParty(party?: { max: number; current: number; id?: string }): this;
|
|
221
223
|
public setStartTimestamp(timestamp: Date | number | null): this;
|
|
222
224
|
public setEndTimestamp(timestamp: Date | number | null): this;
|
|
@@ -860,6 +862,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
|
|
|
860
862
|
public sessions: SessionManager;
|
|
861
863
|
public presences: PresenceManager;
|
|
862
864
|
public billing: BillingManager;
|
|
865
|
+
public quests: QuestManager;
|
|
863
866
|
public settings: ClientUserSettingManager;
|
|
864
867
|
public readonly sessionId: If<Ready, string, undefined>;
|
|
865
868
|
public destroy(): void;
|
|
@@ -980,7 +983,13 @@ export class ClientUser extends User {
|
|
|
980
983
|
public stopRinging(channel: ChannelResolvable): Promise<void>;
|
|
981
984
|
public fetchBurstCredit(): Promise<number>;
|
|
982
985
|
public setPronouns(pronouns?: string | null): Promise<this>;
|
|
986
|
+
public setClan(guild?: GuildIDResolve): Promise<this>;
|
|
987
|
+
public deleteClan(): Promise<this>;
|
|
983
988
|
public setGlobalName(globalName?: string | null): Promise<this>;
|
|
989
|
+
public addWidget(type: WidgetType, gameId: string, comment?: string | null, tags?: string[]): Promise<any>;
|
|
990
|
+
public delWidget(type: WidgetType, gameId?: string): Promise<any>;
|
|
991
|
+
public widgetsList(): Promise<WidgetsResponse>;
|
|
992
|
+
public setNameStyle(fontName: FontName | number, effectName: EffectName | number, color1: number | string, color2?: number | string | null): Promise<this>;
|
|
984
993
|
}
|
|
985
994
|
|
|
986
995
|
export class Options extends null {
|
|
@@ -1662,6 +1671,8 @@ export class Guild extends AnonymousGuild {
|
|
|
1662
1671
|
): Promise<this>;
|
|
1663
1672
|
public topEmojis(): Promise<Collection<number, GuildEmoji>>;
|
|
1664
1673
|
public setVanityCode(code?: string): Promise<this>;
|
|
1674
|
+
public mute(options?: GuildMuteOptions): Promise<any>;
|
|
1675
|
+
public unmute(): Promise<any>;
|
|
1665
1676
|
}
|
|
1666
1677
|
|
|
1667
1678
|
export class GuildAuditLogs<T extends GuildAuditLogsResolvable = 'ALL'> {
|
|
@@ -2637,6 +2648,29 @@ export class BillingManager extends BaseManager {
|
|
|
2637
2648
|
public fetchCurrentSubscription(): Promise<Collection<Snowflake, object>>;
|
|
2638
2649
|
}
|
|
2639
2650
|
|
|
2651
|
+
export class QuestManager extends BaseManager {
|
|
2652
|
+
constructor(client: Client);
|
|
2653
|
+
public cache: Collection<string, Quest>;
|
|
2654
|
+
public get(): Promise<QuestData>;
|
|
2655
|
+
public orbs(): Promise<OrbsData>;
|
|
2656
|
+
public getQuest(id: string): Quest | undefined;
|
|
2657
|
+
public list(): Quest[];
|
|
2658
|
+
public getExpired(date?: Date): Quest[];
|
|
2659
|
+
public getCompleted(): Quest[];
|
|
2660
|
+
public getClaimable(): Quest[];
|
|
2661
|
+
public filterQuestsValid(): Quest[];
|
|
2662
|
+
public hasQuest(id: string): boolean;
|
|
2663
|
+
public getApplicationData(ids: string[]): Promise<ApplicationData[]>;
|
|
2664
|
+
public acceptQuest(questId: string, options?: QuestEnrollOptions): Promise<Quest | undefined>;
|
|
2665
|
+
public videoProgress(questId: string, timestamp: number): Promise<any>;
|
|
2666
|
+
public heartbeat(questId: string, applicationId: string, terminal?: boolean): Promise<any>;
|
|
2667
|
+
public doingQuest(quest: Quest): Promise<void>;
|
|
2668
|
+
public autoCompleteAll(): Promise<void>;
|
|
2669
|
+
public readonly size: number;
|
|
2670
|
+
public clear(): void;
|
|
2671
|
+
public [Symbol.iterator](): IterableIterator<Quest>;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2640
2674
|
export class Session extends Base {
|
|
2641
2675
|
constructor(client: Client);
|
|
2642
2676
|
public id?: string;
|
|
@@ -3739,6 +3773,117 @@ export interface Collectibles {
|
|
|
3739
3773
|
nameplate: NameplateData | null;
|
|
3740
3774
|
}
|
|
3741
3775
|
|
|
3776
|
+
export type WidgetType = 'favorite_games' | 'current_games' | 'played_games' | 'want_to_play_games';
|
|
3777
|
+
|
|
3778
|
+
export interface WidgetGameData {
|
|
3779
|
+
game_id: string;
|
|
3780
|
+
comment?: string | null;
|
|
3781
|
+
tags?: string[];
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
export interface WidgetData {
|
|
3785
|
+
id: string;
|
|
3786
|
+
data: {
|
|
3787
|
+
type: WidgetType;
|
|
3788
|
+
games: WidgetGameData[];
|
|
3789
|
+
};
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
export interface WidgetsResponse {
|
|
3793
|
+
widgets: WidgetData[];
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
export interface QuestEnrollOptions {
|
|
3797
|
+
location?: number;
|
|
3798
|
+
isTargeted?: boolean;
|
|
3799
|
+
metadataRaw?: any;
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
export interface QuestTaskConfig {
|
|
3803
|
+
tasks?: {
|
|
3804
|
+
WATCH_VIDEO?: { target: number };
|
|
3805
|
+
WATCH_VIDEO_ON_MOBILE?: { target: number };
|
|
3806
|
+
PLAY_ON_DESKTOP?: { target: number };
|
|
3807
|
+
STREAM_ON_DESKTOP?: { target: number };
|
|
3808
|
+
PLAY_ACTIVITY?: { target: number };
|
|
3809
|
+
};
|
|
3810
|
+
}
|
|
3811
|
+
|
|
3812
|
+
export interface QuestUserStatus {
|
|
3813
|
+
enrolled_at?: string;
|
|
3814
|
+
completed_at?: string;
|
|
3815
|
+
claimed_at?: string;
|
|
3816
|
+
progress?: {
|
|
3817
|
+
WATCH_VIDEO?: { value: number };
|
|
3818
|
+
WATCH_VIDEO_ON_MOBILE?: { value: number };
|
|
3819
|
+
PLAY_ON_DESKTOP?: { value: number };
|
|
3820
|
+
STREAM_ON_DESKTOP?: { value: number };
|
|
3821
|
+
PLAY_ACTIVITY?: { value: number };
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
export interface QuestConfig {
|
|
3826
|
+
expires_at?: string;
|
|
3827
|
+
messages?: {
|
|
3828
|
+
quest_name?: string;
|
|
3829
|
+
};
|
|
3830
|
+
application?: {
|
|
3831
|
+
id: string;
|
|
3832
|
+
name: string;
|
|
3833
|
+
};
|
|
3834
|
+
task_config: QuestTaskConfig;
|
|
3835
|
+
task_config_v2?: QuestTaskConfig;
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
export interface QuestRawData {
|
|
3839
|
+
id: string;
|
|
3840
|
+
config: QuestConfig;
|
|
3841
|
+
user_status?: QuestUserStatus;
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
export class Quest {
|
|
3845
|
+
constructor(data: QuestRawData);
|
|
3846
|
+
public id: string;
|
|
3847
|
+
public config: QuestConfig;
|
|
3848
|
+
public userStatus?: QuestUserStatus;
|
|
3849
|
+
public isExpired(date?: Date): boolean;
|
|
3850
|
+
public isCompleted(): boolean;
|
|
3851
|
+
public hasClaimedRewards(): boolean;
|
|
3852
|
+
public isEnrolledQuest(): boolean;
|
|
3853
|
+
public updateUserStatus(status: Partial<QuestUserStatus>): void;
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
export interface QuestData {
|
|
3857
|
+
quests?: QuestRawData[];
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
export interface OrbsData {
|
|
3861
|
+
balance?: number;
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
export interface ApplicationData {
|
|
3865
|
+
id: string;
|
|
3866
|
+
name: string;
|
|
3867
|
+
icon: string;
|
|
3868
|
+
description: string;
|
|
3869
|
+
executables: {
|
|
3870
|
+
os: string;
|
|
3871
|
+
name: string;
|
|
3872
|
+
is_launcher: boolean;
|
|
3873
|
+
}[];
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
export type FontName = 'Sans' | 'Tempo' | 'Sakura' | 'JellyBean' | 'Modern' | 'Medieval' | '8Bit' | 'Vampire';
|
|
3877
|
+
|
|
3878
|
+
export type EffectName = 'Solid' | 'Gradient' | 'Neon' | 'Toon' | 'Pop';
|
|
3879
|
+
|
|
3880
|
+
export interface GuildMuteOptions {
|
|
3881
|
+
muted?: boolean;
|
|
3882
|
+
suppressRoles?: boolean;
|
|
3883
|
+
suppressEveryone?: boolean;
|
|
3884
|
+
muteScheduledEvents?: boolean;
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3742
3887
|
export class User extends PartialTextBasedChannel(Base) {
|
|
3743
3888
|
protected constructor(client: Client, data: RawUserData);
|
|
3744
3889
|
private _equals(user: APIUser): boolean;
|