@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 +141 -165
- package/package.json +2 -2
- package/src/attachment.js +21 -0
- package/src/client.js +49 -89
- package/src/gateway.js +153 -121
- package/src/index.js +5 -3
package/README.md
CHANGED
|
@@ -1,189 +1,165 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@jnode/discord`
|
|
2
2
|
|
|
3
3
|
Simple Discord API package for Node.js.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
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
|
|
18
|
+
const { Client, Attachment } = require('@jnode/discord');
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
###
|
|
21
|
+
### Start a simple "Ping-Pong" bot
|
|
22
|
+
|
|
22
23
|
```js
|
|
23
|
-
client
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
155
|
+
Emitted when a critical gateway error occurs (e.g., invalid token).
|
|
176
156
|
|
|
177
|
-
|
|
157
|
+
## Class: `discord.Attachment`
|
|
178
158
|
|
|
179
|
-
|
|
180
|
-
- `body`: The response body.
|
|
181
|
-
- `headers`: The response headers.
|
|
159
|
+
Represents a file to be uploaded.
|
|
182
160
|
|
|
183
|
-
|
|
161
|
+
### `new discord.Attachment(filename, type, body)`
|
|
184
162
|
|
|
185
|
-
- `
|
|
186
|
-
|
|
187
|
-
|
|
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": "
|
|
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": "
|
|
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
|
-
|
|
3
|
-
|
|
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
|
|
8
|
+
by JustApple
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
//
|
|
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
|
-
//
|
|
18
|
-
//errors from Discord API
|
|
15
|
+
// errors from Discord API
|
|
19
16
|
class DiscordAPIError extends Error {
|
|
20
|
-
constructor(
|
|
21
|
-
super(`Discord API respond with code ${
|
|
22
|
-
this.
|
|
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
|
|
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.
|
|
34
|
-
this.
|
|
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
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
73
|
-
|
|
64
|
+
// throw error if not 2xx
|
|
65
|
+
if ((res.statusCode > 299) || (res.statusCode < 200)) throw new DiscordAPIError(res);
|
|
74
66
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
33
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
|
|
55
|
+
// socket open
|
|
56
|
+
this.socket.addEventListener('open', (event) => {
|
|
57
|
+
this.emit('socketOpen', event);
|
|
58
58
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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.
|
|
79
|
+
}, this.reconnectDelay);
|
|
69
80
|
}
|
|
70
81
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
|
|
83
|
+
// error
|
|
84
|
+
this.socket.addEventListener('error', (event) => {
|
|
85
|
+
this.emit('socketError', event);
|
|
74
86
|
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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
|
|
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
|
};
|