@jnode/discord 1.0.18 → 2.0.1

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
@@ -1,189 +1,165 @@
1
- # JustDiscord
1
+ # `@jnode/discord`
2
2
 
3
3
  Simple Discord API package for Node.js.
4
4
 
5
- ```shell
6
- npm install @jnode/discord
7
- ```
5
+ **Note:** This package requires Node.js **v22.4.0** or later to use the built-in `WebSocket` for Discord gateway connections.
8
6
 
9
- ## Basic Usage
7
+ ## Installation
10
8
 
11
- ### Import JustDiscord
12
- ```js
13
- const discord = require('@jnode/discord');
14
9
  ```
10
+ npm i @jnode/discord
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ### Import
15
16
 
16
- ### Create a Client
17
17
  ```js
18
- const client = new discord.Client('BOT_TOKEN');
18
+ const { Client, Attachment } = require('@jnode/discord');
19
19
  ```
20
20
 
21
- ### Connect to Discord Gateway
21
+ ### Start a simple "Ping-Pong" bot
22
+
22
23
  ```js
23
- client.connectGateway((gateway) => {
24
- // receive Discord gateway "READY" event
25
- gateway.on('READY', (d) => {
26
- console.log('Connected to Discord.');
24
+ const client = new Client('YOUR_BOT_TOKEN');
25
+
26
+ // Initialize the gateway to receive events
27
+ const gateway = client.gateway({
28
+ intents: 3276799 // All intents (ensure they are enabled in dev portal)
29
+ });
30
+
31
+ // Listen for message events
32
+ gateway.on('MESSAGE_CREATE', async (message) => {
33
+ if (message.content === '!ping') {
34
+ // Send a reply using the REST client
35
+ await client.request('POST', `/channels/${message.channel_id}/messages`, {
36
+ content: 'Pong!'
27
37
  });
38
+ }
28
39
  });
29
40
  ```
30
41
 
31
- ## Class: `Client`
32
-
33
- The main class for interacting with the Discord API and Gateway.
42
+ ### Sending files (Attachments)
34
43
 
35
- ### Constructor
36
44
  ```js
37
- new discord.Client(token, options = {})
45
+ const fs = require('fs');
46
+
47
+ async function sendImage(channelId) {
48
+ const fileData = fs.readFileSync('./image.png');
49
+ const image = new Attachment('image.png', 'image/png', fileData);
50
+
51
+ await client.request('POST', `/channels/${channelId}/messages`,
52
+ { content: 'Here is your image!' },
53
+ [image] // Pass attachments as an array
54
+ );
55
+ }
38
56
  ```
39
- - `token`: Your Discord bot token.
40
- - `options`: An optional object for setting various client options:
41
- - `apiVersion`: The Discord API version. Default is `10`.
42
- - `apiBase`: The base URL of the Discord API. Default is `discord.com/api`.
43
- - `apiAutoRetry`: Whether to auto-retry requests when receiving a 429 error. Default is `true`.
44
- - `apiThrowError`: Whether to throw errors when the API status code is not 2xx. Default is `true`.
45
- - `gatewayIntents`: The gateway intents used for connecting to the Gateway. Default is `0b11111111111111111000110011`. You can find more about intents in the [Discord Developer Documentation](https://discord.com/developers/docs/events/gateway).
46
- - `gatewayUrl`: The base URL for the Discord Gateway. Default is `wss://gateway.discord.gg`.
47
- - `gatewayReconnectDelay`: The delay (in milliseconds) before attempting to reconnect to the gateway. Default is `5000`. Set to less than 0 to disable auto-reconnect.
48
- - `gatewayConnectTimeout`: The timeout (in milliseconds) before giving up on connecting to the gateway. Default is `5000`. Set to less than 0 to disable connect timeout.
49
- - `gatewayThrowError`: Whether to throw errors when the gateway encounters an issue. Default is `true`.
50
-
51
- ### Methods
52
-
53
- - `apiUrl(path)`: Returns the full API URL with the base, version, and given path.
54
- - `path`: API endpoint path. Example: `/channels/123456789`.
55
- - **Returns**: `string` - The full API URL. Example: `https://discord.com/api/v10/channels/123456789`.
56
-
57
- - `async apiRequest(method = 'GET', path = '/', body)`: Makes an HTTP request to the Discord API, find out more at [Discord Developer Documentation](https://discord.com/developers/docs).
58
- - `method`: HTTP method (e.g. `GET`, `POST`, `PUT`, `DELETE`). Default is `GET`.
59
- - `path`: API endpoint path. Default is `/`. Example: `/channels/123456789/messages`.
60
- - `body`: Request body data (will be stringified). Example: `{ content: 'Hello, Discord!' }`.
61
- - **Returns**: `Promise<RequestResponse>` - A promise that resolves to a `RequestResponse` object.
62
- - **Example**:
63
- ```js
64
- client.apiRequest('POST', '/channels/123456789/messages', { content: 'Hello, Discord!' })
65
- .then(res => {
66
- if (res.statusCode === 200) {
67
- console.log('Message sent successfully!');
68
- } else {
69
- console.error('Failed to send message:', res.statusCode, res.text());
70
- }
71
- }).catch(err => {
72
- console.error('Error sending message:', err);
73
- });
74
- ```
75
-
76
- - `async apiRequestMultipart(method = 'GET', path = '/', body, attachments = [])`: Makes a multipart HTTP request to the Discord API.
77
- - `method`: HTTP method (e.g. `GET`, `POST`, `PUT`, `DELETE`). Default is `GET`.
78
- - `path`: API endpoint path. Default is `/`. Example: `/channels/123456789/messages`.
79
- - `body`: Request body data (will be stringified). Example: `{ content: 'Hello, Discord!' }`.
80
- - `attachments`: An array of attachments, each attachment is an object:
81
- - `name`: Name of the file. Example: `image.png`
82
- - `type` (Optional): Content type of the data. Defaults to `application/octet-stream`.
83
- - `data` (Option 1): Data (string or Buffer) of this file.
84
- - `file` (Option 2): Path to a local file.
85
- - `encoded`/`base64` (Option 3): base64 encoded data string.
86
- - `stream` (Option 4): Any readable stream.
87
- - **Returns**: `Promise<RequestResponse>` - A promise that resolves to a `RequestResponse` object.
88
- - **Example**:
89
- ```js
90
- const fs = require('fs').promises;
91
-
92
- async function uploadFile(channelId, filePath) {
93
- const fileData = await fs.readFile(filePath);
94
- const fileType = 'image/png';
95
- const fileName = 'my_image.png';
96
-
97
- client.apiRequestMultipart('POST', `/channels/${channelId}/messages`, { content: 'Here\'s an image!' }, [{
98
- name: fileName,
99
- type: fileType,
100
- data: fileData
101
- }]).then(res => {
102
- if (res.statusCode === 200) {
103
- console.log('File uploaded successfully!');
104
- } else {
105
- console.error('Failed to upload file:', res.statusCode, res.text());
106
- }
107
- }).catch(err => {
108
- console.error('Error uploading file:', err);
109
- });
110
- }
111
- uploadFile('123456789', './my_image.png');
112
- ```
113
-
114
- - `async connectGateway(cb)`: Connects to the Discord Gateway.
115
- - `cb`: A callback function that takes a `gateway` object as an argument.
116
- - **Returns**: `Promise<Gateway>` - A promise that resolves to a `Gateway` object.
117
- - **Example**:
118
- ```js
119
- client.connectGateway((gateway) => {
120
- gateway.on('READY', (data) => {
121
- console.log('Connected to Discord, user id:', data.user.id);
122
- });
123
- gateway.on('MESSAGE_CREATE', (message) => {
124
- if (message.content === '!ping') {
125
- console.log('Recieve Ping Message')
126
- }
127
- });
128
- });
129
- ```
130
-
131
- ## Class: `Gateway`
132
-
133
- Manages the Discord Gateway connection for receiving real-time events. You can find more about gateway events in the [Discord Developer Documentation](https://discord.com/developers/docs/events/gateway).
134
-
135
- ### Events
136
-
137
- - `socketOpened`: Emitted when the WebSocket connection is opened.
138
- - `event`: A WebSocket event object.
139
- - `socketClosed`: Emitted when the WebSocket connection is closed.
140
- - `event`: A WebSocket event object.
141
- - `socketError`: Emitted when a WebSocket error occurs.
142
- - `event`: A WebSocket event object.
143
- - `socketMessage`: Emitted when a raw WebSocket message is received.
144
- - `event`: A WebSocket event object.
145
- - `message`: Emitted when a complete JSON payload is received.
146
- - `data`: A JSON object.
147
- - Discord Gateway Event (`READY`, `MESSAGE_CREATE`... etc.): The gateway will auto dispatch discord events. Check [Discord Developer Documentation](https://discord.com/developers/docs/events/gateway-events) for all avaliable events. The event callback will be a data object from discord.
148
-
149
- ### Methods
150
-
151
- - `async getGatewayUrl()`: Retrieves the gateway URL from the Discord API.
152
- - **Returns**: `Promise<string>` - A promise that resolves to the gateway URL.
153
-
154
- - `connect()`: Opens the WebSocket connection to the Discord Gateway.
155
-
156
- - `sendMessage(op, d = null)`: Sends a message to the Discord Gateway.
157
- - `op`: Opcode of the message. Check [Discord Developer Documentation](https://discord.com/developers/docs/events/gateway-events) for all avaliable opcodes.
158
- - `d`: Payload of the message.
159
- - **Example**:
160
- ```js
161
- gateway.sendMessage(1, {}); //send heartbeat
162
- gateway.sendMessage(2, { //Identify
163
- token: 'BOT_TOKEN',
164
- properties: {
165
- os: process.platform,
166
- browser: 'Node.js',
167
- device: 'JustDiscord'
168
- },
169
- intents: 0b11111111111111111000110011 //replace with your intents
170
- });
171
- ```
172
57
 
173
- ## Class: `DiscordAPIError`
58
+ ## How it works?
59
+
60
+ `@jnode/discord` provides a lightweight wrapper around the Discord REST API and WebSocket Gateway.
61
+
62
+ 1. **REST Client (`Client`)**: Handles all HTTP requests to Discord (messages, channel management, etc.). It includes built-in support for rate-limit auto-retries and multipart/form-data for file uploads.
63
+ 2. **Gateway (`Gateway`)**: An `EventEmitter` that maintains a WebSocket connection to Discord. it handles heartbeats, identifies your bot, and emits events when things happen on Discord (like a new message or a member joining).
64
+
65
+ We keep it simple: no heavy abstractions, just the tools you need to interact with the API directly.
66
+
67
+ ------
68
+
69
+ # Reference
70
+
71
+ ## Class: `discord.Client`
72
+
73
+ The main controller for interacting with the Discord REST API.
74
+
75
+ ### `new discord.Client(token[, options])`
76
+
77
+ - `token` [\<string\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) Your Discord Bot token.
78
+ - `options` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)
79
+ - `baseUrl` [\<string\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) The API root. **Default:** `'https://discord.com/api/v10'`.
80
+ - `autoRetry` [\<boolean\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#boolean_type) Whether to automatically wait and retry when hit by a **429** Rate Limit. **Default:** `true`.
81
+
82
+ ### `client.request(method, path[, body, attachments, options])`
83
+
84
+ - `method` [\<string\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) HTTP method (e.g., `'GET'`, `'POST'`, `'PATCH'`). **Default:** `'GET'`.
85
+ - `path` [\<string\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) The API endpoint path (e.g., `'/channels/123/messages'`).
86
+ - `body` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) The JSON payload to send.
87
+ - `attachments` [\<discord.Attachment[]\>](#class-discordattachment) An array of attachment objects for file uploads.
88
+ - `options` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)
89
+ - `auditLog` [\<string\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) Reason to be displayed in the Discord Audit Log.
90
+ - `headers` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) Additional HTTP headers.
91
+ - `requestOptions` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) Options passed to the underlying `@jnode/request`.
92
+ - Returns: [\<Promise\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) Fulfills with the parsed JSON response, or `null` if status is **204**.
93
+
94
+ ### `client.gateway([options])`
95
+
96
+ - `options` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) Options for the gateway (see [`discord.Gateway`](#class-discordgateway)).
97
+ - Returns: [\<discord.Gateway\>](#class-discordgateway) A new gateway instance.
98
+
99
+ ## Class: `discord.Gateway`
100
+
101
+ - Extends: [\<EventEmitter\>](https://nodejs.org/docs/latest/api/events.html#class-eventemitter)
102
+
103
+ Handles the WebSocket connection to receive real-time events.
104
+
105
+ ### `new discord.Gateway(client[, options])`
106
+
107
+ - `client` [\<discord.Client\>](#class-discordclient) An instance of the Discord client.
108
+ - `options` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)
109
+ - `intents` [\<number\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type) Gateway intents bitmask. **Default:** `3276799` (All non-privileged).
110
+ - `reconnectDelay` [\<number\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type) Delay (ms) before reconnecting after a close. **Default:** `5000`.
111
+ - `connectTimeout` [\<number\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type) Timeout (ms) for the initial connection. **Default:** `5000`.
112
+ - `apiVersion` [\<number\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type) Discord Gateway version. **Default:** `10`.
113
+ - `heartbeatJitter` [\<number\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type) Coefficient for heartbeat interval jitter. **Default:** `0.9`.
114
+ - `heartbeatAckTimeout` [\<number\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type) Timeout (ms) to wait for a heartbeat ACK before closing. **Default:** `3000`.
115
+ - `presence` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) Initial presence information.
116
+ - `shard` [\<number[]\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) Array of two integers `[shard_id, num_shards]`.
117
+
118
+ ### `gateway.send(op[, d])`
119
+
120
+ - `op` [\<number\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type) Gateway Opcode.
121
+ - `d` [\<any\>] Data payload.
122
+
123
+ Sends a raw event through the gateway.
124
+
125
+ ### `gateway.close()`
126
+
127
+ Closes the WebSocket connection.
128
+
129
+ ### Event: `'socketOpen'`
130
+
131
+ Emitted when the WebSocket connection is established.
132
+
133
+ ### Event: `'socketClose'`
134
+
135
+ - `event` [\<CloseEvent\>]
136
+
137
+ Emitted when the WebSocket connection is closed.
138
+
139
+ ### Event: `'message'`
140
+
141
+ - `data` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)
142
+
143
+ Emitted for every raw message received from the gateway.
144
+
145
+ ### Event: `<DISPATCH_EVENT_NAME>`
146
+
147
+ - `data` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)
148
+
149
+ Emitted when a specific Discord Dispatch event is received (Opcode 0). E.G., `'READY'`, `'MESSAGE_CREATE'`, `'GUILD_CREATE'`.
150
+
151
+ ### Event: `'error'`
152
+
153
+ - `err` [\<Error\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
174
154
 
175
- An error that is thrown when the Discord API returns a non-2xx status code.
155
+ Emitted when a critical gateway error occurs (e.g., invalid token).
176
156
 
177
- ### Properties
157
+ ## Class: `discord.Attachment`
178
158
 
179
- - `message`: The error message.
180
- - `body`: The response body.
181
- - `headers`: The response headers.
159
+ Represents a file to be uploaded.
182
160
 
183
- ## Helper functions
161
+ ### `new discord.Attachment(filename, type, body)`
184
162
 
185
- - `RequestResponse` class with properties like:
186
- - `statusCode`: Status code.
187
- - `headers`: Response headers.
188
- - `text(encoding)`: Return response body in string, with optional encoding. Example: `res.text('utf-8')`.
189
- - `json()`: Return response body in JSON format, or `undefined` when cannot parse JSON. Example: `res.json()`.
163
+ - `filename` [\<string\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) The name of the file (e.g., `'photo.jpg'`).
164
+ - `type` [\<string\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) The MIME type (e.g., `'image/jpeg'`).
165
+ - `body` [\<Buffer\>](https://nodejs.org/docs/latest/api/buffer.html#class-buffer) | [\<string\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) The actual file content.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jnode/discord",
3
- "version": "1.0.18",
3
+ "version": "2.0.1",
4
4
  "description": "Simple Discord API package for Node.js.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -25,6 +25,6 @@
25
25
  "node": ">=22.4.0"
26
26
  },
27
27
  "dependencies": {
28
- "@jnode/request": ">=1.1.3 <2.0.0"
28
+ "@jnode/request": "^2.1.3"
29
29
  }
30
30
  }
@@ -0,0 +1,21 @@
1
+ /*
2
+ @jnode/discord/attachment.js
3
+ v2
4
+
5
+ Simple Discord API package for Node.js.
6
+ Must use Node.js v22.4.0 or later for WebSocket (Discord gateway).
7
+
8
+ by JustApple
9
+ */
10
+
11
+ // attachment class
12
+ class Attachment {
13
+ constructor(filename, type, body) {
14
+ this.filename = filename;
15
+ this.type = type;
16
+ this.body = body;
17
+ }
18
+ }
19
+
20
+ // export
21
+ module.exports = Attachment;
package/src/client.js CHANGED
@@ -1,125 +1,85 @@
1
1
  /*
2
- JustDiscord/client.js
3
- v.1.0.0
2
+ @jnode/discord/client.js
3
+ v2
4
4
 
5
5
  Simple Discord API package for Node.js.
6
6
  Must use Node.js v22.4.0 or later for WebSocket (Discord gateway).
7
7
 
8
- by JustNode Dev Team / JustApple
8
+ by JustApple
9
9
  */
10
10
 
11
- //load jnode packages
12
- const request = require('@jnode/request');
13
-
14
- //load classes and functions
11
+ // dependencies
12
+ const { request, FormPart } = require('@jnode/request');
15
13
  const DiscordGateway = require('./gateway.js');
16
14
 
17
- //error types
18
- //errors from Discord API
15
+ // errors from Discord API
19
16
  class DiscordAPIError extends Error {
20
- constructor(code, body, headers) {
21
- super(`Discord API respond with code ${code}.`);
22
- this.body = body;
23
- this.headers = headers;
17
+ constructor(res) {
18
+ super(`Discord API respond with code ${res.statusCode}.`);
19
+ this.res = res;
24
20
  }
25
21
  }
26
22
 
27
- //Discord Client, everything starts here
23
+ // Discord client
28
24
  class DiscordClient {
29
25
  constructor(token, options = {}) {
30
26
  this.token = token;
31
27
 
32
- //client api options
33
- this.apiVersion = options.apiVersion ?? 10;
34
- this.apiBase = options.apiBase ?? 'discord.com/api';
35
- this.apiAutoRetry = options.apiAutoRetry ?? true;
36
- this.apiThrowError = options.apiThrowError ?? true; //throw error while api status code is not 2xx
37
-
38
- //client gateway options
39
- this.gatewayIntents = options.gatewayIntents ?? 0b11001100011111111111111111;
40
- this.gatewayUrl = options.gatewayUrl ?? 'wss://gateway.discord.gg';
41
- this.gatewayOriginalUrl = this.gatewayUrl;
42
- this.gatewayReconnectDelay = options.gatewayReconnectDelay ?? 5000; //less than 0 to disable auto reconnect
43
- this.gatewayConnectTimeout = options.gatewayConnectTimeout ?? 5000; //less than 0 to disable connect timeout (may cause error)
44
- this.gatewayThrowError = options.gatewayThrowError ?? true; //throw error while gateway went something wrong
45
-
46
- //gateway
47
- this.gateway = new DiscordGateway(this);
28
+ // client api options
29
+ this.baseUrl = options.baseUrl ?? 'https://discord.com/api/v10';
30
+ this.autoRetry = options.autoRetry ?? true;
48
31
  }
49
32
 
50
- //get full api url with base, version and path
51
- apiUrl(path) {
52
- return `https://${this.apiBase}/v${this.apiVersion}${path}`;
53
- }
33
+ // make request
34
+ async request(method = 'GET', path = '/', body, attachments = [], options = {}) {
35
+ // generate body
36
+ let reqBody;
37
+ if (!attachments?.length) {
38
+ reqBody = JSON.stringify(body);
39
+ } else {
40
+ reqBody = [new FormPart('payload_json', JSON.stringify(body), { 'Content-Type': 'application/json' })];
41
+
42
+ // add every attachment
43
+ for (let i in attachments) {
44
+ attachments[i].name = attachments[i].name ?? `files[${i}]`;
45
+ reqBody.push(attachments[i]);
46
+ }
47
+ }
54
48
 
55
- //make an request to Discord
56
- async apiRequest(method = 'GET', path = '/', body) {
57
- const res = await request.request(method, this.apiUrl(path), {
49
+ // make request
50
+ const res = await request(method, this.baseUrl + path, reqBody, {
58
51
  'Authorization': `Bot ${this.token}`,
59
52
  'User-Agent': 'DiscordBot',
60
- 'Content-Type': (body !== undefined) ? 'application/json' : null
61
- }, (body !== undefined) ? JSON.stringify(body) : undefined); //make an request
62
-
63
- if ((res.statusCode === 429) && this.apiAutoRetry) { //retry if recieved 429
64
- await delay(res.json().retry_after);
65
- return this.apiRequest(method, path, body);
66
- }
67
-
68
- if (((res.statusCode > 299) || (res.statusCode < 200)) && this.apiThrowError) { //throw error if not 2xx
69
- throw new DiscordAPIError(res.statusCode, res.json() ?? res.text(), res.headers);
53
+ 'Content-Type': (body !== undefined) ? 'application/json' : null,
54
+ 'X-Audit-Log-Reason': options.auditLog ? encodeURIComponent(options.auditLog) : null,
55
+ ...options.headers
56
+ }, options.requestOptions);
57
+
58
+ // retry if recieved 429
59
+ if ((res.statusCode === 429) && this.autoRetry) {
60
+ await delay((await res.json()).retry_after * 1000);
61
+ return this.request(method, path, body, attachments, options);
70
62
  }
71
63
 
72
- return res;
73
- }
64
+ // throw error if not 2xx
65
+ if ((res.statusCode > 299) || (res.statusCode < 200)) throw new DiscordAPIError(res);
74
66
 
75
- //make an multi part request to Discord
76
- async apiRequestMultipart(method = 'GET', path = '/', body, attachments = []) {
77
- let parts = [];
78
- parts.push({ //json data
79
- disposition: 'form-data; name="payload_json"',
80
- type: 'application/json',
81
- data: JSON.stringify(body)
82
- });
83
-
84
- for (let i = 0; i < attachments.length; i++) { //add every attachment
85
- parts.push({
86
- disposition: `form-data; name="files[${i}]"; filename="${encodeURIComponent(attachments[i].name)}"`,
87
- type: attachments[i].type,
88
- data: attachments[i].data,
89
- base64: attachments[i].encoded ?? attachments[i].base64,
90
- stream: attachments[i].stream,
91
- file: attachments[i].file
92
- });
93
- }
94
-
95
- const res = await request.multipartRequest(method, this.apiUrl(path), {
96
- 'Authorization': `Bot ${this.token}`,
97
- 'User-Agent': 'DiscordBot'
98
- }, parts); //make an request
99
-
100
- if ((res.statusCode === 429) && this.apiAutoRetry) { //retry if recieved 429
101
- await delay(res.json().retry_after);
102
- return this.apiRequestMultipart(method, path, body, attachments);
103
- }
104
-
105
- if (((res.statusCode > 299) || (res.statusCode < 200)) && this.apiThrowError) { //throw error if not 2xx
106
- throw new DiscordAPIError(res.statusCode, res.json() ?? res.text(), res.headers);
67
+ try {
68
+ return (res.statusCode === 204) ? null : await res.json();
69
+ } catch {
70
+ return await res.text();
107
71
  }
108
-
109
- return res;
110
72
  }
111
73
 
112
- async connectGateway(cb) {
113
- await this.gateway.getGatewayUrl();
114
- this.gateway.connect();
115
- if (cb) cb(this.gateway);
116
- return this.gateway;
74
+ gateway(options) {
75
+ return new DiscordGateway(this, options);
117
76
  }
118
77
  }
119
78
 
120
- //wait time (ms)
79
+ // wait time (ms)
121
80
  function delay(ms) {
122
81
  return new Promise((resolve, reject) => { setTimeout(resolve, ms); });
123
82
  }
124
83
 
84
+ // export
125
85
  module.exports = DiscordClient;
package/src/gateway.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
- JustDiscord/gateway.js
3
- v.1.0.0
2
+ @jnode/discord/gateway.js
3
+ v2
4
4
 
5
5
  Simple Discord API package for Node.js.
6
6
  Must use Node.js v22.4.0 or later for WebSocket (Discord gateway).
@@ -8,144 +8,176 @@ Must use Node.js v22.4.0 or later for WebSocket (Discord gateway).
8
8
  by JustNode Dev Team / JustApple
9
9
  */
10
10
 
11
- //load classes and functions
12
- //errors from Discord Gateway
13
- class DiscordGatewayError extends Error { constructor(...i) { super(...i); } }
14
-
15
- //load node packages
11
+ // dependencies
16
12
  const EventEmitter = require('events');
17
13
 
18
- //Discord Gateway, recieve live messages with WebSocket
14
+ // Discord gateway, recieve live events with WebSocket
19
15
  class DiscordGateway extends EventEmitter {
20
- constructor(client) {
16
+ constructor(client, options = {}) {
21
17
  super();
22
-
18
+
23
19
  this.client = client;
24
- this.socket = {};
25
- this.s = null;
26
- }
27
-
28
- //get gateway url from api
29
- async getGatewayUrl() {
30
- const res = await this.client.apiRequest('GET', '/gateway/bot'); //make an api request
31
- this.client.gatewayUrl = res.json().url;
32
- this.client.gatewayOriginalUrl = res.json().url;
33
- return res.json().url;
20
+
21
+ // gateway options
22
+ this.intents = options.intents ?? 0b11001100011111111111111111;
23
+ this.reconnectDelay = options.reconnectDelay ?? 5000;
24
+ this.connectTimeout = options.connectTimeout ?? 5000;
25
+ this.apiVersion = options.apiVersion ?? 10;
26
+ this.heartbeatJitter = options.heartbeatJitter ?? 0.9;
27
+ this.heartbeatAckTimeout = options.heartbeatAckTimeout ?? 3000;
28
+ this.shard = options.shard;
29
+ this.largeThreshold = options.largeThreshold;
30
+ this.presence = options.presence;
31
+
32
+ this._s = null;
33
+
34
+ // connect anyways
35
+ this.connect();
34
36
  }
35
-
36
- //connect to gateway
37
- connect() {
38
- if ((this.socket.readyState !== 3) && this.socket.close) this.socket.close(); //close privous connect
39
-
40
- this.socket = new WebSocket(`${this.client.gatewayUrl}?v=${this.client.apiVersion}&encoding=json`); //connect
41
-
42
- if (this.client.gatewayConnectTimeout !== 0) { //set connect timeout
43
- this.connectTimeout = setTimeout(() => {
44
- this.socket.close(); //close socket
37
+
38
+ // connect to gateway
39
+ async connect() {
40
+ // get gateway url
41
+ const gatewayUrl = this._resumeUrl ?? this.client._gatewayUrl ?? (this.client._gatewayUrl = (await this.client.request('GET', '/gateway/bot')).url);
42
+
43
+ // create connection
44
+ this.socket = new WebSocket(`${gatewayUrl}?v=${this.apiVersion}&encoding=json`);
45
+
46
+ // connect timeout
47
+ if (this.connectTimeout > 0) {
48
+ clearTimeout(this._connectTimeout);
49
+ this._connectTimeout = setTimeout(() => {
50
+ this.socket.close();
45
51
  this.emit('timeout');
46
-
47
- if (this.client.gatewayReconnectDelay >= 0) { //if auto reconnect is allowed
48
- setTimeout(() => {
49
- this.connect();
50
- }, this.client.gatewayReconnectDelay); //reconnect after specific time
51
- }
52
- }, this.client.gatewayConnectTimeout);
52
+ }, this.connectTimeout);
53
53
  }
54
-
55
- this.socket.addEventListener('open', (event) => { //socket opened
56
- this.emit('socketOpened', event); //send event with socket event
57
- clearTimeout(this.connectTimeout); //clear timeout if successfully connected
54
+
55
+ // socket open
56
+ this.socket.addEventListener('open', (event) => {
57
+ this.emit('socketOpen', event);
58
58
  });
59
-
60
- this.socket.addEventListener('close', (event) => { //socket closed
61
- this.emit('socketClosed', event); //send event with event (may have close code and reason)
62
- clearInterval(this.heartbeatInterval); //clear heatbeat interval
63
-
64
-
65
- if (this.client.gatewayReconnectDelay >= 0) { //if auto reconnect is allowed
59
+
60
+ // socket close
61
+ this.socket.addEventListener('close', (event) => {
62
+ this.emit('socketClose', event);
63
+ clearTimeout(this._connectTimeout);
64
+ clearInterval(this._heartbeatInterval);
65
+ clearTimeout(this._heartbeatTimeout);
66
+
67
+ // error close
68
+ if ([4004, 4010, 4011, 4012, 4013, 4014].includes(event.code)) {
69
+ const err = new Error(event.reason);
70
+ err.code = event.code;
71
+ this.emit('error', err);
72
+ return;
73
+ }
74
+
75
+ // reconnect
76
+ if (this.reconnectDelay >= 0) {
66
77
  setTimeout(() => {
67
78
  this.connect();
68
- }, this.client.gatewayReconnectDelay); //reconnect after specific time
79
+ }, this.reconnectDelay);
69
80
  }
70
81
  });
71
-
72
- this.socket.addEventListener('error', (event) => { //socket error
73
- this.emit('socketError', event); //send event with event (may have error info)
82
+
83
+ // error
84
+ this.socket.addEventListener('error', (event) => {
85
+ this.emit('socketError', event);
74
86
  });
75
-
76
- this.socket.addEventListener('message', (event) => { //recieve gateway message
77
- try {
78
- this.emit('socketMessage', event); //send event with event (may have data and more)
79
-
80
- const data = JSON.parse(event.data); //parse json data
81
- this.emit('message', data); //send event with json data
82
-
83
- //auto handle by op
84
- switch (data.op) {
85
- case 0: //Dispatch
86
- this.emit(data.t, data.d); //send t event with d
87
- this.s = data.s ?? this.s; //save s number there is
88
-
89
- //auto handle by t
90
- switch (data.t) {
91
- case 'READY': //save resume data
92
- this.sessionId = data.d['session_id'];
93
- this.client.gatewayUrl = data.d['resume_gateway_url'];
94
- break;
95
- default:
96
- break;
97
- }
98
- break;
99
- case 7: //Reconnect, need to reconnect
100
- this.socket.close(); //close socket
101
- break;
102
- case 9: //Invalid Session, need to reconnect
103
- this.client.gatewayUrl = this.client.gatewayOriginalUrl; //reset url
104
- this.sessionId = undefined; //reset session id
105
- this.socket.close(); //close socket
106
- break;
107
- case 10: //Hello
108
- this.heartbeatIntervalMS = data.d['heartbeat_interval'] * 0.9; //save heart beat interval time
109
- this.sendMessage(1); //send first heartbeat
110
-
111
- clearInterval(this.heartbeatInterval); //clear old interval
112
- this.heartbeatInterval = setInterval(() => { //start heartbeat interval
113
- this.sendMessage(1);
114
- }, this.heartbeatIntervalMS);
115
-
116
- if (this.sessionId) { //may be resumed
117
- this.sendMessage(6, { //Resume
118
- token: this.client.token,
119
- session_id: this.sessionId,
120
- seq: this.s
121
- });
122
- } else { //start new session
123
- this.sendMessage(2, { //Identify
124
- token: this.client.token,
125
- properties: {
126
- os: process.platform,
127
- browser: 'Node.js',
128
- device: 'JustDiscord'
129
- },
130
- intents: this.client.gatewayIntents
131
- });
132
- }
133
- break;
134
- default:
135
- break;
136
- }
137
- } catch (err) {
138
- if (this.client.gatewayThrowError) { //throw gateway error
139
- err.name = 'DiscordGatewayError'; //change the name of the error
140
- throw err;
141
- }
87
+
88
+ // message
89
+ this.socket.addEventListener('message', (event) => {
90
+ this.emit('socketMessage', event);
91
+
92
+ const data = JSON.parse(event.data);
93
+ this.emit('message', data);
94
+
95
+ // handle by op
96
+ switch (data.op) {
97
+ case 0: // dispatch
98
+ this.emit(data.t, data.d);
99
+ this._s = data.s ?? this._s;
100
+
101
+ // ready event
102
+ if (data.t === 'READY') {
103
+ this._sessionId = data.d.session_id;
104
+ this._resumeUrl = data.d.resume_gateway_url;
105
+ }
106
+ break;
107
+ case 7: // reconnect
108
+ this.socket.close();
109
+
110
+ break;
111
+ case 9: // invaild session
112
+ if (!data.d) { // start new connection
113
+ this._resumeUrl = null;
114
+ this._sessionId = null;
115
+ this._s = null;
116
+ }
117
+ this.socket.close();
118
+ break;
119
+ case 10: // hello
120
+ clearTimeout(this._connectTimeout);
121
+
122
+ this.heartbeat();
123
+
124
+ // start heatbeat interval
125
+ clearInterval(this._heartbeatInterval);
126
+ this._heartbeatInterval = setInterval(() => {
127
+ this.heartbeat();
128
+ }, data.d.heartbeat_interval * this.heartbeatJitter);
129
+
130
+ // start identify or resume
131
+ if (this._sessionId && this._resumeUrl) { // resume
132
+ this.send(6, {
133
+ token: this.client.token,
134
+ session_id: this._sessionId,
135
+ seq: this._s
136
+ });
137
+ } else { // identify
138
+ this.send(2, {
139
+ token: this.client.token,
140
+ intents: this.intents,
141
+ properties: {
142
+ os: process.platform,
143
+ browser: 'jnode_discord',
144
+ device: 'jnode_discord'
145
+ },
146
+ presence: this.presence,
147
+ shard: this.shard,
148
+ large_threshold: this.largeThreshold
149
+ });
150
+ }
151
+
152
+ break;
153
+ case 11: // heartbeat ack
154
+ clearTimeout(this._heartbeatTimeout);
155
+ break;
142
156
  }
143
157
  });
144
158
  }
145
-
146
- sendMessage(op, d = null) {
159
+
160
+ heartbeat() {
161
+ this.send(1, this._s);
162
+
163
+ // could not receive heartbeat ack
164
+ clearTimeout(this._heartbeatTimeout);
165
+ this._heartbeatTimeout = setTimeout(() => {
166
+ this.emit('heartbeatTimeout');
167
+ this.socket.close();
168
+ }, this.heartbeatAckTimeout);
169
+ }
170
+
171
+ // sends a gateway event
172
+ send(op, d = null) {
147
173
  this.socket.send(JSON.stringify({ op, d }));
148
174
  }
175
+
176
+ // closes the gateway
177
+ close() {
178
+ this.socket.close();
179
+ }
149
180
  }
150
181
 
182
+ // export
151
183
  module.exports = DiscordGateway;
package/src/index.js CHANGED
@@ -1,14 +1,16 @@
1
1
  /*
2
- JustDiscord/index.js
3
- v.1.0.0
2
+ @jnode/discord/index.js
3
+ v2
4
4
 
5
5
  Simple Discord API package for Node.js.
6
6
  Must use Node.js v22.4.0 or later for WebSocket (Discord gateway).
7
7
 
8
- by JustNode Dev Team / JustApple
8
+ by JustApple
9
9
  */
10
10
 
11
+ // export
11
12
  module.exports = {
13
+ Attachment: require('./attachment.js'),
12
14
  Client: require('./client.js'),
13
15
  Gateway: require('./gateway.js')
14
16
  };