├── .editorconfig
├── .eslintrc.json
├── .github
├── ISSUE_TEMPLATE
│ └── can-t-get-it-to-work.md
└── workflows
│ └── ci.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── index.d.ts
├── index.js
├── lib
├── eris
│ ├── errors
│ │ ├── DiscordHTTPError.js
│ │ └── DiscordRESTError.js
│ ├── rest
│ │ └── RequestHandler.js
│ └── util
│ │ └── SequentialBucket.js
└── oauth.js
├── package-lock.json
└── package.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 4
8 | indent_style = tab
9 | insert_final_newline = true
10 | max_line_length = 80
11 | trim_trailing_whitespace = true
12 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true,
4 | "node": true
5 | },
6 | "parserOptions": {
7 | "ecmaVersion": 2020
8 | },
9 | "extends": "eslint:recommended",
10 | "rules": {
11 | "semi": "error",
12 | "no-var": "error",
13 | "prefer-const": "error",
14 | "keyword-spacing": "error",
15 | "eqeqeq": "error",
16 | "eol-last": "error",
17 | "brace-style": ["error", "stroustrup"],
18 | "comma-dangle": ["error", "always-multiline"],
19 | "object-curly-spacing": ["error", "always"],
20 | "quotes": ["error", "double", {
21 | "allowTemplateLiterals": true
22 | }],
23 | "indent": ["error", "tab", {
24 | "SwitchCase": 1,
25 | "flatTernaryExpressions": true
26 | }],
27 | "object-curly-newline": ["error", {
28 | "ExportDeclaration": "never",
29 | "ImportDeclaration": "always"
30 | }]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/can-t-get-it-to-work.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Can't get it to work
3 | about: Use this template if you are having issues with the library (all issues that
4 | don't follow this template will be ignored and closed).
5 | title: ''
6 | labels: ''
7 | assignees: reboxer
8 |
9 | ---
10 |
11 | **Describe the error**
12 | A clear and concise description of what the error is
13 |
14 | **NodeJS version**
15 | *Your NodeJS version*
16 |
17 | **Library version**
18 | *The version of the library you are using*
19 |
20 | **Error stack and exact error message**
21 | *The error stack thrown by the library and the exact error message sent by discord (see [debugging](https://github.com/reboxer/discord-oauth2#debugging))
22 |
23 | **Relevant code**
24 | ```js
25 | // Put here the code you are using that is not working
26 | ```
27 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Run ESLint
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | lint:
11 | name: Run ESlint
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v2
17 | - run: npm ci
18 | - run: npm run lint
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib/eris
3 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "trailingComma": "all",
4 | "tabWidth": 4,
5 | "semi": true,
6 | "singleQuote": false
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 reboxer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # discord-oauth2 [](https://www.npmjs.com/package/discord-oauth2)
2 |
3 | A really simple to use module to use discord's OAuth2 API.
4 |
5 | Please check out discord's OAuth2 documentation: https://discord.com/developers/docs/topics/oauth2
6 |
7 | ### Installing
8 |
9 | ```bash
10 | npm install discord-oauth2
11 | ```
12 |
13 | # Class constructor
14 |
15 | One parameter is passed to the class constructor:
16 |
17 | ### `Options`
18 |
19 | Since the module uses a modified version of [Eris](https://github.com/abalabahaha/eris)'s request handler, it takes the same options, all of them default to the default Eris REST options if no options are passed.
20 |
21 | Request handler options:
22 | ```
23 | agent: A HTTPS Agent used to proxy requests
24 |
25 | requestTimeout: A number of milliseconds before requests are considered timed out.
26 |
27 | latencyThreshold: The average request latency at which the RequestHandler will start emitting latency errors.
28 |
29 | ratelimiterOffset: A number of milliseconds to offset the ratelimit timing calculations by.
30 | ```
31 |
32 | Others, you can pass these options to the class constructor so you don't have to pass them each time you call a function:
33 | ```
34 | clientId: Your application's client id.
35 |
36 | clientSecret: Your application's client secret.
37 |
38 | redirectUri: Your URL redirect uri.
39 |
40 | credentials: Base64 encoding of the UTF-8 encoded credentials string of your application, you can pass this in the constructor to not pass it every time you want to use the revokeToken() method.
41 | ```
42 |
43 | # Events
44 |
45 | In the Eris Library, client extends the `events` modules and the client is passed to the RequestHandler so it's able to emit events, this modified RequestHandler extends `events` so it can emit the same events.
46 |
47 | There are only two events, `debug` and `warn`.
48 |
49 | # Methods
50 |
51 | ### `tokenRequest(object)`
52 |
53 | Takes an object with the following properties:
54 |
55 | `clientId`: Your application's client id. Can be omitted if provided on the client constructor.
56 |
57 | `clientSecret`: Your application's client secret. Can be omitted if provided on the client constructor.
58 |
59 | `scope`: The scopes requested in your authorization url, can be either a space-delimited string of scopes, or an array of strings containing scopes.
60 |
61 | `redirectUri`: Your URL redirect uri. Can be omitted if provided on the client constructor.
62 |
63 | `grantType`: The grant type to set for the request, either authorization_code or refresh_token.
64 |
65 | `code`: The code from the querystring (grantType `authorization_code` only).
66 |
67 | `refreshToken`: The user's refresh token (grantType `refresh_token` only).
68 |
69 |
70 | Returns a promise which resolves in an object with the access token.
71 |
72 | Please refer to discord's OAuth2 [documentation](https://discord.com/developers/docs/topics/oauth2#authorization-code-grant-access-token-exchange-example) for the parameters needed.
73 |
74 | ```js
75 | const DiscordOauth2 = require("discord-oauth2");
76 | const oauth = new DiscordOauth2();
77 |
78 | oauth.tokenRequest({
79 | clientId: "332269999912132097",
80 | clientSecret: "937it3ow87i4ery69876wqire",
81 |
82 | code: "query code",
83 | scope: "identify guilds",
84 | grantType: "authorization_code",
85 |
86 | redirectUri: "http://localhost/callback",
87 | }).then(console.log)
88 | ```
89 |
90 | Using class constructor options, array of scopes and grantType refresh_token:
91 |
92 | ```js
93 | const DiscordOauth2 = require("discord-oauth2");
94 | const oauth = new DiscordOauth2({
95 | clientId: "332269999912132097",
96 | clientSecret: "937it3ow87i4ery69876wqire",
97 | redirectUri: "http://localhost/callback",
98 | });
99 |
100 | oauth.tokenRequest({
101 | // clientId, clientSecret and redirectUri are omitted, as they were already set on the class constructor
102 | refreshToken: "D43f5y0ahjqew82jZ4NViEr2YafMKhue",
103 | grantType: "refresh_token",
104 | });
105 |
106 | // On successful request both requesting and refreshing an access token return the same object
107 | /*
108 | {
109 | "access_token": "6qrZcUqja7812RVdnEKjpzOL4CvHBFG",
110 | "token_type": "Bearer",
111 | "expires_in": 604800,
112 | "refresh_token": "D43f5y0ahjqew82jZ4NViEr2YafMKhue",
113 | "scope": "identify guilds"
114 | }
115 | */
116 | ```
117 |
118 | ### `revokeToken(access_token, credentials)`
119 |
120 | `access_token`: The access token to revoke.
121 |
122 | `credentials`: The base64 encoded credentials string of your application.
123 |
124 | Returns a promise which resolves in an empty object if successful.
125 |
126 | ```js
127 | const DiscordOauth2 = require("discord-oauth2");
128 | const oauth = new DiscordOauth2();
129 |
130 | const clientID = "332269999912132097";
131 | const client_secret = "937it3ow87i4ery69876wqire";
132 | const access_token = "6qrZcUqja7812RVdnEKjpzOL4CvHBFG";
133 |
134 | // You must encode your client ID along with your client secret including the colon in between
135 | const credentials = Buffer.from(`${clientID}:${client_secret}`).toString("base64"); // MzMyMjY5OTk5OTEyMTMyMDk3OjkzN2l0M293ODdpNGVyeTY5ODc2d3FpcmU=
136 |
137 | oauth.revokeToken(access_token, credentials).then(console.log); // {}
138 | ```
139 |
140 | ### `getUser(access_token)`
141 |
142 | `access_token`: The user's access token.
143 |
144 | Requires the `identify` scope. Include the `email` scope if you want to get the user's email.
145 |
146 | Returns the [user](https://discord.com/developers/docs/resources/user#user-object) object of the requester's account.
147 |
148 | ```js
149 | const DiscordOauth2 = require("discord-oauth2");
150 | const oauth = new DiscordOauth2();
151 |
152 | const access_token = "6qrZcUqja7812RVdnEKjpzOL4CvHBFG";
153 |
154 | oauth.getUser(access_token).then(console.log);
155 | /*
156 | {
157 | username: '1337 Krew',
158 | locale: 'en-US',
159 | mfa_enabled: true,
160 | flags: 128,
161 | avatar: '8342729096ea3675442027381ff50dfe',
162 | discriminator: '4421',
163 | id: '80351110224678912'
164 | }
165 | */
166 | ```
167 |
168 | ### `getUserGuilds(access_token, object)`
169 |
170 | `access_token`: The user's access token.
171 |
172 | Options:
173 |
174 | `before`: Get guilds before this guild ID.
175 |
176 | `after`: Get guilds after this guild ID.
177 |
178 | `limit`: Max number of guilds to return (1-200).
179 |
180 | `withCounts`: Include approximate member and presence counts in response.
181 |
182 | Requires the `guilds` scope.
183 |
184 | Returns a list of partial [guild](https://discord.com/developers/docs/resources/guild#guild-object) objects the current user is a member of.
185 |
186 | ```js
187 | const DiscordOauth2 = require("discord-oauth2");
188 | const oauth = new DiscordOauth2();
189 |
190 | const access_token = "6qrZcUqja7812RVdnEKjpzOL4CvHBFG";
191 |
192 | oauth.getUserGuilds(access_token).then(console.log);
193 | /*
194 | {
195 | "id": "80351110224678912",
196 | "name": "1337 Krew",
197 | "icon": "8342729096ea3675442027381ff50dfe",
198 | "owner": true,
199 | "permissions": 36953089,
200 | "permissions_new": "36953089"
201 | }
202 | */
203 | ```
204 |
205 | ### `getUserConnections(access_token)`
206 |
207 | `access_token`: The user's access token.
208 |
209 | Requires the `connections` OAuth2 scope.
210 |
211 | Returns a list of [connection](https://discord.com/developers/docs/resources/user#connection-object) objects.
212 |
213 | ```js
214 | const DiscordOauth2 = require("discord-oauth2");
215 | const oauth = new DiscordOauth2();
216 |
217 | const access_token = "6qrZcUqja7812RVdnEKjpzOL4CvHBFG";
218 |
219 | oauth.getUserConnections(access_token).then(console.log);
220 | /*
221 | [ { verified: true,
222 | name: 'epicusername',
223 | show_activity: true,
224 | friend_sync: false,
225 | type: 'twitch',
226 | id: '31244565',
227 | visibility: 1 } ]
228 | */
229 | ```
230 |
231 | ### `addMember(object)`
232 |
233 | Force join a user to a guild.
234 |
235 | Takes an object with the following properties (required):
236 |
237 | `accessToken`: The user access token.
238 |
239 | `botToken`: The token of the bot used to authenticate.
240 |
241 | `guildId`: The ID of the guild to join.
242 |
243 | `userId`: The ID of the user to be added to the guild.
244 |
245 | Optional:
246 |
247 | `nickname`: Value to set users nickname to.
248 |
249 | `roles`: Array of role ids the member is assigned.
250 |
251 | `mute`: Whether the user is muted in voice channels.
252 |
253 | `deaf`: Whether the user is deafened in voice channels.
254 |
255 | Returns a member object if the user wasn't part of the guild, else, returns an empty string (length 0).
256 |
257 | ```js
258 | const DiscordOauth2 = require("discord-oauth2");
259 | const oauth = new DiscordOauth2();
260 |
261 | oauth.addMember({
262 | accessToken: "2qRZcUqUa9816RVnnEKRpzOL2CvHBgF",
263 | botToken: "NDgyMjM4ODQzNDI1MjU5NTIz.XK93JQ.bnLsc71_DGum-Qnymb4T5F6kGY8",
264 | guildId: "216488324594438692",
265 | userId: "80351110224678912",
266 |
267 | nickname: "george michael",
268 | roles: ["624615851966070786"],
269 | mute: true,
270 | deaf: true,
271 | }).then(console.log); // Member object or empty string
272 |
273 | /*
274 | {
275 | nick: 'george michael',
276 | user: {
277 | username: 'some username',
278 | discriminator: '0001',
279 | id: '421610529323943943',
280 | avatar: null
281 | },
282 | roles: [ '324615841966570766' ],
283 | premium_since: null,
284 | deaf: true,
285 | mute: true,
286 | joined_at: '2019-09-20T14:44:12.603123+00:00'
287 | }
288 | */
289 | ```
290 |
291 | ### `getGuildMember(access_token, guildId)`
292 |
293 | Get the member object in a guild the user is in.
294 |
295 | `access_token`: The user access token.
296 |
297 | `guildId`: The ID of the guild to get the member object from.
298 |
299 | Returns a member object.
300 |
301 | ```js
302 | const DiscordOauth2 = require("discord-oauth2");
303 | const oauth = new DiscordOauth2();
304 |
305 | const access_token = "6qrZcUqja7812RVdnEKjpzOL4CvHBFG";
306 | const guildId = "216488324594438692";
307 |
308 | oauth.getGuildMember(access_token, guildId).then(console.log);
309 |
310 | /*
311 | {
312 | roles: [
313 | '3812761565259673315',
314 | ],
315 | nick: 'my nickname',
316 | avatar: null,
317 | premium_since: null,
318 | joined_at: '2017-02-15T12:06:21.285000+00:00',
319 | is_pending: false,
320 | pending: false,
321 | communication_disabled_until: null,
322 | user: {
323 | id: '462551172942746558',
324 | username: 'some username',
325 | avatar: '5282b6a9haac8ada65d6997d88f734k5',
326 | discriminator: '2142',
327 | public_flags: 32
328 | },
329 | mute: false,
330 | deaf: false
331 | }
332 | */
333 | ```
334 |
335 | ### `getCurrentAuthorizationInformation(access_token)`
336 |
337 | `access_token`: The user's access token.
338 |
339 | Returns info about the current authorization. Includes the [user](https://discord.com/developers/docs/resources/user#user-object) object of the requester's account if they authorized with the `identify` scope.
340 |
341 | ```js
342 | const DiscordOauth2 = require("discord-oauth2");
343 | const oauth = new DiscordOauth2();
344 |
345 | const access_token = "6qrZcUqja7812RVdnEKjpzOL4CvHBFG";
346 |
347 | oauth.getCurrentAuthorizationInformation(access_token).then(console.log);
348 | /*
349 | {
350 | "application": {
351 | "id": "159799960412356608",
352 | "name": "AIRHORN SOLUTIONS",
353 | "icon": "f03590d3eb764081d154a66340ea7d6d",
354 | "description": "",
355 | "hook": true,
356 | "bot_public": true,
357 | "bot_require_code_grant": false,
358 | "verify_key": "c8cde6a3c8c6e49d86af3191287b3ce255872be1fff6dc285bdb420c06a2c3c8"
359 | },
360 | "scopes": [
361 | "guilds.join",
362 | "identify"
363 | ],
364 | "expires": "2021-01-23T02:33:17.017000+00:00",
365 | "user": {
366 | "id": "268473310986240001",
367 | "username": "discord",
368 | "avatar": "f749bb0cbeeb26ef21eca719337d20f1",
369 | "discriminator": "0",
370 | "global_name": "Discord",
371 | "public_flags": 131072
372 | }
373 | }
374 | */
375 | ```
376 |
377 | ### `generateAuthUrl(object)`
378 |
379 | Dynamically generate an OAuth2 URL.
380 |
381 | Takes an object with the following properties:
382 |
383 | `clientId`: Your application's client id. Can be omitted if provided on the client constructor.
384 |
385 | `prompt`: Controls how existing authorizations are handled, either consent or none (for passthrough scopes authorization is always required).
386 |
387 | `scope`: The scopes requested in your authorization url, can be either a space-delimited string of scopes, or an array of strings containing scopes.
388 |
389 | `redirectUri`: Your URL redirect uri. Can be omitted if provided on the client constructor.
390 |
391 | `responseType`: The response type, either code or token (token is for client-side web applications only). Defaults to code.
392 |
393 | `state`: A unique cryptographically secure string (https://discord.com/developers/docs/topics/oauth2#state-and-security).
394 |
395 | `permissions`: The permissions number for the bot invite (only with bot scope) (https://discord.com/developers/docs/topics/permissions).
396 |
397 | `integrationType`: The installation context for the authorization, either 0 for guild or 1 for user install (only with applications.commands scope) (https://discord.com/developers/docs/resources/application#installation-context).
398 |
399 | `guildId`: The guild id to pre-fill the bot invite (only with bot scope).
400 |
401 | `disableGuildSelect`: Disallows the user from changing the guild for the bot invite, either true or false (only with bot scope).
402 |
403 | ```js
404 | const crypto = require('crypto')
405 | const DiscordOauth2 = require("discord-oauth2");
406 | const oauth = new DiscordOauth2({
407 | clientId: "332269999912132097",
408 | clientSecret: "937it3ow87i4ery69876wqire",
409 | redirectUri: "http://localhost/callback",
410 | });
411 |
412 | const url = oauth.generateAuthUrl({
413 | scope: ["identify", "guilds"],
414 | state: crypto.randomBytes(16).toString("hex"), // Be aware that randomBytes is sync if no callback is provided
415 | });
416 |
417 | console.log(url);
418 | // https://discord.com/api/oauth2/authorize?client_id=332269999912132097&redirect_uri=http%3A%2F%2Flocalhost%2Fcallback&response_type=code&scope=identify%20guilds&state=132054f372bfca771de3dfe54aaacece
419 |
420 | ```
421 |
422 | # Debugging
423 |
424 | By default when you log an error to the console, it will look something like this `DiscordHTTPError: 400 Bad Request on POST /api/v7/oauth2/token` followed by a very long stack trace what most of the times won't be useful (if you already know where the function is called).
425 |
426 | To easily debug any issues you are having, you can access the following properties of the error object thrown:
427 |
428 | `req`: The HTTP request sent to discord.
429 |
430 | `res`: The HTTP response sent from discord to our request.
431 |
432 | `code`: If the error is a `DiscordHTTPError`, it will be the HTTP status code of the response (same as `res.statusCode`).
433 | If the error is a `DiscordRESTError`, it will be a [Discord API JSON error code](https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes).
434 |
435 | `response`: An object containing properties that describe the error.
436 | If the error is a `DiscordHTTPError`, the object will have the `error` and `error_description` properties.
437 | If the error is a `DiscordRESTError`, the object will have the `message` and `code` (JSON error code. See `code`.) properties.
438 |
439 | `message`: If the error is a `DiscordHTTPError`, it will be a string including the status of the HTTP request and the endpoint used.
440 | If the error is a `DiscordRESTError`, it will be a string including the error code and it's meaning.
441 |
442 | `stack`: The error stack trace.
443 |
444 | ```js
445 | // error.response for DiscordRESTError
446 | {
447 | message: 'Missing Permissions',
448 | code: 50013
449 | }
450 | ```
451 |
452 | ```js
453 | // error.response for DiscordHTTPError
454 | {
455 | error: 'invalid_request',
456 | error_description: 'Invalid "code" in request.'
457 | }
458 | ```
459 |
460 | # Contributing
461 |
462 | All contributions are welcome.
463 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from "events";
2 | import { ClientRequest, IncomingHttpHeaders, IncomingMessage } from "http";
3 |
4 | declare namespace OAuth {
5 | export interface User {
6 | id: string;
7 | username: string;
8 | global_name: string | null;
9 | discriminator: string;
10 | avatar: string | null | undefined;
11 | mfa_enabled?: boolean;
12 | banner?: string | null | undefined;
13 | accent_color?: string | null | undefined;
14 | locale?: string;
15 | verified?: boolean;
16 | email?: string | null | undefined;
17 | flags?: number;
18 | premium_type?: number;
19 | public_flags?: number;
20 | }
21 |
22 | export interface Member {
23 | user?: User;
24 | nick: string | null | undefined;
25 | roles: string[];
26 | joined_at: number;
27 | premium_since?: number | null | undefined;
28 | deaf: boolean;
29 | mute: boolean;
30 | pending?: boolean;
31 | is_pending?: boolean;
32 | communication_disabled_until?: string | null;
33 | }
34 |
35 | // This is not accurate as discord sends a partial object
36 | export interface Integration {
37 | id: string;
38 | name: string;
39 | type: string;
40 | enabled: boolean;
41 | syncing: boolean;
42 | role_id: string;
43 | enable_emoticons?: boolean;
44 | expire_behavior: 0 | 1;
45 | expire_grace_period: number;
46 | user?: User;
47 | account: {
48 | id: string;
49 | name: string;
50 | };
51 | synced_at: number;
52 | subscriber_count: number;
53 | revoked: boolean;
54 | application?: IntegrationApplication;
55 | }
56 |
57 | export interface Connection {
58 | id: string;
59 | name: string;
60 | type: string;
61 | revoked?: string;
62 | integrations?: Integration[];
63 | verified: boolean;
64 | friend_sync: boolean;
65 | show_activity: boolean;
66 | visibility: 0 | 1;
67 | }
68 |
69 | export interface IntegrationApplication {
70 | id: string;
71 | name: string;
72 | icon: string | null | undefined;
73 | description: string;
74 | bot?: User;
75 | }
76 |
77 | export interface PartialApplication {
78 | id: string;
79 | name: string;
80 | icon: string | null | undefined;
81 | description: string;
82 | hook?: boolean | null | undefined;
83 | bot_public: boolean;
84 | bot_require_code_grant: boolean;
85 | verify_key: string;
86 | }
87 |
88 | export interface TokenRequestResult {
89 | access_token: string;
90 | token_type: string;
91 | expires_in: number;
92 | refresh_token: string;
93 | scope: string;
94 | webhook?: Webhook;
95 | guild?: Guild;
96 | }
97 |
98 | export interface AuthorizationInformation {
99 | application: PartialApplication;
100 | scopes: string[];
101 | expires: string;
102 | user?: User;
103 | }
104 |
105 | export interface PartialGuild {
106 | id: string;
107 | name: string;
108 | icon: string | null;
109 | owner?: boolean;
110 | permissions?: string;
111 | features: string[];
112 | approximate_member_count?: number;
113 | approximate_presence_count?: number;
114 | }
115 |
116 | export interface Webhook {
117 | type: boolean;
118 | id: string;
119 | name: string;
120 | avatar: string | null;
121 | channel_id: string;
122 | guild_id: string;
123 | application_id: string;
124 | token: string;
125 | url: string;
126 | }
127 |
128 | export interface Guild {
129 | id: string;
130 | name: string;
131 | icon: string | null;
132 | owner_id: string;
133 | splash: string | null;
134 | discovery_splash: string | null;
135 | afk_channel_id: string | null;
136 | afk_timeout: number;
137 | widget_enabled?: boolean;
138 | widget_channel_id?: string | null;
139 | verification_level: number;
140 | default_message_notifications: number;
141 | explicit_content_filter: number;
142 | roles: Role[];
143 | emojis: Emoji[];
144 | features: string[];
145 | mfa_level: number;
146 | application_id: string | null;
147 | system_channel_id: string | null;
148 | system_channel_flags: number;
149 | rules_channel_id: string | null;
150 | max_presences?: number | null;
151 | max_members?: number;
152 | vanity_url_code: string | null;
153 | description: string | null;
154 | banner: string | null;
155 | premium_tier: number;
156 | premium_subscription_count?: number;
157 | preferred_locale: string;
158 | public_updates_channel_id: string | null;
159 | max_video_channel_users?: number;
160 | max_stage_video_channel_users?: number;
161 | nsfw?: boolean | null | undefined;
162 | nsfw_level: number;
163 | stickers?: Sticker[];
164 | premium_progress_bar_enabled: boolean;
165 | safety_alerts_channel_id: string | null;
166 | }
167 |
168 | export interface Role {
169 | id: string;
170 | name: string;
171 | color: number;
172 | hoist: boolean;
173 | icon?: string | null;
174 | unicode_emoji?: string | null;
175 | position: number;
176 | permissions: string;
177 | managed: boolean;
178 | mentionable: boolean;
179 | flags: number;
180 | tags?: RoleTags;
181 | }
182 |
183 | export interface Emoji {
184 | id: string | null;
185 | name: string | null;
186 | roles?: string[];
187 | user?: User;
188 | require_colons?: boolean;
189 | managed?: boolean;
190 | animated?: boolean;
191 | available?: boolean;
192 | }
193 |
194 | export interface Sticker {
195 | id: string;
196 | name: string;
197 | description: string | null;
198 | tags: string;
199 | type: number;
200 | format_type: number;
201 | available?: boolean;
202 | guild_id: string;
203 | }
204 |
205 | export interface RoleTags {
206 | bot_id?: string;
207 | integration_id?: string;
208 | premium_subscriber?: null;
209 | subscription_listing_id?: string;
210 | available_for_purchase?: null;
211 | guild_connections?: null;
212 | }
213 |
214 | export interface HTTPResponse {
215 | code: number;
216 | message: string;
217 | }
218 |
219 | export class DiscordHTTPError extends Error {
220 | code: number;
221 | headers: IncomingHttpHeaders;
222 | name: "DiscordHTTPError";
223 | req: ClientRequest;
224 | res: IncomingMessage;
225 | response: HTTPResponse;
226 | constructor(
227 | req: ClientRequest,
228 | res: IncomingMessage,
229 | response: HTTPResponse,
230 | stack: string,
231 | );
232 | flattenErrors(errors: HTTPResponse, keyPrefix?: string): string[];
233 | }
234 |
235 | export class DiscordRESTError extends Error {
236 | code: number;
237 | headers: IncomingHttpHeaders;
238 | name: string;
239 | req: ClientRequest;
240 | res: IncomingMessage;
241 | response: HTTPResponse;
242 | constructor(
243 | req: ClientRequest,
244 | res: IncomingMessage,
245 | response: HTTPResponse,
246 | stack: string,
247 | );
248 | flattenErrors(errors: HTTPResponse, keyPrefix?: string): string[];
249 | }
250 | }
251 |
252 | declare class OAuth extends EventEmitter {
253 | constructor(opts?: {
254 | version?: string;
255 | clientId?: string;
256 | redirectUri?: string;
257 | credentials?: string;
258 | clientSecret?: string;
259 | requestTimeout?: number;
260 | latencyThreshold?: number;
261 | ratelimiterOffset?: number;
262 | });
263 | on(event: "debug" | "warn", listener: (message: string) => void): this;
264 | tokenRequest(opts: {
265 | code?: string;
266 | scope: string[] | string;
267 | clientId?: string;
268 | grantType: "authorization_code" | "refresh_token";
269 | redirectUri?: string;
270 | refreshToken?: string;
271 | clientSecret?: string;
272 | }): Promise;
273 | revokeToken(access_token: string, credentials?: string): Promise;
274 | getCurrentAuthorizationInformation(access_token: string): Promise;
275 | getUser(access_token: string): Promise;
276 | getUserGuilds(access_token: string, opts?: {
277 | before?: string;
278 | after?: string;
279 | limit?: number;
280 | withCounts?: boolean;
281 | }): Promise;
282 | getUserConnections(access_token: string): Promise;
283 | addMember(opts: {
284 | deaf?: boolean;
285 | mute?: boolean;
286 | roles?: string[];
287 | nickname?: string;
288 | userId: string;
289 | guildId: string;
290 | botToken: string;
291 | accessToken: string;
292 | }): Promise;
293 | getGuildMember(
294 | access_token: string,
295 | guildId: string,
296 | ): Promise;
297 | generateAuthUrl(opts: {
298 | scope: string[] | string;
299 | state?: string;
300 | clientId?: string;
301 | prompt?: "consent" | "none";
302 | redirectUri?: string;
303 | responseType?: "code" | "token";
304 | permissions?: string;
305 | integrationType?: 1 | 0;
306 | guildId?: string;
307 | disableGuildSelect?: boolean;
308 | }): string;
309 | }
310 | export = OAuth;
311 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | const OAuth = require("./lib/oauth");
3 | OAuth.DiscordHTTPError = require("./lib/eris/errors/DiscordHTTPError");
4 | OAuth.DiscordRESTError = require("./lib/eris/errors/DiscordRESTError");
5 | module.exports = OAuth;
6 |
--------------------------------------------------------------------------------
/lib/eris/errors/DiscordHTTPError.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2016-2020 abalabahaha
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | "use strict";
24 |
25 | class DiscordHTTPError extends Error {
26 | constructor(req, res, response, stack) {
27 | super();
28 |
29 | Object.defineProperty(this, "req", {
30 | enumerable: false,
31 | value: req,
32 | writable: false,
33 | });
34 | Object.defineProperty(this, "res", {
35 | enumerable: false,
36 | value: res,
37 | writable: false,
38 | });
39 | Object.defineProperty(this, "response", {
40 | enumerable: false,
41 | value: response,
42 | writable: false,
43 | });
44 |
45 | Object.defineProperty(this, "code", {
46 | value: res.statusCode,
47 | writable: false,
48 | });
49 | let message = `${this.name}: ${res.statusCode} ${res.statusMessage} on ${req.method} ${req.path}`;
50 | const errors = this.flattenErrors(response);
51 | if (errors.length > 0) {
52 | message += "\n " + errors.join("\n ");
53 | }
54 | Object.defineProperty(this, "message", {
55 | value: message,
56 | writable: false,
57 | });
58 |
59 | if (stack) {
60 | Object.defineProperty(this, "stack", {
61 | value: this.message + "\n" + stack,
62 | writable: false,
63 | });
64 | }
65 | else {
66 | Error.captureStackTrace(this, DiscordHTTPError);
67 | }
68 | }
69 |
70 | get name() {
71 | return this.constructor.name;
72 | }
73 |
74 | flattenErrors(errors, keyPrefix = "") {
75 | let messages = [];
76 | for (const fieldName in errors) {
77 | if (!errors.hasOwnProperty(fieldName) || fieldName === "message" || fieldName === "code") {
78 | continue;
79 | }
80 | if (Array.isArray(errors[fieldName])) {
81 | messages = messages.concat(errors[fieldName].map((str) => `${keyPrefix + fieldName}: ${str}`));
82 | }
83 | }
84 | return messages;
85 | }
86 | }
87 |
88 | module.exports = DiscordHTTPError;
89 |
--------------------------------------------------------------------------------
/lib/eris/errors/DiscordRESTError.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2016-2020 abalabahaha
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | "use strict";
24 |
25 | class DiscordRESTError extends Error {
26 | constructor(req, res, response, stack) {
27 | super();
28 |
29 | Object.defineProperty(this, "req", {
30 | enumerable: false,
31 | value: req,
32 | writable: false,
33 | });
34 | Object.defineProperty(this, "res", {
35 | enumerable: false,
36 | value: res,
37 | writable: false,
38 | });
39 | Object.defineProperty(this, "response", {
40 | enumerable: false,
41 | value: response,
42 | writable: false,
43 | });
44 |
45 | Object.defineProperty(this, "code", {
46 | value: +response.code || -1,
47 | writable: false,
48 | });
49 |
50 | let message = this.name + ": " + (response.message || "Unknown error");
51 | if (response.errors) {
52 | message += "\n " + this.flattenErrors(response.errors).join("\n ");
53 | }
54 | else {
55 | const errors = this.flattenErrors(response);
56 | if (errors.length > 0) {
57 | message += "\n " + errors.join("\n ");
58 | }
59 | }
60 | Object.defineProperty(this, "message", {
61 | value: message,
62 | writable: false,
63 | });
64 |
65 | if (stack) {
66 | Object.defineProperty(this, "stack", {
67 | value: this.message + "\n" + stack,
68 | writable: false,
69 | });
70 | }
71 | else {
72 | Error.captureStackTrace(this, DiscordRESTError);
73 | }
74 | }
75 |
76 | get name() {
77 | return `${this.constructor.name} [${this.code}]`;
78 | }
79 |
80 | flattenErrors(errors, keyPrefix = "") {
81 | let messages = [];
82 | for (const fieldName in errors) {
83 | if (!errors.hasOwnProperty(fieldName) || fieldName === "message" || fieldName === "code") {
84 | continue;
85 | }
86 | if (errors[fieldName]._errors) {
87 | messages = messages.concat(errors[fieldName]._errors.map((obj) => `${keyPrefix + fieldName}: ${obj.message}`));
88 | }
89 | else if (Array.isArray(errors[fieldName])) {
90 | messages = messages.concat(errors[fieldName].map((str) => `${keyPrefix + fieldName}: ${str}`));
91 | }
92 | else if (typeof errors[fieldName] === "object") {
93 | messages = messages.concat(this.flattenErrors(errors[fieldName], keyPrefix + fieldName + "."));
94 | }
95 | }
96 | return messages;
97 | }
98 | }
99 |
100 | module.exports = DiscordRESTError;
101 |
--------------------------------------------------------------------------------
/lib/eris/rest/RequestHandler.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2016-2021 abalabahaha
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | "use strict";
24 |
25 | const DiscordHTTPError = require("../errors/DiscordHTTPError");
26 | const DiscordRESTError = require("../errors/DiscordRESTError");
27 | const HTTPS = require("https");
28 | const SequentialBucket = require("../util/SequentialBucket");
29 | const Zlib = require("zlib");
30 | const EventEmitter = require("events");
31 |
32 | /**
33 | * Handles API requests
34 | */
35 | class RequestHandler extends EventEmitter {
36 | constructor(options) {
37 | super();
38 |
39 | this.options = options = Object.assign({
40 | agent: null,
41 | baseURL: "/api/v9",
42 | domain: "discord.com",
43 | disableLatencyCompensation: false,
44 | latencyThreshold: 30000,
45 | ratelimiterOffset: 0,
46 | requestTimeout: 15000,
47 | }, options);
48 |
49 | this.userAgent = `Discord-OAuth2 (https://github.com/reboxer/discord-oauth2, ${require("../../../package.json").version})`;
50 | this.ratelimits = {};
51 | this.latencyRef = {
52 | latency: this.options.ratelimiterOffset,
53 | raw: new Array(10).fill(this.options.ratelimiterOffset),
54 | timeOffset: 0,
55 | timeOffsets: new Array(10).fill(0),
56 | lastTimeOffsetCheck: 0,
57 | };
58 | this.globalBlock = false;
59 | this.readyQueue = [];
60 | }
61 |
62 | globalUnblock() {
63 | this.globalBlock = false;
64 | while (this.readyQueue.length > 0) {
65 | this.readyQueue.shift()();
66 | }
67 | }
68 |
69 | // We need this for the Add Guild Member endpoint
70 | routefy(url) {
71 | return url.replace(/\/([a-z-]+)\/(?:[0-9]{17,19})/g, function(match, p) {
72 | return p === "guilds" ? match : `/${p}/:id`;
73 | });
74 | }
75 |
76 | /**
77 | * Make an API request
78 | * @arg {String} method Uppercase HTTP method
79 | * @arg {String} url URL of the endpoint
80 | * @arg {Boolean} [auth] Whether to add the Authorization header and token or not
81 | * @arg {String} [contentType] Content-Type header
82 | * @arg {Number} [attempts=0] Number of attempts
83 | * @arg {Object} [body] Request payload
84 | * @returns {Promise