guilds.js 0.0.0 → 0.0.2
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/LICENSE +1 -1
- package/README.md +52 -0
- package/dist/index.cjs +238 -0
- package/dist/index.d.cts +106 -0
- package/dist/index.d.mts +106 -0
- package/dist/index.mjs +228 -0
- package/package.json +65 -1
- package/public/logo.png +0 -0
package/LICENSE
CHANGED
|
@@ -175,7 +175,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
|
175
175
|
|
|
176
176
|
END OF TERMS AND CONDITIONS
|
|
177
177
|
|
|
178
|
-
Copyright 2026 Andrew (
|
|
178
|
+
Copyright 2026 Andrew (andrewdku / wlix)
|
|
179
179
|
|
|
180
180
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
181
|
you may not use this file except in compliance with the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="public/logo.png" alt="guilds.js Logo" width="85" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<div align="center">
|
|
6
|
+
<a href="https://github.com/andrewdku/guilds.js">GitHub</a> |
|
|
7
|
+
<a href="https://npmjs.com/package/guilds.js">npm</a>
|
|
8
|
+
</div>
|
|
9
|
+
<h1 align="center">guilds.js</h1>
|
|
10
|
+
|
|
11
|
+
> Interact with Discord's API with ease
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install guilds.js
|
|
17
|
+
# or
|
|
18
|
+
yarn add guilds.js
|
|
19
|
+
# or
|
|
20
|
+
pnpm add guilds.js
|
|
21
|
+
# or
|
|
22
|
+
bun add guilds.js
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Example
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { Client } from "guilds.js";
|
|
29
|
+
|
|
30
|
+
const client = new Client({
|
|
31
|
+
// https://discord.com/developers/applications
|
|
32
|
+
token: "bot token",
|
|
33
|
+
|
|
34
|
+
// client intents (as a number)
|
|
35
|
+
intents: 0,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
client.once("ready", () => {
|
|
39
|
+
console.log("Client is ready!");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// start connection to Discord gateway
|
|
43
|
+
client.connect();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
# Contributing
|
|
47
|
+
|
|
48
|
+
[pnpm](https://pnpm.io) is used throughout this project for packages and scripts. Pull requests are always welcome. For major changes, please open an issue to discuss what you wish to change.
|
|
49
|
+
|
|
50
|
+
# License
|
|
51
|
+
|
|
52
|
+
guilds.js is licensed under the [Apache 2.0 license](LICENSE).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
//#region src/utils/constants.ts
|
|
4
|
+
const activityTypes = {
|
|
5
|
+
Competing: 5,
|
|
6
|
+
Custom: 4,
|
|
7
|
+
Listening: 2,
|
|
8
|
+
Playing: 0,
|
|
9
|
+
Streaming: 1,
|
|
10
|
+
Watching: 3
|
|
11
|
+
};
|
|
12
|
+
const baseApiUrl = "https://discord.com/api/v10";
|
|
13
|
+
const errorCodes = [
|
|
14
|
+
"ClientIntentsError",
|
|
15
|
+
"ClientPropsError",
|
|
16
|
+
"ClientTokenError",
|
|
17
|
+
"DiscordAPIError",
|
|
18
|
+
"GatewayError",
|
|
19
|
+
"WebSocketError"
|
|
20
|
+
];
|
|
21
|
+
const opCodes = {
|
|
22
|
+
Dispatch: 0,
|
|
23
|
+
Heartbeat: 1,
|
|
24
|
+
HeartbeatACK: 11,
|
|
25
|
+
Hello: 10,
|
|
26
|
+
Identify: 2,
|
|
27
|
+
InvalidSession: 9,
|
|
28
|
+
PresenceUpdate: 3,
|
|
29
|
+
Reconnect: 7,
|
|
30
|
+
RequestGuildMembers: 8,
|
|
31
|
+
RequestSoundboardSounds: 12,
|
|
32
|
+
Resume: 6,
|
|
33
|
+
VoiceStateUpdate: 3
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/classes/EventHandler.ts
|
|
38
|
+
var EventHandler = class {
|
|
39
|
+
#listeners = {};
|
|
40
|
+
on(event, listener) {
|
|
41
|
+
if (!this.#listeners[event]) this.#listeners[event] = [];
|
|
42
|
+
this.#listeners[event].push(listener);
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
once(event, listener) {
|
|
46
|
+
const wrapped = (...args) => {
|
|
47
|
+
listener(...args);
|
|
48
|
+
this.off(event, wrapped);
|
|
49
|
+
};
|
|
50
|
+
this.on(event, wrapped);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
off(event, listener) {
|
|
54
|
+
if (!this.#listeners[event]) return this;
|
|
55
|
+
this.#listeners[event] = this.#listeners[event].filter((l) => l !== listener);
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
async emit(event, ...args) {
|
|
59
|
+
if (!this.#listeners[event]) return false;
|
|
60
|
+
for (const listener of this.#listeners[event]) await listener(...args);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/classes/GuildsError.ts
|
|
67
|
+
var GuildsError = class extends Error {
|
|
68
|
+
static name = "GuildsError";
|
|
69
|
+
scope;
|
|
70
|
+
get name() {
|
|
71
|
+
return this.scope ? `${this.constructor.name} (${this.scope})` : this.constructor.name;
|
|
72
|
+
}
|
|
73
|
+
constructor(message, scope) {
|
|
74
|
+
super(message);
|
|
75
|
+
if (scope) this.scope = scope;
|
|
76
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/classes/Routes.ts
|
|
82
|
+
var Routes = class {
|
|
83
|
+
static gateway(bot = false) {
|
|
84
|
+
return `${baseApiUrl}/gateway${bot ? "/bot" : ""}`;
|
|
85
|
+
}
|
|
86
|
+
static user(userId = "@me") {
|
|
87
|
+
return `${baseApiUrl}/users/${userId}`;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/classes/DiscordAPI.ts
|
|
93
|
+
var DiscordAPI = class {
|
|
94
|
+
#token;
|
|
95
|
+
constructor(token) {
|
|
96
|
+
if (!token) throw new GuildsError("Token must be provided", "DiscordAPIError");
|
|
97
|
+
this.#token = token;
|
|
98
|
+
}
|
|
99
|
+
get token() {
|
|
100
|
+
return this.#token;
|
|
101
|
+
}
|
|
102
|
+
async get(input, init) {
|
|
103
|
+
return await fetch(input, init);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/classes/Client.ts
|
|
109
|
+
var Client = class extends EventHandler {
|
|
110
|
+
#api;
|
|
111
|
+
#token;
|
|
112
|
+
#heartbeatInterval;
|
|
113
|
+
#presence = {
|
|
114
|
+
platform: "desktop",
|
|
115
|
+
status: "online",
|
|
116
|
+
activities: []
|
|
117
|
+
};
|
|
118
|
+
#sequenceNumber = null;
|
|
119
|
+
#sessionId;
|
|
120
|
+
#intents;
|
|
121
|
+
#ready = false;
|
|
122
|
+
#ws;
|
|
123
|
+
get token() {
|
|
124
|
+
return this.#token;
|
|
125
|
+
}
|
|
126
|
+
get intents() {
|
|
127
|
+
return this.#intents;
|
|
128
|
+
}
|
|
129
|
+
get heartbeatInterval() {
|
|
130
|
+
return this.#heartbeatInterval;
|
|
131
|
+
}
|
|
132
|
+
get presence() {
|
|
133
|
+
return this.#presence;
|
|
134
|
+
}
|
|
135
|
+
isReady() {
|
|
136
|
+
return this.#ready;
|
|
137
|
+
}
|
|
138
|
+
constructor(props) {
|
|
139
|
+
super();
|
|
140
|
+
if (!props || typeof props !== "object") throw new GuildsError("Invalid client props were provided", "ClientPropsError");
|
|
141
|
+
if (!props.token || typeof props.token !== "string") throw new GuildsError("Invalid token was provided", "ClientTokenError");
|
|
142
|
+
if (!props.intents || typeof props.intents !== "number") throw new GuildsError("Invalid intents were provided", "ClientIntentsError");
|
|
143
|
+
if (props.presence) {
|
|
144
|
+
if (typeof props.presence !== "object") throw new GuildsError("Invalid client presence was provided", "ClientPropsError");
|
|
145
|
+
this.setPresence(props.presence);
|
|
146
|
+
}
|
|
147
|
+
this.#token = props.token.trim().toLowerCase().startsWith("bot ") ? props.token : `Bot ${props.token}`;
|
|
148
|
+
this.#intents = props.intents;
|
|
149
|
+
this.#api = new DiscordAPI(this.#token);
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
async connect() {
|
|
153
|
+
const res = await this.#api.get(Routes.gateway(true));
|
|
154
|
+
const userRes = await this.#api.get(Routes.user());
|
|
155
|
+
if (!res.ok || !userRes.ok || !res || !userRes) throw new GuildsError("Failed to connect to Discord", "GatewayError");
|
|
156
|
+
const data = await res.json();
|
|
157
|
+
this.#ws = new WebSocket(`${data.url}?v=10&encoding=json`);
|
|
158
|
+
this.#ws.onmessage = (event) => {
|
|
159
|
+
this.#handleGatewayEvent(JSON.parse(event.data.toString()));
|
|
160
|
+
};
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
#handleGatewayEvent(payload) {
|
|
164
|
+
if (payload.s !== void 0 && payload.s !== null) this.#sequenceNumber = payload.s;
|
|
165
|
+
switch (payload.op) {
|
|
166
|
+
case opCodes.Hello:
|
|
167
|
+
this.emit("debug", "Received Hello event");
|
|
168
|
+
this.#heartbeatInterval = setInterval(() => {
|
|
169
|
+
this.#ws?.send(JSON.stringify({
|
|
170
|
+
op: opCodes.Heartbeat,
|
|
171
|
+
d: this.#sequenceNumber
|
|
172
|
+
}));
|
|
173
|
+
}, payload.d.heartbeat_interval);
|
|
174
|
+
this.emit("debug", "Identifying...");
|
|
175
|
+
this.#ws?.send(JSON.stringify({
|
|
176
|
+
op: opCodes.Identify,
|
|
177
|
+
d: {
|
|
178
|
+
token: this.#token,
|
|
179
|
+
intents: this.#intents,
|
|
180
|
+
presence: this.#presence,
|
|
181
|
+
properties: this.#presence.platform === "desktop" ? {
|
|
182
|
+
$os: "linux",
|
|
183
|
+
$browser: "guilds.js",
|
|
184
|
+
$device: "guilds.js"
|
|
185
|
+
} : {
|
|
186
|
+
$os: "Discord Android",
|
|
187
|
+
$browser: "Discord Android",
|
|
188
|
+
$device: "Discord Android"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}));
|
|
192
|
+
break;
|
|
193
|
+
case opCodes.Dispatch:
|
|
194
|
+
if (payload.t !== "READY") break;
|
|
195
|
+
this.#sessionId = payload.d.session_id;
|
|
196
|
+
this.#ready = true;
|
|
197
|
+
this.emit("ready", this);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
setPresence(presence) {
|
|
202
|
+
this.#presence = {
|
|
203
|
+
...this.#presence,
|
|
204
|
+
...presence
|
|
205
|
+
};
|
|
206
|
+
if (this.#ws) this.#ws.send(JSON.stringify({
|
|
207
|
+
op: opCodes.PresenceUpdate,
|
|
208
|
+
d: {
|
|
209
|
+
status: this.#presence.status,
|
|
210
|
+
since: null,
|
|
211
|
+
afk: false,
|
|
212
|
+
activities: (this.#presence.activities ?? []).map((a) => ({
|
|
213
|
+
...a,
|
|
214
|
+
type: typeof a.type === "string" ? activityTypes[a.type] : a.type
|
|
215
|
+
}))
|
|
216
|
+
}
|
|
217
|
+
}));
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
async disconnect() {
|
|
221
|
+
if (this.#heartbeatInterval) clearInterval(this.#heartbeatInterval);
|
|
222
|
+
if (this.#ws) {
|
|
223
|
+
this.#ws.close(1e3, "Client disconnected");
|
|
224
|
+
this.#ws = void 0;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
exports.Client = Client;
|
|
231
|
+
exports.DiscordAPI = DiscordAPI;
|
|
232
|
+
exports.EventHandler = EventHandler;
|
|
233
|
+
exports.GuildsError = GuildsError;
|
|
234
|
+
exports.Routes = Routes;
|
|
235
|
+
exports.activityTypes = activityTypes;
|
|
236
|
+
exports.baseApiUrl = baseApiUrl;
|
|
237
|
+
exports.errorCodes = errorCodes;
|
|
238
|
+
exports.opCodes = opCodes;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
//#region src/utils/constants.d.ts
|
|
2
|
+
declare const activityTypes: {
|
|
3
|
+
readonly Competing: 5;
|
|
4
|
+
readonly Custom: 4;
|
|
5
|
+
readonly Listening: 2;
|
|
6
|
+
readonly Playing: 0;
|
|
7
|
+
readonly Streaming: 1;
|
|
8
|
+
readonly Watching: 3;
|
|
9
|
+
};
|
|
10
|
+
declare const baseApiUrl = "https://discord.com/api/v10";
|
|
11
|
+
declare const errorCodes: readonly ["ClientIntentsError", "ClientPropsError", "ClientTokenError", "DiscordAPIError", "GatewayError", "WebSocketError"];
|
|
12
|
+
declare const opCodes: {
|
|
13
|
+
readonly Dispatch: 0;
|
|
14
|
+
readonly Heartbeat: 1;
|
|
15
|
+
readonly HeartbeatACK: 11;
|
|
16
|
+
readonly Hello: 10;
|
|
17
|
+
readonly Identify: 2;
|
|
18
|
+
readonly InvalidSession: 9;
|
|
19
|
+
readonly PresenceUpdate: 3;
|
|
20
|
+
readonly Reconnect: 7;
|
|
21
|
+
readonly RequestGuildMembers: 8;
|
|
22
|
+
readonly RequestSoundboardSounds: 12;
|
|
23
|
+
readonly Resume: 6;
|
|
24
|
+
readonly VoiceStateUpdate: 3;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/classes/EventHandler.d.ts
|
|
28
|
+
declare class EventHandler<Events extends Record<string, any[]>> {
|
|
29
|
+
#private;
|
|
30
|
+
on<K extends keyof Events>(event: K, listener: (...args: Events[K]) => any): this;
|
|
31
|
+
once<K extends keyof Events>(event: K, listener: (...args: Events[K]) => any): this;
|
|
32
|
+
off<K extends keyof Events>(event: K, listener: (...args: Events[K]) => any): this;
|
|
33
|
+
emit<K extends keyof Events>(event: K, ...args: Events[K]): Promise<boolean>;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/classes/GuildsError.d.ts
|
|
37
|
+
declare class GuildsError extends Error {
|
|
38
|
+
static name: string;
|
|
39
|
+
readonly scope?: ErrorCode;
|
|
40
|
+
get name(): string;
|
|
41
|
+
constructor(message: string, scope?: ErrorCode);
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/classes/Routes.d.ts
|
|
45
|
+
declare class Routes {
|
|
46
|
+
static gateway(bot?: boolean): "https://discord.com/api/v10/gateway/bot" | "https://discord.com/api/v10/gateway";
|
|
47
|
+
static user(userId?: string): `https://discord.com/api/v10/users/${string}`;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/classes/DiscordAPI.d.ts
|
|
51
|
+
declare class DiscordAPI {
|
|
52
|
+
#private;
|
|
53
|
+
constructor(token: string);
|
|
54
|
+
get token(): string;
|
|
55
|
+
get(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/classes/Client.d.ts
|
|
59
|
+
declare class Client<Ready extends boolean = false> extends EventHandler<ClientEvents> {
|
|
60
|
+
#private;
|
|
61
|
+
get token(): string;
|
|
62
|
+
get intents(): number;
|
|
63
|
+
get heartbeatInterval(): NodeJS.Timeout | undefined;
|
|
64
|
+
get presence(): ClientPresence;
|
|
65
|
+
isReady(): this is Client<true>;
|
|
66
|
+
constructor(props: ClientProps);
|
|
67
|
+
connect(): Promise<Client<true>>;
|
|
68
|
+
setPresence(presence: Partial<ClientPresence>): this;
|
|
69
|
+
disconnect(): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/types.d.ts
|
|
73
|
+
type ActivityType = keyof typeof activityTypes | (typeof activityTypes)[keyof typeof activityTypes];
|
|
74
|
+
type ClientEvents = {
|
|
75
|
+
debug: [message: string];
|
|
76
|
+
error: [error: any];
|
|
77
|
+
ready: [readyClient: Client<true>];
|
|
78
|
+
};
|
|
79
|
+
interface ClientPresence {
|
|
80
|
+
activities: ClientActivity[];
|
|
81
|
+
platform: "desktop" | "mobile";
|
|
82
|
+
status: UserStatus;
|
|
83
|
+
}
|
|
84
|
+
interface ClientActivity {
|
|
85
|
+
name: string;
|
|
86
|
+
state?: string;
|
|
87
|
+
type: ActivityType;
|
|
88
|
+
url?: string;
|
|
89
|
+
}
|
|
90
|
+
interface ClientPresence {}
|
|
91
|
+
interface ClientProps {
|
|
92
|
+
token: string;
|
|
93
|
+
intents: number;
|
|
94
|
+
presence?: Partial<ClientPresence>;
|
|
95
|
+
}
|
|
96
|
+
type ErrorCode = (typeof errorCodes)[keyof typeof errorCodes];
|
|
97
|
+
interface GatewayPayload {
|
|
98
|
+
op: (typeof opCodes)[keyof typeof opCodes];
|
|
99
|
+
d?: any;
|
|
100
|
+
s?: number | null;
|
|
101
|
+
t?: string | null;
|
|
102
|
+
}
|
|
103
|
+
type If<Condition extends boolean, Then, Else = never> = Condition extends true ? Then : Else;
|
|
104
|
+
type UserStatus = "online" | "idle" | "dnd" | "offline";
|
|
105
|
+
//#endregion
|
|
106
|
+
export { ActivityType, Client, ClientActivity, ClientEvents, ClientPresence, ClientProps, DiscordAPI, ErrorCode, EventHandler, GatewayPayload, GuildsError, If, Routes, UserStatus, activityTypes, baseApiUrl, errorCodes, opCodes };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
//#region src/utils/constants.d.ts
|
|
2
|
+
declare const activityTypes: {
|
|
3
|
+
readonly Competing: 5;
|
|
4
|
+
readonly Custom: 4;
|
|
5
|
+
readonly Listening: 2;
|
|
6
|
+
readonly Playing: 0;
|
|
7
|
+
readonly Streaming: 1;
|
|
8
|
+
readonly Watching: 3;
|
|
9
|
+
};
|
|
10
|
+
declare const baseApiUrl = "https://discord.com/api/v10";
|
|
11
|
+
declare const errorCodes: readonly ["ClientIntentsError", "ClientPropsError", "ClientTokenError", "DiscordAPIError", "GatewayError", "WebSocketError"];
|
|
12
|
+
declare const opCodes: {
|
|
13
|
+
readonly Dispatch: 0;
|
|
14
|
+
readonly Heartbeat: 1;
|
|
15
|
+
readonly HeartbeatACK: 11;
|
|
16
|
+
readonly Hello: 10;
|
|
17
|
+
readonly Identify: 2;
|
|
18
|
+
readonly InvalidSession: 9;
|
|
19
|
+
readonly PresenceUpdate: 3;
|
|
20
|
+
readonly Reconnect: 7;
|
|
21
|
+
readonly RequestGuildMembers: 8;
|
|
22
|
+
readonly RequestSoundboardSounds: 12;
|
|
23
|
+
readonly Resume: 6;
|
|
24
|
+
readonly VoiceStateUpdate: 3;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/classes/EventHandler.d.ts
|
|
28
|
+
declare class EventHandler<Events extends Record<string, any[]>> {
|
|
29
|
+
#private;
|
|
30
|
+
on<K extends keyof Events>(event: K, listener: (...args: Events[K]) => any): this;
|
|
31
|
+
once<K extends keyof Events>(event: K, listener: (...args: Events[K]) => any): this;
|
|
32
|
+
off<K extends keyof Events>(event: K, listener: (...args: Events[K]) => any): this;
|
|
33
|
+
emit<K extends keyof Events>(event: K, ...args: Events[K]): Promise<boolean>;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/classes/GuildsError.d.ts
|
|
37
|
+
declare class GuildsError extends Error {
|
|
38
|
+
static name: string;
|
|
39
|
+
readonly scope?: ErrorCode;
|
|
40
|
+
get name(): string;
|
|
41
|
+
constructor(message: string, scope?: ErrorCode);
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/classes/Routes.d.ts
|
|
45
|
+
declare class Routes {
|
|
46
|
+
static gateway(bot?: boolean): "https://discord.com/api/v10/gateway/bot" | "https://discord.com/api/v10/gateway";
|
|
47
|
+
static user(userId?: string): `https://discord.com/api/v10/users/${string}`;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/classes/DiscordAPI.d.ts
|
|
51
|
+
declare class DiscordAPI {
|
|
52
|
+
#private;
|
|
53
|
+
constructor(token: string);
|
|
54
|
+
get token(): string;
|
|
55
|
+
get(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/classes/Client.d.ts
|
|
59
|
+
declare class Client<Ready extends boolean = false> extends EventHandler<ClientEvents> {
|
|
60
|
+
#private;
|
|
61
|
+
get token(): string;
|
|
62
|
+
get intents(): number;
|
|
63
|
+
get heartbeatInterval(): NodeJS.Timeout | undefined;
|
|
64
|
+
get presence(): ClientPresence;
|
|
65
|
+
isReady(): this is Client<true>;
|
|
66
|
+
constructor(props: ClientProps);
|
|
67
|
+
connect(): Promise<Client<true>>;
|
|
68
|
+
setPresence(presence: Partial<ClientPresence>): this;
|
|
69
|
+
disconnect(): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/types.d.ts
|
|
73
|
+
type ActivityType = keyof typeof activityTypes | (typeof activityTypes)[keyof typeof activityTypes];
|
|
74
|
+
type ClientEvents = {
|
|
75
|
+
debug: [message: string];
|
|
76
|
+
error: [error: any];
|
|
77
|
+
ready: [readyClient: Client<true>];
|
|
78
|
+
};
|
|
79
|
+
interface ClientPresence {
|
|
80
|
+
activities: ClientActivity[];
|
|
81
|
+
platform: "desktop" | "mobile";
|
|
82
|
+
status: UserStatus;
|
|
83
|
+
}
|
|
84
|
+
interface ClientActivity {
|
|
85
|
+
name: string;
|
|
86
|
+
state?: string;
|
|
87
|
+
type: ActivityType;
|
|
88
|
+
url?: string;
|
|
89
|
+
}
|
|
90
|
+
interface ClientPresence {}
|
|
91
|
+
interface ClientProps {
|
|
92
|
+
token: string;
|
|
93
|
+
intents: number;
|
|
94
|
+
presence?: Partial<ClientPresence>;
|
|
95
|
+
}
|
|
96
|
+
type ErrorCode = (typeof errorCodes)[keyof typeof errorCodes];
|
|
97
|
+
interface GatewayPayload {
|
|
98
|
+
op: (typeof opCodes)[keyof typeof opCodes];
|
|
99
|
+
d?: any;
|
|
100
|
+
s?: number | null;
|
|
101
|
+
t?: string | null;
|
|
102
|
+
}
|
|
103
|
+
type If<Condition extends boolean, Then, Else = never> = Condition extends true ? Then : Else;
|
|
104
|
+
type UserStatus = "online" | "idle" | "dnd" | "offline";
|
|
105
|
+
//#endregion
|
|
106
|
+
export { ActivityType, Client, ClientActivity, ClientEvents, ClientPresence, ClientProps, DiscordAPI, ErrorCode, EventHandler, GatewayPayload, GuildsError, If, Routes, UserStatus, activityTypes, baseApiUrl, errorCodes, opCodes };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
//#region src/utils/constants.ts
|
|
2
|
+
const activityTypes = {
|
|
3
|
+
Competing: 5,
|
|
4
|
+
Custom: 4,
|
|
5
|
+
Listening: 2,
|
|
6
|
+
Playing: 0,
|
|
7
|
+
Streaming: 1,
|
|
8
|
+
Watching: 3
|
|
9
|
+
};
|
|
10
|
+
const baseApiUrl = "https://discord.com/api/v10";
|
|
11
|
+
const errorCodes = [
|
|
12
|
+
"ClientIntentsError",
|
|
13
|
+
"ClientPropsError",
|
|
14
|
+
"ClientTokenError",
|
|
15
|
+
"DiscordAPIError",
|
|
16
|
+
"GatewayError",
|
|
17
|
+
"WebSocketError"
|
|
18
|
+
];
|
|
19
|
+
const opCodes = {
|
|
20
|
+
Dispatch: 0,
|
|
21
|
+
Heartbeat: 1,
|
|
22
|
+
HeartbeatACK: 11,
|
|
23
|
+
Hello: 10,
|
|
24
|
+
Identify: 2,
|
|
25
|
+
InvalidSession: 9,
|
|
26
|
+
PresenceUpdate: 3,
|
|
27
|
+
Reconnect: 7,
|
|
28
|
+
RequestGuildMembers: 8,
|
|
29
|
+
RequestSoundboardSounds: 12,
|
|
30
|
+
Resume: 6,
|
|
31
|
+
VoiceStateUpdate: 3
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/classes/EventHandler.ts
|
|
36
|
+
var EventHandler = class {
|
|
37
|
+
#listeners = {};
|
|
38
|
+
on(event, listener) {
|
|
39
|
+
if (!this.#listeners[event]) this.#listeners[event] = [];
|
|
40
|
+
this.#listeners[event].push(listener);
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
once(event, listener) {
|
|
44
|
+
const wrapped = (...args) => {
|
|
45
|
+
listener(...args);
|
|
46
|
+
this.off(event, wrapped);
|
|
47
|
+
};
|
|
48
|
+
this.on(event, wrapped);
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
off(event, listener) {
|
|
52
|
+
if (!this.#listeners[event]) return this;
|
|
53
|
+
this.#listeners[event] = this.#listeners[event].filter((l) => l !== listener);
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
async emit(event, ...args) {
|
|
57
|
+
if (!this.#listeners[event]) return false;
|
|
58
|
+
for (const listener of this.#listeners[event]) await listener(...args);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/classes/GuildsError.ts
|
|
65
|
+
var GuildsError = class extends Error {
|
|
66
|
+
static name = "GuildsError";
|
|
67
|
+
scope;
|
|
68
|
+
get name() {
|
|
69
|
+
return this.scope ? `${this.constructor.name} (${this.scope})` : this.constructor.name;
|
|
70
|
+
}
|
|
71
|
+
constructor(message, scope) {
|
|
72
|
+
super(message);
|
|
73
|
+
if (scope) this.scope = scope;
|
|
74
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/classes/Routes.ts
|
|
80
|
+
var Routes = class {
|
|
81
|
+
static gateway(bot = false) {
|
|
82
|
+
return `${baseApiUrl}/gateway${bot ? "/bot" : ""}`;
|
|
83
|
+
}
|
|
84
|
+
static user(userId = "@me") {
|
|
85
|
+
return `${baseApiUrl}/users/${userId}`;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/classes/DiscordAPI.ts
|
|
91
|
+
var DiscordAPI = class {
|
|
92
|
+
#token;
|
|
93
|
+
constructor(token) {
|
|
94
|
+
if (!token) throw new GuildsError("Token must be provided", "DiscordAPIError");
|
|
95
|
+
this.#token = token;
|
|
96
|
+
}
|
|
97
|
+
get token() {
|
|
98
|
+
return this.#token;
|
|
99
|
+
}
|
|
100
|
+
async get(input, init) {
|
|
101
|
+
return await fetch(input, init);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/classes/Client.ts
|
|
107
|
+
var Client = class extends EventHandler {
|
|
108
|
+
#api;
|
|
109
|
+
#token;
|
|
110
|
+
#heartbeatInterval;
|
|
111
|
+
#presence = {
|
|
112
|
+
platform: "desktop",
|
|
113
|
+
status: "online",
|
|
114
|
+
activities: []
|
|
115
|
+
};
|
|
116
|
+
#sequenceNumber = null;
|
|
117
|
+
#sessionId;
|
|
118
|
+
#intents;
|
|
119
|
+
#ready = false;
|
|
120
|
+
#ws;
|
|
121
|
+
get token() {
|
|
122
|
+
return this.#token;
|
|
123
|
+
}
|
|
124
|
+
get intents() {
|
|
125
|
+
return this.#intents;
|
|
126
|
+
}
|
|
127
|
+
get heartbeatInterval() {
|
|
128
|
+
return this.#heartbeatInterval;
|
|
129
|
+
}
|
|
130
|
+
get presence() {
|
|
131
|
+
return this.#presence;
|
|
132
|
+
}
|
|
133
|
+
isReady() {
|
|
134
|
+
return this.#ready;
|
|
135
|
+
}
|
|
136
|
+
constructor(props) {
|
|
137
|
+
super();
|
|
138
|
+
if (!props || typeof props !== "object") throw new GuildsError("Invalid client props were provided", "ClientPropsError");
|
|
139
|
+
if (!props.token || typeof props.token !== "string") throw new GuildsError("Invalid token was provided", "ClientTokenError");
|
|
140
|
+
if (!props.intents || typeof props.intents !== "number") throw new GuildsError("Invalid intents were provided", "ClientIntentsError");
|
|
141
|
+
if (props.presence) {
|
|
142
|
+
if (typeof props.presence !== "object") throw new GuildsError("Invalid client presence was provided", "ClientPropsError");
|
|
143
|
+
this.setPresence(props.presence);
|
|
144
|
+
}
|
|
145
|
+
this.#token = props.token.trim().toLowerCase().startsWith("bot ") ? props.token : `Bot ${props.token}`;
|
|
146
|
+
this.#intents = props.intents;
|
|
147
|
+
this.#api = new DiscordAPI(this.#token);
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
async connect() {
|
|
151
|
+
const res = await this.#api.get(Routes.gateway(true));
|
|
152
|
+
const userRes = await this.#api.get(Routes.user());
|
|
153
|
+
if (!res.ok || !userRes.ok || !res || !userRes) throw new GuildsError("Failed to connect to Discord", "GatewayError");
|
|
154
|
+
const data = await res.json();
|
|
155
|
+
this.#ws = new WebSocket(`${data.url}?v=10&encoding=json`);
|
|
156
|
+
this.#ws.onmessage = (event) => {
|
|
157
|
+
this.#handleGatewayEvent(JSON.parse(event.data.toString()));
|
|
158
|
+
};
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
#handleGatewayEvent(payload) {
|
|
162
|
+
if (payload.s !== void 0 && payload.s !== null) this.#sequenceNumber = payload.s;
|
|
163
|
+
switch (payload.op) {
|
|
164
|
+
case opCodes.Hello:
|
|
165
|
+
this.emit("debug", "Received Hello event");
|
|
166
|
+
this.#heartbeatInterval = setInterval(() => {
|
|
167
|
+
this.#ws?.send(JSON.stringify({
|
|
168
|
+
op: opCodes.Heartbeat,
|
|
169
|
+
d: this.#sequenceNumber
|
|
170
|
+
}));
|
|
171
|
+
}, payload.d.heartbeat_interval);
|
|
172
|
+
this.emit("debug", "Identifying...");
|
|
173
|
+
this.#ws?.send(JSON.stringify({
|
|
174
|
+
op: opCodes.Identify,
|
|
175
|
+
d: {
|
|
176
|
+
token: this.#token,
|
|
177
|
+
intents: this.#intents,
|
|
178
|
+
presence: this.#presence,
|
|
179
|
+
properties: this.#presence.platform === "desktop" ? {
|
|
180
|
+
$os: "linux",
|
|
181
|
+
$browser: "guilds.js",
|
|
182
|
+
$device: "guilds.js"
|
|
183
|
+
} : {
|
|
184
|
+
$os: "Discord Android",
|
|
185
|
+
$browser: "Discord Android",
|
|
186
|
+
$device: "Discord Android"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}));
|
|
190
|
+
break;
|
|
191
|
+
case opCodes.Dispatch:
|
|
192
|
+
if (payload.t !== "READY") break;
|
|
193
|
+
this.#sessionId = payload.d.session_id;
|
|
194
|
+
this.#ready = true;
|
|
195
|
+
this.emit("ready", this);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
setPresence(presence) {
|
|
200
|
+
this.#presence = {
|
|
201
|
+
...this.#presence,
|
|
202
|
+
...presence
|
|
203
|
+
};
|
|
204
|
+
if (this.#ws) this.#ws.send(JSON.stringify({
|
|
205
|
+
op: opCodes.PresenceUpdate,
|
|
206
|
+
d: {
|
|
207
|
+
status: this.#presence.status,
|
|
208
|
+
since: null,
|
|
209
|
+
afk: false,
|
|
210
|
+
activities: (this.#presence.activities ?? []).map((a) => ({
|
|
211
|
+
...a,
|
|
212
|
+
type: typeof a.type === "string" ? activityTypes[a.type] : a.type
|
|
213
|
+
}))
|
|
214
|
+
}
|
|
215
|
+
}));
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
async disconnect() {
|
|
219
|
+
if (this.#heartbeatInterval) clearInterval(this.#heartbeatInterval);
|
|
220
|
+
if (this.#ws) {
|
|
221
|
+
this.#ws.close(1e3, "Client disconnected");
|
|
222
|
+
this.#ws = void 0;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
//#endregion
|
|
228
|
+
export { Client, DiscordAPI, EventHandler, GuildsError, Routes, activityTypes, baseApiUrl, errorCodes, opCodes };
|
package/package.json
CHANGED
|
@@ -1,7 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guilds.js",
|
|
3
|
-
"
|
|
3
|
+
"description": "Interact with Discord's API with ease",
|
|
4
|
+
"version": "0.0.2",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"exports": {
|
|
7
|
+
"default": "./dist/index.cjs",
|
|
8
|
+
"import": "./dist/index.mjs",
|
|
9
|
+
"require": "./dist/index.cjs",
|
|
10
|
+
"types": "./dist/index.d.cts"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.mjs",
|
|
13
|
+
"module": "./dist/index.mjs",
|
|
14
|
+
"types": "./dist/index.d.mts",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^25.3.0",
|
|
18
|
+
"prettier": "^3.8.1",
|
|
19
|
+
"tsdown": "^0.20.3",
|
|
20
|
+
"typescript": "^5.9.3"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"package.json",
|
|
26
|
+
"public/logo.png",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"api",
|
|
31
|
+
"discord",
|
|
32
|
+
"discord-bot",
|
|
33
|
+
"javascript",
|
|
34
|
+
"library",
|
|
35
|
+
"module",
|
|
36
|
+
"node",
|
|
37
|
+
"node-js",
|
|
38
|
+
"npm",
|
|
39
|
+
"pnpm",
|
|
40
|
+
"tsdown",
|
|
41
|
+
"typescript",
|
|
42
|
+
"websocket",
|
|
43
|
+
"ws"
|
|
44
|
+
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/andrewdku/guilds"
|
|
51
|
+
},
|
|
4
52
|
"publishConfig": {
|
|
5
53
|
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"prettier": {
|
|
56
|
+
"arrowParens": "always",
|
|
57
|
+
"jsxSingleQuote": false,
|
|
58
|
+
"printWidth": 90,
|
|
59
|
+
"quoteProps": "as-needed",
|
|
60
|
+
"semi": true,
|
|
61
|
+
"singleQuote": false,
|
|
62
|
+
"tabWidth": 4,
|
|
63
|
+
"trailingComma": "es5",
|
|
64
|
+
"useTabs": false
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "tsdown",
|
|
68
|
+
"format": "prettier --write . --config package.json --ignore-path .prettierignore",
|
|
69
|
+
"prepublish": "tsdown"
|
|
6
70
|
}
|
|
7
71
|
}
|
package/public/logo.png
ADDED
|
Binary file
|