discord-self-lite 0.1.10 → 0.1.12
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/package.json +1 -1
- package/src/classes/Channel.js +7 -6
- package/src/classes/Client.js +21 -1
- package/src/classes/Guild.js +4 -4
- package/src/classes/Message.js +36 -18
- package/src/connection/rest/RestManager.js +26 -6
- package/src/connection/ws/WebSocket.js +36 -16
- package/src/classes/WebSocketError.js +0 -18
package/package.json
CHANGED
package/src/classes/Channel.js
CHANGED
|
@@ -55,6 +55,7 @@ class Channel {
|
|
|
55
55
|
*/
|
|
56
56
|
async send(payload) {
|
|
57
57
|
const data = await this.rest.sendMessage(this.id, payload);
|
|
58
|
+
if (!data) return null;
|
|
58
59
|
return new Message(this.client, data);
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -134,7 +135,7 @@ class Channel {
|
|
|
134
135
|
* @returns {Guild|null} The guild instance or null if not a guild channel
|
|
135
136
|
*/
|
|
136
137
|
getGuild() {
|
|
137
|
-
const guildId = this.
|
|
138
|
+
const guildId = this.guildId;
|
|
138
139
|
if (!guildId) return null;
|
|
139
140
|
return this.client.getGuild(guildId);
|
|
140
141
|
}
|
|
@@ -144,7 +145,7 @@ class Channel {
|
|
|
144
145
|
* @returns {Promise<Guild|null>} The guild instance or null if not a guild channel
|
|
145
146
|
*/
|
|
146
147
|
async fetchGuild() {
|
|
147
|
-
const guildId = this.
|
|
148
|
+
const guildId = this.guildId;
|
|
148
149
|
if (!guildId) return null;
|
|
149
150
|
return await this.client.fetchGuild(guildId);
|
|
150
151
|
}
|
|
@@ -213,7 +214,7 @@ class Channel {
|
|
|
213
214
|
if (this.isDM() || this.isGroupDM()) {
|
|
214
215
|
return `https://discord.com/channels/@me/${this.id}`;
|
|
215
216
|
}
|
|
216
|
-
const guildId = this.
|
|
217
|
+
const guildId = this.guildId;
|
|
217
218
|
return `https://discord.com/channels/${guildId}/${this.id}`;
|
|
218
219
|
}
|
|
219
220
|
|
|
@@ -234,8 +235,8 @@ class Channel {
|
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
// Apply channel permission overwrites
|
|
237
|
-
if (this.
|
|
238
|
-
const overwrites = this.
|
|
238
|
+
if (this.permissionOverwrites) {
|
|
239
|
+
const overwrites = this.permissionOverwrites;
|
|
239
240
|
|
|
240
241
|
// Role overwrites (deny takes precedence over allow)
|
|
241
242
|
const memberRoles = member.roles || [];
|
|
@@ -244,7 +245,7 @@ class Channel {
|
|
|
244
245
|
// Role overwrite
|
|
245
246
|
if (
|
|
246
247
|
memberRoles.includes(overwrite.id) ||
|
|
247
|
-
overwrite.id === this.
|
|
248
|
+
overwrite.id === this.guildId
|
|
248
249
|
) {
|
|
249
250
|
permissions &= ~toBigInt(overwrite.deny || 0);
|
|
250
251
|
permissions |= toBigInt(overwrite.allow || 0);
|
package/src/classes/Client.js
CHANGED
|
@@ -49,7 +49,27 @@ class Client extends EventEmitter {
|
|
|
49
49
|
async login(token) {
|
|
50
50
|
this.token = token;
|
|
51
51
|
this.rest = new RestManager(this.token, this.options.apiVersion || 9);
|
|
52
|
-
|
|
52
|
+
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const onReady = () => {
|
|
55
|
+
this.removeListener("error", onError);
|
|
56
|
+
resolve();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const onError = (msg) => {
|
|
60
|
+
this.removeListener("ready", onReady);
|
|
61
|
+
reject(new Error(msg));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
this.once("ready", onReady);
|
|
65
|
+
this.once("error", onError);
|
|
66
|
+
|
|
67
|
+
this.connect().catch((err) => {
|
|
68
|
+
this.removeListener("ready", onReady);
|
|
69
|
+
this.removeListener("error", onError);
|
|
70
|
+
reject(err);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
/**
|
package/src/classes/Guild.js
CHANGED
|
@@ -105,10 +105,10 @@ class Guild {
|
|
|
105
105
|
* @returns {string|null} The icon URL or null if no icon
|
|
106
106
|
*/
|
|
107
107
|
getIconURL(options = {}) {
|
|
108
|
-
if (!this.
|
|
108
|
+
if (!this.icon) return null;
|
|
109
109
|
const size = options.size || 512;
|
|
110
110
|
const format = options.format || "png";
|
|
111
|
-
return `https://cdn.discordapp.com/icons/${this.id}/${this.
|
|
111
|
+
return `https://cdn.discordapp.com/icons/${this.id}/${this.icon}.${format}?size=${size}`;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/**
|
|
@@ -119,10 +119,10 @@ class Guild {
|
|
|
119
119
|
* @returns {string|null} The banner URL or null if no banner
|
|
120
120
|
*/
|
|
121
121
|
getBannerURL(options = {}) {
|
|
122
|
-
if (!this.
|
|
122
|
+
if (!this.banner) return null;
|
|
123
123
|
const size = options.size || 512;
|
|
124
124
|
const format = options.format || "png";
|
|
125
|
-
return `https://cdn.discordapp.com/banners/${this.id}/${this.
|
|
125
|
+
return `https://cdn.discordapp.com/banners/${this.id}/${this.banner}.${format}?size=${size}`;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
package/src/classes/Message.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
const WebSocketError = require("./WebSocketError");
|
|
2
1
|
const User = require("./User");
|
|
3
2
|
|
|
3
|
+
function camelCaseKeys(obj) {
|
|
4
|
+
if (Array.isArray(obj)) {
|
|
5
|
+
return obj.map((item) => camelCaseKeys(item));
|
|
6
|
+
} else if (obj !== null && typeof obj === "object") {
|
|
7
|
+
const newObj = {};
|
|
8
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
9
|
+
const camelKey = key.replace(/_([a-z])/g, (_, letter) =>
|
|
10
|
+
letter.toUpperCase(),
|
|
11
|
+
);
|
|
12
|
+
newObj[camelKey] = camelCaseKeys(value);
|
|
13
|
+
}
|
|
14
|
+
return newObj;
|
|
15
|
+
}
|
|
16
|
+
return obj;
|
|
17
|
+
}
|
|
18
|
+
|
|
4
19
|
/**
|
|
5
20
|
* Represents a Discord message
|
|
6
21
|
*/
|
|
@@ -32,12 +47,13 @@ class Message {
|
|
|
32
47
|
}
|
|
33
48
|
}
|
|
34
49
|
|
|
35
|
-
this.components = this.components || [];
|
|
50
|
+
this.components = camelCaseKeys(this.components || []);
|
|
36
51
|
this.attachments = this.attachments || [];
|
|
37
|
-
this.embeds = this.embeds || [];
|
|
52
|
+
this.embeds = camelCaseKeys(this.embeds || []);
|
|
38
53
|
this.mentions = this.mentions || [];
|
|
39
54
|
this.mentionRoles = this.mentionRoles || [];
|
|
40
55
|
this.reactions = this.reactions || [];
|
|
56
|
+
this.url = `https://discord.com/channels/${this.guildId ? this.guildId : "@me"}/${this.channelId}/${this.id}`;
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
/**
|
|
@@ -65,9 +81,9 @@ class Message {
|
|
|
65
81
|
this[camelKey] = value;
|
|
66
82
|
}
|
|
67
83
|
|
|
68
|
-
this.components = this.components || [];
|
|
84
|
+
this.components = camelCaseKeys(this.components || []);
|
|
69
85
|
this.attachments = this.attachments || [];
|
|
70
|
-
this.embeds = this.embeds || [];
|
|
86
|
+
this.embeds = camelCaseKeys(this.embeds || []);
|
|
71
87
|
this.mentions = this.mentions || [];
|
|
72
88
|
this.mentionRoles = this.mentionRoles || [];
|
|
73
89
|
this.reactions = this.reactions || [];
|
|
@@ -117,11 +133,11 @@ class Message {
|
|
|
117
133
|
|
|
118
134
|
// Get all buttons from components
|
|
119
135
|
const buttons = [];
|
|
120
|
-
if (this.
|
|
121
|
-
for (const row of this.
|
|
136
|
+
if (this.components && Array.isArray(this.components)) {
|
|
137
|
+
for (const row of this.components) {
|
|
122
138
|
if (row.components && Array.isArray(row.components)) {
|
|
123
139
|
for (const component of row.components) {
|
|
124
|
-
if (component.type === 2 && component.
|
|
140
|
+
if (component.type === 2 && component.customId) {
|
|
125
141
|
// Type 2 is button
|
|
126
142
|
buttons.push(component);
|
|
127
143
|
}
|
|
@@ -137,7 +153,7 @@ class Message {
|
|
|
137
153
|
// Handle different input types
|
|
138
154
|
if (input === null) {
|
|
139
155
|
// No input: click first button
|
|
140
|
-
customId = buttons[0].
|
|
156
|
+
customId = buttons[0].customId;
|
|
141
157
|
} else if (typeof input === "number") {
|
|
142
158
|
// Integer input: click button at that index
|
|
143
159
|
if (input < 0 || input >= buttons.length) {
|
|
@@ -147,21 +163,21 @@ class Message {
|
|
|
147
163
|
} button(s) (0-${buttons.length - 1})`,
|
|
148
164
|
);
|
|
149
165
|
}
|
|
150
|
-
customId = buttons[input].
|
|
166
|
+
customId = buttons[input].customId;
|
|
151
167
|
} else if (typeof input === "string") {
|
|
152
|
-
// String input: use as
|
|
168
|
+
// String input: use as customId directly
|
|
153
169
|
customId = input;
|
|
154
170
|
} else {
|
|
155
171
|
throw new Error("Invalid input type. Expected null, number, or string.");
|
|
156
172
|
}
|
|
157
173
|
|
|
158
174
|
// Get required data for button interaction
|
|
159
|
-
const applicationId = this.
|
|
160
|
-
const messageFlags = this.
|
|
175
|
+
const applicationId = this.applicationId || this.author.id;
|
|
176
|
+
const messageFlags = this.flags || 0;
|
|
161
177
|
|
|
162
178
|
if (!this.client.sessionId) {
|
|
163
|
-
throw new
|
|
164
|
-
"No session ID available - client not properly connected",
|
|
179
|
+
throw new Error(
|
|
180
|
+
"No session ID available - client not properly connected (yet).",
|
|
165
181
|
);
|
|
166
182
|
}
|
|
167
183
|
|
|
@@ -197,7 +213,7 @@ class Message {
|
|
|
197
213
|
* Returns the referenced `Message` instance or `null` if not available.
|
|
198
214
|
*/
|
|
199
215
|
async fetchReference() {
|
|
200
|
-
const ref = this.
|
|
216
|
+
const ref = this.data?.message_reference;
|
|
201
217
|
if (!ref) return null;
|
|
202
218
|
|
|
203
219
|
const messageId = ref.messageId || ref.message_id;
|
|
@@ -207,13 +223,15 @@ class Message {
|
|
|
207
223
|
let channel;
|
|
208
224
|
try {
|
|
209
225
|
channel = await this.client.resolveChannel(channelId);
|
|
210
|
-
} catch {
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.error(error);
|
|
211
228
|
return null;
|
|
212
229
|
}
|
|
213
230
|
|
|
214
231
|
try {
|
|
215
232
|
return await channel.fetchMessage(messageId);
|
|
216
|
-
} catch {
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error(error);
|
|
217
235
|
return null;
|
|
218
236
|
}
|
|
219
237
|
}
|
|
@@ -74,7 +74,7 @@ class RestManager {
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
console.log(
|
|
77
|
-
`⏸️
|
|
77
|
+
`⏸️ Suspending route ${routeKey} for 2 minutes (pre-emptive)`,
|
|
78
78
|
);
|
|
79
79
|
|
|
80
80
|
// Clear queued requests for this route
|
|
@@ -192,7 +192,7 @@ class RestManager {
|
|
|
192
192
|
});
|
|
193
193
|
|
|
194
194
|
console.log(
|
|
195
|
-
`⏸️
|
|
195
|
+
`⏸️ Suspending route ${routeKey} for 2 minutes (queue processing)`,
|
|
196
196
|
);
|
|
197
197
|
|
|
198
198
|
// Clear remaining queued requests for this route
|
|
@@ -267,15 +267,14 @@ class RestManager {
|
|
|
267
267
|
`⏳ Global rate limit set, waiting ${retryAfter + 2000}ms`,
|
|
268
268
|
);
|
|
269
269
|
} else {
|
|
270
|
-
|
|
271
|
-
const suspensionTime = 2 * 60 * 1000;
|
|
270
|
+
const suspensionTime = retryAfter + 500;
|
|
272
271
|
this.suspendedRoutes.set(routeKey, {
|
|
273
272
|
suspendedUntil: Date.now() + suspensionTime,
|
|
274
273
|
reason: `429 response on attempt ${attempt + 1}`,
|
|
275
274
|
});
|
|
276
275
|
|
|
277
276
|
console.log(
|
|
278
|
-
`⏸️
|
|
277
|
+
`⏸️ Suspending route ${routeKey} for ${suspensionTime}ms (429 response)`,
|
|
279
278
|
);
|
|
280
279
|
|
|
281
280
|
// Clear queue for this route
|
|
@@ -353,6 +352,15 @@ class RestManager {
|
|
|
353
352
|
|
|
354
353
|
return await response.json();
|
|
355
354
|
} catch (error) {
|
|
355
|
+
// Do not retry on Missing Permissions (50013)
|
|
356
|
+
if (
|
|
357
|
+
error.name === "DiscordAPIError" &&
|
|
358
|
+
error.fullError &&
|
|
359
|
+
error.fullError.code === 50013
|
|
360
|
+
) {
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
|
|
356
364
|
if (attempt === maxRetries - 1) {
|
|
357
365
|
throw error;
|
|
358
366
|
}
|
|
@@ -672,7 +680,19 @@ class RestManager {
|
|
|
672
680
|
* @returns {Promise<object>} Message data
|
|
673
681
|
*/
|
|
674
682
|
async fetchMessage(channelId, messageId) {
|
|
675
|
-
|
|
683
|
+
const messages = await this.request(
|
|
684
|
+
`/channels/${channelId}/messages?limit=1&around=${messageId}`,
|
|
685
|
+
);
|
|
686
|
+
if (Array.isArray(messages)) {
|
|
687
|
+
const message = messages.find((m) => m.id === messageId);
|
|
688
|
+
if (message) return message;
|
|
689
|
+
}
|
|
690
|
+
throw new DiscordAPIError(
|
|
691
|
+
"Unknown Message",
|
|
692
|
+
404,
|
|
693
|
+
`/channels/${channelId}/messages/${messageId}`,
|
|
694
|
+
{ message: "Unknown Message", code: 10008 },
|
|
695
|
+
);
|
|
676
696
|
}
|
|
677
697
|
|
|
678
698
|
/**
|
|
@@ -4,8 +4,6 @@ const Message = require("../../classes/Message");
|
|
|
4
4
|
const Guild = require("../../classes/Guild");
|
|
5
5
|
const Channel = require("../../classes/Channel");
|
|
6
6
|
const ClientUser = require("../../classes/ClientUser");
|
|
7
|
-
const WebSocketError = require("../../classes/WebSocketError");
|
|
8
|
-
|
|
9
7
|
class DiscordWebSocket extends EventEmitter {
|
|
10
8
|
constructor(client, token, options = {}) {
|
|
11
9
|
super();
|
|
@@ -31,7 +29,7 @@ class DiscordWebSocket extends EventEmitter {
|
|
|
31
29
|
this.ws = new WebSocket(this.gatewayUrl);
|
|
32
30
|
|
|
33
31
|
this.ws.on("open", () => {
|
|
34
|
-
|
|
32
|
+
this.client.emit("debug", "WebSocket connected");
|
|
35
33
|
this.client.emit("connected");
|
|
36
34
|
});
|
|
37
35
|
|
|
@@ -40,23 +38,47 @@ class DiscordWebSocket extends EventEmitter {
|
|
|
40
38
|
});
|
|
41
39
|
|
|
42
40
|
this.ws.on("close", (code, reason) => {
|
|
43
|
-
|
|
41
|
+
this.client.emit("debug", `WebSocket closed: ${code} - ${reason}`);
|
|
44
42
|
this.ready = false;
|
|
45
43
|
if (this.heartbeatInterval) {
|
|
46
44
|
clearInterval(this.heartbeatInterval);
|
|
47
45
|
this.heartbeatInterval = null;
|
|
48
46
|
}
|
|
49
47
|
this.client.emit("disconnected", code, reason);
|
|
48
|
+
const terminalCodes = [4004, 4010, 4011, 4012, 4013, 4014];
|
|
49
|
+
|
|
50
|
+
if (terminalCodes.includes(code)) {
|
|
51
|
+
if (code === 4004) {
|
|
52
|
+
try {
|
|
53
|
+
if (this.ws) this.ws.terminate();
|
|
54
|
+
} catch {
|
|
55
|
+
/* ignore */
|
|
56
|
+
}
|
|
57
|
+
this.ws = null;
|
|
58
|
+
this.client.emit(
|
|
59
|
+
"error",
|
|
60
|
+
`Authentication failed (close code ${code}). Please check your token and try again.`,
|
|
61
|
+
);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.client.emit(
|
|
66
|
+
"debug",
|
|
67
|
+
`Terminal error received (${code}). Connection will not be restarted. (Reason: ${reason})`,
|
|
68
|
+
);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
50
72
|
if (code !== 1000) {
|
|
51
|
-
// Not a clean close
|
|
52
73
|
this.reconnect();
|
|
53
74
|
}
|
|
54
75
|
});
|
|
55
76
|
|
|
56
77
|
this.ws.on("error", (error) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
78
|
+
this.client.emit(
|
|
79
|
+
"error",
|
|
80
|
+
`WebSocket error: ${error && error.message ? error.message : String(error)}`,
|
|
81
|
+
);
|
|
60
82
|
});
|
|
61
83
|
}
|
|
62
84
|
|
|
@@ -87,7 +109,7 @@ class DiscordWebSocket extends EventEmitter {
|
|
|
87
109
|
this.handleDispatch(message);
|
|
88
110
|
break;
|
|
89
111
|
default:
|
|
90
|
-
|
|
112
|
+
this.client.emit("debug", `Unhandled op: ${message.op}`);
|
|
91
113
|
}
|
|
92
114
|
}
|
|
93
115
|
|
|
@@ -185,9 +207,9 @@ class DiscordWebSocket extends EventEmitter {
|
|
|
185
207
|
const member = new GuildMember(this.client, memberData, guild);
|
|
186
208
|
guild._members.set(this.client.user.id, member);
|
|
187
209
|
} catch (err) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
err.message
|
|
210
|
+
this.client.emit(
|
|
211
|
+
"debug",
|
|
212
|
+
`Failed to fetch member for guild ${guild.id}: ${err && err.message ? err.message : String(err)}`,
|
|
191
213
|
);
|
|
192
214
|
}
|
|
193
215
|
}
|
|
@@ -197,16 +219,14 @@ class DiscordWebSocket extends EventEmitter {
|
|
|
197
219
|
|
|
198
220
|
reconnect() {
|
|
199
221
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
200
|
-
|
|
201
|
-
console.error(err);
|
|
202
|
-
this.client.emit("error", err);
|
|
222
|
+
this.client.emit("error", "Max reconnect attempts reached");
|
|
203
223
|
this.client.emit("maxReconnects");
|
|
204
224
|
return;
|
|
205
225
|
}
|
|
206
226
|
|
|
207
227
|
this.reconnectAttempts++;
|
|
208
228
|
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); // Exponential backoff
|
|
209
|
-
|
|
229
|
+
this.client.emit("debug", `Reconnecting in ${delay}ms...`);
|
|
210
230
|
setTimeout(() => {
|
|
211
231
|
this.connect();
|
|
212
232
|
}, delay);
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Represents an error related to the WebSocket connection
|
|
3
|
-
* @extends Error
|
|
4
|
-
*/
|
|
5
|
-
class WebSocketError extends Error {
|
|
6
|
-
/**
|
|
7
|
-
* Create a new WebSocketError
|
|
8
|
-
* @param {string} message - The error message
|
|
9
|
-
* @param {number} [code] - The WebSocket close code
|
|
10
|
-
*/
|
|
11
|
-
constructor(message, code) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.name = "WebSocketError";
|
|
14
|
-
this.code = code;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
module.exports = WebSocketError;
|