djs-selfbot-v13 3.7.1 → 3.7.3
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 +60 -2
- package/package.json +4 -4
- package/src/client/Client.js +14 -0
- package/src/client/actions/GuildMemberRemove.js +1 -1
- package/src/client/actions/GuildMemberUpdate.js +1 -1
- package/src/client/websocket/handlers/GUILD_MEMBER_ADD.js +1 -1
- package/src/managers/BillingManager.js +3 -3
- package/src/managers/DeveloperManager.js +253 -0
- package/src/managers/QuestManager.js +376 -0
- package/src/managers/ThreadManager.js +1 -1
- package/src/structures/ClientUser.js +180 -6
- package/src/structures/Guild.js +54 -0
- package/src/structures/Presence.js +32 -0
- package/src/structures/interfaces/Application.js +128 -0
- package/src/structures/interfaces/TextBasedChannel.js +27 -0
- package/typings/index.d.ts +221 -0
|
@@ -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;
|
|
@@ -161,7 +161,7 @@ class ThreadManager extends CachedManager {
|
|
|
161
161
|
// Discord sends the thread id as id in this object
|
|
162
162
|
for (const rawMember of rawThreads.members) client.channels.cache.get(rawMember.id)?.members._add(rawMember);
|
|
163
163
|
// Patch firstMessage
|
|
164
|
-
// According to https://github.com/aiko-chan-ai/
|
|
164
|
+
// According to https://github.com/aiko-chan-ai/djs-selfbot-v13/issues/1502, rawThreads.first_messages could be null.
|
|
165
165
|
for (const rawMessage of rawThreads?.first_messages || []) {
|
|
166
166
|
client.channels.cache.get(rawMessage.id)?.messages._add(rawMessage);
|
|
167
167
|
}
|
|
@@ -203,7 +203,7 @@ class ClientUser extends User {
|
|
|
203
203
|
* @example
|
|
204
204
|
* // Set the client user's presence
|
|
205
205
|
* client.user.setPresence({ activities: [{ name: 'with discord.js' }], status: 'idle' });
|
|
206
|
-
* @see {@link https://github.com/aiko-chan-ai/
|
|
206
|
+
* @see {@link https://github.com/aiko-chan-ai/djs-selfbot-v13/blob/main/Document/RichPresence.md}
|
|
207
207
|
*/
|
|
208
208
|
setPresence(data) {
|
|
209
209
|
return this.client.presence.set(data);
|
|
@@ -248,7 +248,7 @@ class ClientUser extends User {
|
|
|
248
248
|
* @example
|
|
249
249
|
* // Set the client user's activity
|
|
250
250
|
* client.user.setActivity('discord.js', { type: 'WATCHING' });
|
|
251
|
-
* @see {@link https://github.com/aiko-chan-ai/
|
|
251
|
+
* @see {@link https://github.com/aiko-chan-ai/djs-selfbot-v13/blob/main/Document/RichPresence.md}
|
|
252
252
|
*/
|
|
253
253
|
setActivity(name, options = {}) {
|
|
254
254
|
if (!name) return this.setPresence({ activities: [], shardId: options.shardId });
|
|
@@ -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
|