@jnode/discord 1.0.0 → 1.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/package.json +1 -1
- package/src/client.js +124 -0
- package/src/gateway.js +151 -0
- package/src/index.js +8 -2
package/package.json
CHANGED
package/src/client.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*
|
|
2
|
+
JustDiscord/client.js
|
|
3
|
+
v.1.0.0
|
|
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 JustNode Dev Team / JustApple
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
//load jnode packages
|
|
12
|
+
const request = require('@jnode/request');
|
|
13
|
+
|
|
14
|
+
//load classes and functions
|
|
15
|
+
const DiscordGateway = require('./gateway.js');
|
|
16
|
+
const error = require('./error.js');
|
|
17
|
+
|
|
18
|
+
//error types
|
|
19
|
+
//errors from Discord API
|
|
20
|
+
class DiscordAPIError extends Error {
|
|
21
|
+
constructor(code, body, headers) {
|
|
22
|
+
super(`Discord API respond with code ${code}.`);
|
|
23
|
+
this.body = body;
|
|
24
|
+
this.headers = headers;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//Discord Client, everything starts here
|
|
29
|
+
class DiscordClient {
|
|
30
|
+
constructor(token, options = {}) {
|
|
31
|
+
this.token = token;
|
|
32
|
+
|
|
33
|
+
//client api options
|
|
34
|
+
this.apiVersion = options.apiVersion ?? 10;
|
|
35
|
+
this.apiBase = options.apiBase ?? 'discord.com/api';
|
|
36
|
+
this.apiAutoRetry = options.apiAutoRetry ?? true;
|
|
37
|
+
this.apiThrowError = options.apiThrowError ?? true; //throw error while api status code is not 2xx
|
|
38
|
+
|
|
39
|
+
//client gateway options
|
|
40
|
+
this.gatewayIntents = options.gatewayIntents ?? 0b11111111111111111000110011;
|
|
41
|
+
this.gatewayUrl = options.gatewayUrl ?? 'wss://gateway.discord.gg';
|
|
42
|
+
this.gatewayOriginalUrl = this.gatewayUrl;
|
|
43
|
+
this.gatewayReconnectDelay = options.gatewayReconnectDelay ?? 5000; //less than 0 to disable auto reconnect
|
|
44
|
+
this.gatewayConnectTimeout = options.gatewayConnectTimeout ?? 5000; //less than 0 to disable connect timeout (may cause error)
|
|
45
|
+
this.gatewayThrowError = options.gatewayThrowError ?? true; //throw error while gateway went something wrong
|
|
46
|
+
|
|
47
|
+
//gateway
|
|
48
|
+
this.gateway = new DiscordGateway(this);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//get full api url with base, version and path
|
|
52
|
+
apiUrl(path) {
|
|
53
|
+
return `https://${this.apiBase}/v${this.apiVersion}${path}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//make an request to Discord
|
|
57
|
+
async apiRequest(method = 'GET', path = '/', body) {
|
|
58
|
+
const res = await request.request(method, this.apiUrl(path), {
|
|
59
|
+
'Authorization': `Bot ${this.token}`,
|
|
60
|
+
'User-Agent': 'DiscordBot',
|
|
61
|
+
'Content-Type': (body !== undefined) ? 'application/json' : null
|
|
62
|
+
}, (body !== undefined) ? JSON.stringify(body) : undefined); //make an request
|
|
63
|
+
|
|
64
|
+
if ((res.code === 429) && this.apiAutoRetry) { //retry if recieved 429
|
|
65
|
+
await delay(res.json.retry_after);
|
|
66
|
+
return this.apiRequest(method, path, body);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (((res.code > 299) || (res.code < 200)) && this.apiThrowError) { //throw error if not 2xx
|
|
70
|
+
throw new DiscordAPIError(res.code, res.json ?? res.body, res.headers);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return res;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//make an multi part request to Discord
|
|
77
|
+
async apiRequestMultipart(method = 'GET', path = '/', body, attachments = []) {
|
|
78
|
+
let parts = [];
|
|
79
|
+
parts.push({ //json data
|
|
80
|
+
disposition: 'name="payload_json"',
|
|
81
|
+
contentType: 'application/json',
|
|
82
|
+
data: JSON.stringify(body)
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < attachments.length; i++) { //add every attachment
|
|
86
|
+
parts.push({
|
|
87
|
+
disposition: `name="files[${i}]"; filename="${encodeURIComponent(attachments[i].name)}"`,
|
|
88
|
+
contentType: attachments[i].type,
|
|
89
|
+
data: attachments[i].data,
|
|
90
|
+
encoded: attachments[i].encoded
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const res = await request(method, this.apiUrl(path), {
|
|
95
|
+
'Authorization': `Bot ${this.token}`,
|
|
96
|
+
'User-Agent': 'DiscordBot',
|
|
97
|
+
'Content-Type': 'multipart/form-data; boundary=----JustDiscordFormBoundary'
|
|
98
|
+
}, request.generateMultipartBody(parts)); //make an request
|
|
99
|
+
|
|
100
|
+
if ((res.code === 429) && this.apiAutoRetry) { //retry if recieved 429
|
|
101
|
+
await delay(res.json.retry_after);
|
|
102
|
+
return this.apiRequest(method, path, body, multipart, true);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (((res.code > 299) || (res.code < 200)) && this.apiThrowError) { //throw error if not 2xx
|
|
106
|
+
throw new DiscordAPIError(res.code, res.json ?? res.body, res.headers);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return res;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async connectGateway() {
|
|
113
|
+
await this.gateway.getGatewayUrl();
|
|
114
|
+
this.gateway.connect();
|
|
115
|
+
return this.gateway;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//wait time (ms)
|
|
120
|
+
function delay(ms) {
|
|
121
|
+
return new Promise((resolve, reject) => { setTimeout(resolve, ms); });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = DiscordClient;
|
package/src/gateway.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/*
|
|
2
|
+
JustDiscord/gateway.js
|
|
3
|
+
v.1.0.0
|
|
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 JustNode Dev Team / JustApple
|
|
9
|
+
*/
|
|
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
|
|
16
|
+
const EventEmitter = require('events');
|
|
17
|
+
|
|
18
|
+
//Discord Gateway, recieve live messages with WebSocket
|
|
19
|
+
class DiscordGateway extends EventEmitter {
|
|
20
|
+
constructor(client) {
|
|
21
|
+
super();
|
|
22
|
+
|
|
23
|
+
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;
|
|
34
|
+
}
|
|
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
|
|
45
|
+
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);
|
|
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
|
|
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
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
this.connect();
|
|
68
|
+
}, this.client.gatewayReconnectDelay); //reconnect after specific time
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.socket.addEventListener('error', (event) => { //socket error
|
|
73
|
+
this.emit('socketError', event); //send event with event (may have error info)
|
|
74
|
+
});
|
|
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
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
sendMessage(op, d = null) {
|
|
147
|
+
this.socket.send(JSON.stringify({ op, d }));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = DiscordGateway;
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
JustDiscord
|
|
2
|
+
JustDiscord/index.js
|
|
3
3
|
v.1.0.0
|
|
4
4
|
|
|
5
5
|
Simple Discord API package for Node.js.
|
|
@@ -8,4 +8,10 @@ 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
|
-
|
|
11
|
+
//load node packages
|
|
12
|
+
const EventEmitter = require('events');
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
client: require('./client.js'),
|
|
16
|
+
gateway: require('./gateway.js')
|
|
17
|
+
};
|