discord-self-lite 0.1.9 → 0.1.11

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 CHANGED
@@ -45,126 +45,17 @@ client.on("messageCreate", async (message) => {
45
45
 
46
46
  ## API Reference
47
47
 
48
- ### Client
48
+ The full API documentation is available in the [`docs/api/`](docs/api/README.md) directory. Here are the core classes:
49
49
 
50
- ```javascript
51
- const { Client } = require("discord-self-lite");
52
- const client = new Client();
53
- ```
54
-
55
- #### Methods
56
-
57
- - `client.login(token)` - Login with user token
58
- - `client.getChannel(channelId)` - Get cached channel
59
- - `client.fetchChannel(channelId)` - Fetch channel from API
60
- - `client.fetchMessage(channelId, messageId)` - Fetch specific message
61
-
62
- #### Properties
63
-
64
- - `client.user` - Current user object (available after ready event)
65
- - `client.guilds` - Map of cached guild instances
66
- - `client.channels` - Map of cached channel instances
67
- - `client.sessionId` - Session ID from Discord
68
-
69
- #### Events
70
-
71
- - `ready` - Fired when client is ready
72
- - `messageCreate` - Fired when a message is created
73
-
74
- ### User
75
-
76
- #### Methods
77
-
78
- - `user.setStatus(status)` - Set user status ('online', 'idle', 'dnd', 'invisible')
79
- - `user.setPresence(presence)` - Set full presence data
80
- - `user.setActivity(activity)` - Set user activity
81
-
82
- #### Properties
83
-
84
- - `user.id` - User ID
85
- - `user.username` - Username
86
- - `user.discriminator` - Discriminator (4-digit number)
87
- - `user.avatar` - Avatar hash
88
-
89
- ### Message
90
-
91
- #### Methods
92
-
93
- - `message.reply(content, options)` - Reply to the message
94
- - `message.react(emoji)` - React to the message
95
- - `message.clickButton(identifier)` - Click a button on the message
96
- - `identifier` can be:
97
- - `null` or omitted: clicks first button
98
- - `number`: clicks button at index (0-based)
99
- - `string`: clicks button with custom ID
100
-
101
- #### Properties
102
-
103
- - `message.content` - Message content
104
- - `message.author` - Message author
105
- - `message.channel` - Message channel
106
- - `message.guild` - Message guild (null for DMs)
107
- - `message.components` - Message components (buttons, etc.)
108
-
109
- ### Channel
110
-
111
- #### Methods
112
-
113
- - `channel.send(content, options)` - Send a message to the channel
114
- - `channel.fetchMessages(options)` - Fetch messages from the channel
115
-
116
- #### Properties
117
-
118
- - `channel.id` - Channel ID
119
- - `channel.name` - Channel name
120
- - `channel.type` - Channel type
121
-
122
- ### Guild
123
-
124
- #### Methods
125
-
126
- - `guild.getChannels()` - Get cached channels for this guild
127
- - `guild.fetchChannels()` - Fetch all channels for this guild from API
128
- - `guild.getChannel(channelId)` - Get a specific channel from cache
129
- - `guild.fetchChannel(channelId)` - Fetch a specific channel from API
130
-
131
- #### Properties
132
-
133
- - `guild.id` - Guild ID
134
- - `guild.name` - Guild name
135
- - `guild.icon` - Guild icon hash
136
- - `guild.ownerId` - Guild owner ID
137
-
138
- ### WebhookClient
139
-
140
- Send messages to Discord webhooks with support for embeds and more.
141
-
142
- ```javascript
143
- const { WebhookClient } = require("discord-self-lite");
144
- const webhook = new WebhookClient("YOUR_WEBHOOK_URL", {
145
- username: "My Bot",
146
- avatarURL: "https://example.com/avatar.png",
147
- });
148
- ```
149
-
150
- #### Constructor
151
-
152
- - `new WebhookClient(url, options)` - Create a webhook client
153
- - `url` - Discord webhook URL
154
- - `options.username` - Default username for messages
155
- - `options.avatarURL` - Default avatar URL for messages
156
-
157
- #### Methods
158
-
159
- - `webhook.send(content, options)` - Send a message (supports text, embeds, and objects)
160
- - `webhook.edit(messageId, content, options)` - Edit a message
161
- - `webhook.delete(messageId)` - Delete a message
162
- - `webhook.fetchMessage(messageId)` - Fetch a message
163
-
164
- #### Static Methods
50
+ - **[`Client`](docs/api/Client.md)** - Main entry point, connection manager, and cache storage.
51
+ - **[`User`](docs/api/User.md) & [`ClientUser`](docs/api/ClientUser.md)** - Represents Discord users and the logged-in selfbot.
52
+ - **[`Message`](docs/api/Message.md)** - Represents text messages, supports replying, reacting, and clicking buttons.
53
+ - **[`Channel`](docs/api/Channel.md)** - Represents Text, DM, or Voice channels to fetch or send messages.
54
+ - **[`Guild`](docs/api/Guild.md) & [`GuildMember`](docs/api/GuildMember.md)** - Represents Discord servers and their members.
55
+ - **[`WebhookClient`](docs/api/WebhookClient.md)** - Standalone client to send requests via webhooks.
56
+ - **[`Permissions`](docs/api/Permissions.md) & [`BitField`](docs/api/BitField.md)** - Utility classes for resolving and checking Discord permissions.
165
57
 
166
- - `WebhookClient.createEmbed(data)` - Create an embed object
167
- - `WebhookClient.parseURL(url)` - Parse webhook URL to get ID and token
58
+ _Check the [Complete API Reference](docs/api/README.md) for full properties, methods, and examples of each._
168
59
 
169
60
  ## Examples
170
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discord-self-lite",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Lightweight Discord selfbot library",
5
5
  "keywords": [
6
6
  "discord",
@@ -32,8 +32,7 @@
32
32
  "test": "eslint . && prettier --check .",
33
33
  "format": "prettier --write .",
34
34
  "fix": "eslint . --fix && prettier --write .",
35
- "docs": "echo \"📚 Documentation available at docs/README.md\"",
36
- "docs:html": "jsdoc -c jsdoc.conf.json"
35
+ "docs": "echo \"📚 Documentation available at docs/README.md\""
37
36
  },
38
37
  "repository": {
39
38
  "type": "git",
@@ -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
 
@@ -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
- await this.connect();
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
  /**
@@ -99,39 +119,63 @@ class Client extends EventEmitter {
99
119
  }
100
120
 
101
121
  /**
102
- * Get channel from cache, or create empty instance if not cached
122
+ * Get channel from cache
103
123
  * @param {string} id - The channel ID
104
- * @returns {Channel} The channel instance
124
+ * @returns {Channel|null} The cached channel instance or null
105
125
  */
106
126
  getChannel(id) {
107
- if (!this.channels.has(id)) {
108
- this.channels.set(id, new Channel(this, this.rest, id));
109
- }
110
- return this.channels.get(id);
127
+ return this.channels.get(id) || null;
111
128
  }
112
129
 
113
130
  /**
114
- * Get guild from cache, or create empty instance if not cached
131
+ * Resolve channel from cache or fetch from API
132
+ * @param {string} id - The channel ID
133
+ * @returns {Promise<Channel>} The resolved channel instance
134
+ */
135
+ async resolveChannel(id) {
136
+ const cached = this.getChannel(id);
137
+ if (cached) return cached;
138
+ return await this.fetchChannel(id);
139
+ }
140
+
141
+ /**
142
+ * Get guild from cache
115
143
  * @param {string} id - The guild ID
116
- * @returns {Guild} The guild instance
144
+ * @returns {Guild|null} The cached guild instance or null
117
145
  */
118
146
  getGuild(id) {
119
- if (!this.guilds.has(id)) {
120
- this.guilds.set(id, new Guild(this, this.rest, id));
121
- }
122
- return this.guilds.get(id);
147
+ return this.guilds.get(id) || null;
123
148
  }
124
149
 
125
150
  /**
126
- * Get user from cache, or create lightweight instance if not cached
151
+ * Resolve guild from cache or fetch from API
152
+ * @param {string} id - The guild ID
153
+ * @returns {Promise<Guild>} The resolved guild instance
154
+ */
155
+ async resolveGuild(id) {
156
+ const cached = this.getGuild(id);
157
+ if (cached) return cached;
158
+ return await this.fetchGuild(id);
159
+ }
160
+
161
+ /**
162
+ * Get user from cache
127
163
  * @param {string} id - The user ID
128
- * @returns {User} The User instance
164
+ * @returns {User|null} The cached user instance or null
129
165
  */
130
166
  getUser(id) {
131
- if (!this.users.has(id)) {
132
- this.users.set(id, new User(this, { id }));
133
- }
134
- return this.users.get(id);
167
+ return this.users.get(id) || null;
168
+ }
169
+
170
+ /**
171
+ * Resolve user from cache or fetch from API
172
+ * @param {string} id - The user ID
173
+ * @returns {Promise<User>} The resolved user instance
174
+ */
175
+ async resolveUser(id) {
176
+ const cached = this.getUser(id);
177
+ if (cached) return cached;
178
+ return await this.fetchUser(id);
135
179
  }
136
180
 
137
181
  /**
@@ -44,7 +44,7 @@ class Guild {
44
44
  /**
45
45
  * Get a channel from this guild (from cache)
46
46
  * @param {string} channelId - The channel ID
47
- * @returns {Channel} The channel instance
47
+ * @returns {Channel|null} The cached channel instance or null
48
48
  */
49
49
  getChannel(channelId) {
50
50
  const channel = this.client.getChannel(channelId);
@@ -1,4 +1,3 @@
1
- const WebSocketError = require("./WebSocketError");
2
1
  const User = require("./User");
3
2
 
4
3
  /**
@@ -38,11 +37,12 @@ class Message {
38
37
  this.mentions = this.mentions || [];
39
38
  this.mentionRoles = this.mentionRoles || [];
40
39
  this.reactions = this.reactions || [];
40
+ this.url = `https://discord.com/channels/${this.guildId ? this.guildId : "@me"}/${this.channelId}/${this.id}`;
41
41
  }
42
42
 
43
43
  /**
44
44
  * Get the channel this message was sent in
45
- * @returns {Channel} The channel instance
45
+ * @returns {Channel|null} The cached channel instance, or null if not cached
46
46
  */
47
47
  get channel() {
48
48
  return this.client.getChannel(this.channelId);
@@ -89,11 +89,12 @@ class Message {
89
89
  * @returns {Promise<object>} The sent reply message data
90
90
  */
91
91
  async reply(payload) {
92
+ const channel = await this.client.resolveChannel(this.channelId);
92
93
  const replyPayload =
93
94
  typeof payload === "string"
94
95
  ? { content: payload, message_reference: { message_id: this.id } }
95
96
  : { ...payload, message_reference: { message_id: this.id } };
96
- return await this.channel.send(replyPayload);
97
+ return await channel.send(replyPayload);
97
98
  }
98
99
 
99
100
  /**
@@ -159,8 +160,8 @@ class Message {
159
160
  const messageFlags = this.data.flags || 0;
160
161
 
161
162
  if (!this.client.sessionId) {
162
- throw new WebSocketError(
163
- "No session ID available - client not properly connected",
163
+ throw new Error(
164
+ "No session ID available - client not properly connected (yet).",
164
165
  );
165
166
  }
166
167
 
@@ -196,21 +197,18 @@ class Message {
196
197
  * Returns the referenced `Message` instance or `null` if not available.
197
198
  */
198
199
  async fetchReference() {
199
- const ref = this.messageReference || this.data?.message_reference;
200
+ const ref = this.data?.message_reference;
200
201
  if (!ref) return null;
201
202
 
202
203
  const messageId = ref.messageId || ref.message_id;
203
204
  const channelId = ref.channelId || ref.channel_id || this.channelId;
204
205
  if (!messageId || !channelId) return null;
205
206
 
206
- // Try to get channel from cache or fetch from API
207
- let channel = this.client.getChannel(channelId);
208
- if (!channel) {
209
- try {
210
- channel = await this.client.fetchChannel(channelId);
211
- } catch {
212
- return null;
213
- }
207
+ let channel;
208
+ try {
209
+ channel = await this.client.resolveChannel(channelId);
210
+ } catch {
211
+ return null;
214
212
  }
215
213
 
216
214
  try {
@@ -74,7 +74,7 @@ class RestManager {
74
74
  });
75
75
 
76
76
  console.log(
77
- `⏸️ Suspending route ${routeKey} for 2 minutes (pre-emptive)`,
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
- `⏸️ Suspending route ${routeKey} for 2 minutes (queue processing)`,
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
- // Immediately suspend the route for 2 minutes on first 429
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
- `⏸️ Suspending route ${routeKey} for 2 minutes (429 response)`,
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
  }
@@ -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
- console.log("WebSocket connected");
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
- console.log(`WebSocket closed: ${code} - ${reason}`);
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
- const wsError = new WebSocketError(`WebSocket error: ${error.message}`);
58
- console.error(wsError);
59
- this.client.emit("error", wsError);
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
- console.log("Unhandled op:", message.op);
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
- console.warn(
189
- `Failed to fetch member for guild ${guild.id}:`,
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
- const err = new WebSocketError("Max reconnect attempts reached");
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
- console.log(`Reconnecting in ${delay}ms...`);
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;