diff --git a/node_modules/discord-oauth2/.eslintignore b/node_modules/discord-oauth2/.eslintignore new file mode 100644 index 0000000..08b2553 --- /dev/null +++ b/node_modules/discord-oauth2/.eslintignore @@ -0,0 +1 @@ +node_modules diff --git a/node_modules/discord-oauth2/.eslintrc.json b/node_modules/discord-oauth2/.eslintrc.json new file mode 100644 index 0000000..5209531 --- /dev/null +++ b/node_modules/discord-oauth2/.eslintrc.json @@ -0,0 +1,35 @@ +{ + "env": { + "es6": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 2020 + }, + "extends": "eslint:recommended", + "rules": { + "semi": "error", + "no-var": "error", + "prefer-const": "error", + "keyword-spacing": "error", + "eqeqeq": "error", + "eol-last": "error", + "brace-style": ["error", "stroustrup"], + "comma-dangle": ["error", "always-multiline"], + "object-curly-spacing": ["error", "always"], + + "quotes": ["error", "double", { + "allowTemplateLiterals": true + }], + + "indent": ["error", "tab", { + "SwitchCase": 1, + "flatTernaryExpressions": true + }], + + "object-curly-newline": ["error", { + "ExportDeclaration": "never", + "ImportDeclaration": "always" + }] + } +} diff --git a/node_modules/discord-oauth2/.github/ISSUE_TEMPLATE/can-t-get-it-to-work.md b/node_modules/discord-oauth2/.github/ISSUE_TEMPLATE/can-t-get-it-to-work.md new file mode 100644 index 0000000..652977c --- /dev/null +++ b/node_modules/discord-oauth2/.github/ISSUE_TEMPLATE/can-t-get-it-to-work.md @@ -0,0 +1,21 @@ +--- +name: Can't get it to work +about: Use this template if you are having issues with the library (all issues opened + for issues with the library that don't follow this template will be ignored and + closed). +title: '' +labels: '' +assignees: reboxer + +--- + +**Describe the error** +A clear and concise description of what the error is. + +**NodeJS version** +*Your NodeJS version* + +**Relevant code** +```js +// Put here the code you are using that is not working +``` diff --git a/node_modules/discord-oauth2/.github/workflows/ci.yml b/node_modules/discord-oauth2/.github/workflows/ci.yml new file mode 100644 index 0000000..487e168 --- /dev/null +++ b/node_modules/discord-oauth2/.github/workflows/ci.yml @@ -0,0 +1,18 @@ +name: Run ESLint + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + lint: + name: Run ESlint + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: yarn run lint diff --git a/node_modules/discord-oauth2/LICENSE b/node_modules/discord-oauth2/LICENSE new file mode 100644 index 0000000..03da1c4 --- /dev/null +++ b/node_modules/discord-oauth2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 reboxer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/discord-oauth2/README.md b/node_modules/discord-oauth2/README.md new file mode 100644 index 0000000..d931647 --- /dev/null +++ b/node_modules/discord-oauth2/README.md @@ -0,0 +1,358 @@ +# discord-oauth2 [![NPM version](https://img.shields.io/npm/v/discord-oauth2.svg?style=flat-square)](https://www.npmjs.com/package/discord-oauth2) + +A really simple to use module to use discord's OAuth2 API. + +Please check out discord's OAuth2 documentation: https://discord.com/developers/docs/topics/oauth2 + +### Installing + +```bash +npm install discord-oauth2 +``` + +# Class constructor + +One parameter is passed to the class constructor: + +### `Options` + +Since the module uses a modified version of [Eris](https://github.com/abalabahaha/eris) request handler, it takes the same options, all of them default to the default Eris Client options if no options are passed. + +Request handler options: +``` +requestTimeout: A number of milliseconds before requests are considered timed out. + +latencyThreshold: The average request latency at which the RequestHandler will start emitting latency errors. + +ratelimiterOffset: A number of milliseconds to offset the ratelimit timing calculations by. + +``` + +Others, you can pass these options to the class constructor so you don't have to pass them each time you call a function: +``` +version: The Discord API version to use. Defaults to "v7". + +clientId: Your application's client id. + +clientSecret: Your application's client secret. + +redirectUri: Your URL redirect uri. + +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. +``` + +# Events + +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. + +There are only two events, `debug` and `warn`. + +# Methods + +### `tokenRequest()` + +Only takes an object with the following properties: + +`clientId`: Your application's client id. Can be omitted if provided on the client constructor. + +`clientSecret`: Your application's client secret. Can be omitted if provided on the client constructor. + +`scope`: The scopes requested in your authorization url, can be either a space-delimited string of scopes, or an array of strings containing scopes. + +`redirectUri`: Your URL redirect uri. Can be omitted if provided on the client constructor. + +`grantType`: The grant type to set for the request, either authorization_code or refresh_token. + +`code`: The code from the querystring (grantType `authorization_code` only). + +`refreshToken`: The user's refresh token (grantType `refresh_token` only). + + +Returns a promise which resolves in an object with the access token. + +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. + +```js +const DiscordOauth2 = require("discord-oauth2"); +const oauth = new DiscordOauth2(); + +oauth.tokenRequest({ + clientId: "332269999912132097", + clientSecret: "937it3ow87i4ery69876wqire", + + code: "query code", + scope: "identify guilds", + grantType: "authorization_code", + + redirectUri: "http://localhost/callback", +}).then(console.log) +``` + +Using class constructor options, array of scopes and grantType refresh_token: + +```js +const DiscordOauth2 = require("discord-oauth2"); +const oauth = new DiscordOauth2({ + clientId: "332269999912132097", + clientSecret: "937it3ow87i4ery69876wqire", + redirectUri: "http://localhost/callback", +}); + +oauth.tokenRequest({ + // clientId, clientSecret and redirectUri are omitted, as they were already set on the class constructor + refreshToken: "D43f5y0ahjqew82jZ4NViEr2YafMKhue", + grantType: "refresh_token", + scope: ["identify", "guilds"], +}); + +// On successful request both requesting and refreshing an access token return the same object +/* + { + "access_token": "6qrZcUqja7812RVdnEKjpzOL4CvHBFG", + "token_type": "Bearer", + "expires_in": 604800, + "refresh_token": "D43f5y0ahjqew82jZ4NViEr2YafMKhue", + "scope": "identify guilds" + } +*/ +``` + +### `revokeToken()` + +Takes two parameters, the first one is the access_token from the user, the second is a Base64 encoding of the UTF-8 encoded credentials string of your application. + +Returns a promise which resolves in an empty object if successful. + +```js +const DiscordOauth2 = require("discord-oauth2"); +const oauth = new DiscordOauth2(); + +const clientID = "332269999912132097"; +const client_secret = "937it3ow87i4ery69876wqire"; +const access_token = "6qrZcUqja7812RVdnEKjpzOL4CvHBFG"; + +// You must encode your client ID along with your client secret including the colon in between +const credentials = Buffer.from(`${clientID}:${client_secret}`).toString("base64"); // MzMyMjY5OTk5OTEyMTMyMDk3OjkzN2l0M293ODdpNGVyeTY5ODc2d3FpcmU= + +oauth.revokeToken(access_token, credentials).then(console.log); // {} +``` + +### `getUser()` + +Only takes one parameter which is the user's access token. + +Returns the [user](https://discord.com/developers/docs/resources/user#user-object) object of the requester's account, this requires the `identify` scope, which will return the object without an email, and optionally the `email` scope, which returns the object with an email. + +```js +const DiscordOauth2 = require("discord-oauth2"); +const oauth = new DiscordOauth2(); + +const access_token = "6qrZcUqja7812RVdnEKjpzOL4CvHBFG"; + +oauth.getUser(access_token).then(console.log); +/* + { + username: '1337 Krew', + locale: 'en-US', + mfa_enabled: true, + flags: 128, + avatar: '8342729096ea3675442027381ff50dfe', + discriminator: '4421', + id: '80351110224678912' + } +*/ +``` + +### `getUserGuilds()` + +Only takes one parameter which is the user's access token. + +Returns a list of partial [guild](https://discord.com/developers/docs/resources/guild#guild-object) objects the current user is a member of. Requires the `guilds` scope. + +```js +const DiscordOauth2 = require("discord-oauth2"); +const oauth = new DiscordOauth2(); + +const access_token = "6qrZcUqja7812RVdnEKjpzOL4CvHBFG"; + +oauth.getUserGuilds(access_token).then(console.log); +/* + { + "id": "80351110224678912", + "name": "1337 Krew", + "icon": "8342729096ea3675442027381ff50dfe", + "owner": true, + "permissions": 36953089, + "permissions_new": "36953089" + } +*/ +``` + +### `getUserConnections()` + +Only takes one parameter which is the user's access token. + +Returns a list of [connection](https://discord.com/developers/docs/resources/user#connection-object) objects. Requires the `connections` OAuth2 scope. + +```js +const DiscordOauth2 = require("discord-oauth2"); +const oauth = new DiscordOauth2(); + +const access_token = "6qrZcUqja7812RVdnEKjpzOL4CvHBFG"; + +oauth.getUserConnections(access_token).then(console.log); +/* + [ { verified: true, + name: 'epicusername', + show_activity: true, + friend_sync: false, + type: 'twitch', + id: '31244565', + visibility: 1 } ] +*/ +``` + +### `addMember()` + +Force join a user to a guild (server). + +Takes an object with the following properties: + +`accessToken`: The user access token. + +`botToken`: The token of the bot used to authenticate. + +`guildId`: The ID of the guild to join. + +`userId`: The ID of the user to be added to the guild. + +Optional properties (the above ones are required): + +`nickname`: Value to set users nickname to. + +`roles`: Array of role ids the member is assigned. + +`mute`: Whether the user is muted in voice channels. + +`deaf`: Whether the user is deafened in voice channels. + +Returns a member object if the user wasn't part of the guild, else, returns an empty string (length 0). + +```js +const DiscordOauth2 = require("discord-oauth2"); +const oauth = new DiscordOauth2(); + +oauth.addMember({ + accessToken: "2qRZcUqUa9816RVnnEKRpzOL2CvHBgF", + botToken: "NDgyMjM4ODQzNDI1MjU5NTIz.XK93JQ.bnLsc71_DGum-Qnymb4T5F6kGY8", + guildId: "216488324594438692", + userId: "80351110224678912", + + nickname: "george michael", + roles: ["624615851966070786"], + mute: true, + deaf: true, +}).then(console.log); // Member object or empty string + +/* + { + nick: 'george michael', + user: { + username: 'some username', + discriminator: '0001', + id: '421610529323943943', + avatar: null + }, + roles: [ '324615841966570766' ], + premium_since: null, + deaf: true, + mute: true, + joined_at: '2019-09-20T14:44:12.603123+00:00' + } +*/ +``` + +### `generateAuthUrl` + +Dynamically generate an OAuth2 URL. + +Takes an object with the following properties: + +`clientId`: Your application's client id. Can be omitted if provided on the client constructor. + +`prompt`: Controls how existing authorizations are handled, either consent or none (for passthrough scopes authorization is always required). + +`scope`: The scopes requested in your authorization url, can be either a space-delimited string of scopes, or an array of strings containing scopes. + +`redirectUri`: Your URL redirect uri. Can be omitted if provided on the client constructor. + +`responseType`: The response type, either code or token (token is for client-side web applications only). Defaults to code. + +`state`: A unique cryptographically secure string (https://discord.com/developers/docs/topics/oauth2#state-and-security). + +`permissions`: The permissions number for the bot invite (only with bot scope) (https://discord.com/developers/docs/topics/permissions). + +`guildId`: The guild id to pre-fill the bot invite (only with bot scope). + +`disableGuildSelect`: Disallows the user from changing the guild for the bot invite, either true or false (only with bot scope). + +```js +const crypto = require('crypto') +const DiscordOauth2 = require("discord-oauth2"); +const oauth = new DiscordOauth2({ + clientId: "332269999912132097", + clientSecret: "937it3ow87i4ery69876wqire", + redirectUri: "http://localhost/callback", +}); + +const url = oauth.generateAuthUrl({ + scope: ["identify", "guilds"], + state: crypto.randomBytes(16).toString("hex"), // Be aware that randomBytes is sync if no callback is provided +}); + +console.log(url); +// https://discord.com/api/oauth2/authorize?client_id=332269999912132097&redirect_uri=http%3A%2F%2Flocalhost%2Fcallback&response_type=code&scope=identify%20guilds&state=132054f372bfca771de3dfe54aaacece + +``` + +# Debugging + +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). + +To easily debug any issues you are having, you can access the following properties of the error object thrown: + +`req`: The HTTP request sent to discord. + +`res`: The HTTP response sent from discord to our request. + +`code`: If the error is a `DiscordHTTPError`, it will be the HTTP status code of the response (same as `res.statusCode`).
+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). + +`response`: An object containing properties that describe the error.
+If the error is a `DiscordHTTPError`, the object will have the `error` and `error_description` properties.
+If the error is a `DiscordRESTError`, the object will have the `message` and `code` (JSON error code. See `code`.) properties. + +`message`: If the error is a `DiscordHTTPError`, it will be a string including the status of the HTTP request and the endpoint used.
+If the error is a `DiscordRESTError`, it will be a string including the error code and it's meaning. + +`stack`: The error stack trace. + +```js +// error.response for DiscordRESTError +{ + message: 'Missing Permissions', + code: 50013 +} +``` + +```js +// error.response for DiscordHTTPError +{ + error: 'invalid_request', + error_description: 'Invalid "code" in request.' +} +``` + +# Contributing + +All contributions are welcome. diff --git a/node_modules/discord-oauth2/index.d.ts b/node_modules/discord-oauth2/index.d.ts new file mode 100644 index 0000000..5a56091 --- /dev/null +++ b/node_modules/discord-oauth2/index.d.ts @@ -0,0 +1,136 @@ +import { EventEmitter } from "events"; + +interface User { + id: string; + username: string; + discriminator: string; + avatar: string | null | undefined; + mfa_enabled?: true; + locale?: string; + verified?: boolean; + email?: string | null | undefined; + flags?: number; + premium_type?: number; + public_flags?: number; +} + +interface Member { + user?: User; + nick: string | null | undefined; + roles: string[]; + joined_at: number; + premium_since?: number | null | undefined; + deaf: boolean; + mute: boolean; +} + +// This is not accurate as discord sends a partial object +interface Integration { + id: string; + name: string; + type: string; + enabled: boolean; + syncing: boolean; + role_id: string; + enable_emoticons?: boolean; + expire_behavior: 0 | 1; + expire_grace_period: number; + user?: User; + account: { + id: string; + name: string; + }; + synced_at: number; + subscriber_count: number; + revoked: boolean; + application?: Application; +} + +interface Connection { + id: string; + name: string; + type: string; + revoked?: string; + integrations?: Integration[]; + verified: boolean; + friend_sync: boolean; + show_activity: boolean; + visibility: 0 | 1; +} + +interface Application { + id: string; + name: string; + icon: string | null | undefined; + description: string; + summary: string; + bot?: User; +} + +interface TokenRequestResult { + access_token: string; + token_type: string; + expires_in: number; + refresh_token: string; + scope: string; +} + +interface PartialGuild { + id: string; + name: string; + icon: string | null | undefined; + owner?: boolean; + permissions?: number; + features: string[]; + permissions_new?: string; +} + +declare class OAuth extends EventEmitter { + constructor(opts?: { + version?: string, + clientId?: string, + redirectUri?: string, + credentials?: string, + clientSecret?: string, + requestTimeout?: number, + latencyThreshold?: number, + ratelimiterOffset?: number, + }); + on(event: "debug" | "warn", listener: (message: string) => void): this; + tokenRequest(opts: { + code?: string, + scope: string[] | string, + clientId?: string, + grantType: "authorization_code" | "refresh_token", + redirectUri?: string, + refreshToken?: string, + clientSecret?: string, + }): Promise; + revokeToken(access_token: string, credentials?: string): Promise; + getUser(access_token: string): Promise; + getUserGuilds(access_token: string): Promise; + getUserConnections(access_token: string): Promise; + addMember(opts: { + deaf?: boolean, + mute?: boolean, + roles?: string[], + nickname?: string, + userId: string, + guildId: string, + botToken: string, + accessToken: string, + }): Promise; + generateAuthUrl(opts: { + scope: string[] | string, + state?: string, + clientId?: string, + prompt?: "consent" | "none", + redirectUri?: string, + responseType?: "code" | "token", + permissions?: number, + guildId?: string, + disableGuildSelect?: boolean, + }): string; +} + +export = OAuth; diff --git a/node_modules/discord-oauth2/index.js b/node_modules/discord-oauth2/index.js new file mode 100644 index 0000000..d0d8e44 --- /dev/null +++ b/node_modules/discord-oauth2/index.js @@ -0,0 +1,3 @@ +"use strict"; +const OAuth = require("./lib/oauth"); +module.exports = OAuth; diff --git a/node_modules/discord-oauth2/lib/eris/errors/DiscordHTTPError.js b/node_modules/discord-oauth2/lib/eris/errors/DiscordHTTPError.js new file mode 100644 index 0000000..b418448 --- /dev/null +++ b/node_modules/discord-oauth2/lib/eris/errors/DiscordHTTPError.js @@ -0,0 +1,91 @@ +/* + The MIT License (MIT) + + Copyright (c) 2016-2020 abalabahaha + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* eslint-disable no-prototype-builtins */ + +"use strict"; + +class DiscordHTTPError extends Error { + constructor(req, res, response, stack) { + super(); + + Object.defineProperty(this, "req", { + enumerable: false, + value: req, + writable: false, + }); + Object.defineProperty(this, "res", { + enumerable: false, + value: res, + writable: false, + }); + Object.defineProperty(this, "response", { + enumerable: false, + value: response, + writable: false, + }); + + Object.defineProperty(this, "code", { + value: res.statusCode, + writable: false, + }); + let message = `${this.name}: ${res.statusCode} ${res.statusMessage} on ${req.method} ${req.path}`; + const errors = this.flattenErrors(response); + if (errors.length > 0) { + message += "\n " + errors.join("\n "); + } + Object.defineProperty(this, "message", { + value: message, + writable: false, + }); + + if (stack) { + Object.defineProperty(this, "stack", { + value: this.message + "\n" + stack, + writable: false, + }); + } + else { + Error.captureStackTrace(this, DiscordHTTPError); + } + } + + get name() { + return this.constructor.name; + } + + flattenErrors(errors, keyPrefix = "") { + let messages = []; + for (const fieldName in errors) { + if (!errors.hasOwnProperty(fieldName) || fieldName === "message" || fieldName === "code") { + continue; + } + if (Array.isArray(errors[fieldName])) { + messages = messages.concat(errors[fieldName].map((str) => `${keyPrefix + fieldName}: ${str}`)); + } + } + return messages; + } +} + +module.exports = DiscordHTTPError; diff --git a/node_modules/discord-oauth2/lib/eris/errors/DiscordRESTError.js b/node_modules/discord-oauth2/lib/eris/errors/DiscordRESTError.js new file mode 100644 index 0000000..a7375d1 --- /dev/null +++ b/node_modules/discord-oauth2/lib/eris/errors/DiscordRESTError.js @@ -0,0 +1,103 @@ +/* + The MIT License (MIT) + + Copyright (c) 2016-2020 abalabahaha + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* eslint-disable no-prototype-builtins */ + +"use strict"; + +class DiscordRESTError extends Error { + constructor(req, res, response, stack) { + super(); + + Object.defineProperty(this, "req", { + enumerable: false, + value: req, + writable: false, + }); + Object.defineProperty(this, "res", { + enumerable: false, + value: res, + writable: false, + }); + Object.defineProperty(this, "response", { + enumerable: false, + value: response, + writable: false, + }); + + Object.defineProperty(this, "code", { + value: +response.code || -1, + writable: false, + }); + + let message = this.name + ": " + (response.message || "Unknown error"); + if (response.errors) { + message += "\n " + this.flattenErrors(response.errors).join("\n "); + } + else { + const errors = this.flattenErrors(response); + if (errors.length > 0) { + message += "\n " + errors.join("\n "); + } + } + Object.defineProperty(this, "message", { + value: message, + writable: false, + }); + + if (stack) { + Object.defineProperty(this, "stack", { + value: this.message + "\n" + stack, + writable: false, + }); + } + else { + Error.captureStackTrace(this, DiscordRESTError); + } + } + + get name() { + return `${this.constructor.name} [${this.code}]`; + } + + flattenErrors(errors, keyPrefix = "") { + let messages = []; + for (const fieldName in errors) { + if (!errors.hasOwnProperty(fieldName) || fieldName === "message" || fieldName === "code") { + continue; + } + if (errors[fieldName]._errors) { + messages = messages.concat(errors[fieldName]._errors.map((obj) => `${keyPrefix + fieldName}: ${obj.message}`)); + } + else if (Array.isArray(errors[fieldName])) { + messages = messages.concat(errors[fieldName].map((str) => `${keyPrefix + fieldName}: ${str}`)); + } + else if (typeof errors[fieldName] === "object") { + messages = messages.concat(this.flattenErrors(errors[fieldName], keyPrefix + fieldName + ".")); + } + } + return messages; + } +} + +module.exports = DiscordRESTError; diff --git a/node_modules/discord-oauth2/lib/eris/rest/RequestHandler.js b/node_modules/discord-oauth2/lib/eris/rest/RequestHandler.js new file mode 100644 index 0000000..a03261e --- /dev/null +++ b/node_modules/discord-oauth2/lib/eris/rest/RequestHandler.js @@ -0,0 +1,305 @@ +/* + The MIT License (MIT) + + Copyright (c) 2016-2020 abalabahaha + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +"use strict"; + +const DiscordHTTPError = require("../errors/DiscordHTTPError"); +const DiscordRESTError = require("../errors/DiscordRESTError"); +const HTTPS = require("https"); +const SequentialBucket = require("../util/SequentialBucket"); +const EventEmitter = require("events"); + +/** +* Handles API requests +*/ +class RequestHandler extends EventEmitter { + constructor(options) { + super(); + this.version = options.version; + this.userAgent = `Discord-OAuth2 (https://github.com/reboxer/discord-oauth2, ${require("../../../package.json").version})`; + this.ratelimits = {}; + this.requestTimeout = options.requestTimeout; + this.latencyThreshold = options.latencyThreshold; + this.latencyRef = { + latency: 500, + offset: options.ratelimiterOffset, + raw: new Array(10).fill(500), + timeOffset: 0, + timeOffsets: new Array(10).fill(0), + lastTimeOffsetCheck: 0, + }; + this.globalBlock = false; + this.readyQueue = []; + } + + globalUnblock() { + this.globalBlock = false; + while (this.readyQueue.length > 0) { + this.readyQueue.shift()(); + } + } + + // We need this for the Add Guild Member endpoint + routefy(url) { + return url.replace(/\/([a-z-]+)\/(?:[0-9]{17,19})/g, function(match, p) { + return p === "guilds" ? match : `/${p}/:id`; + }); + } + + /** + * Make an API request + * @arg {String} method Uppercase HTTP method + * @arg {String} url URL of the endpoint + * @arg {Object} options + * @arg {Object} [options.auth] + * @arg {String} [options.auth.type] The type of Authorization to use in the header, wheather Basic, Bearer or Bot + * @arg {String} [options.auth.creds] The credentials used for the authentication (bot or user access token), if Basic, a base64 string with application's credentials must be passed + * @arg {String} options.contentType The content type to set in the headers of the request + * @arg {Object} [body] Request payload + * @returns {Promise} Resolves with the returned JSON data + */ + request(method, url, body, options, _route, short) { + const route = _route || this.routefy(url, method); + + const _stackHolder = {}; // Preserve async stack + Error.captureStackTrace(_stackHolder); + + return new Promise((resolve, reject) => { + let attempts = 0; + + const actualCall = (cb) => { + const headers = { + "User-Agent": this.userAgent, + "Content-Type": options.contentType, + }; + let data; + try { + if (options.auth) { + headers["Authorization"] = `${options.auth.type} ${options.auth.creds}`; + } + if (headers["Content-Type"] === "application/json") { + data = JSON.stringify(body); + } + else { + data = body; + } + } + catch (err) { + cb(); + reject(err); + return; + } + + const req = HTTPS.request({ + method: method, + host: "discord.com", + path: `/api/${this.version}` + url, + headers: headers, + }); + + let reqError; + + req.once("abort", () => { + cb(); + reqError = reqError || new Error(`Request aborted by client on ${method} ${url}`); + reqError.req = req; + reject(reqError); + }).once("error", (err) => { + reqError = err; + req.abort(); + }); + + let latency = Date.now(); + + req.once("response", (resp) => { + latency = Date.now() - latency; + this.latencyRef.raw.push(latency); + this.latencyRef.latency = this.latencyRef.latency - ~~(this.latencyRef.raw.shift() / 10) + ~~(latency / 10); + + const headerNow = Date.parse(resp.headers["date"]); + if (this.latencyRef.lastTimeOffsetCheck < Date.now() - 5000) { + const timeOffset = ~~((this.latencyRef.lastTimeOffsetCheck = Date.now()) - headerNow); + if (this.latencyRef.timeOffset - this.latencyRef.latency >= this.latencyThreshold && timeOffset - this.latencyRef.latency >= this.latencyThreshold) { + this.emit("warn", new Error(`Your clock is ${this.latencyRef.timeOffset}ms behind Discord's server clock. Please check your connection and system time.`)); + } + this.latencyRef.timeOffset = ~~(this.latencyRef.timeOffset - this.latencyRef.timeOffsets.shift() / 10 + timeOffset / 10); + this.latencyRef.timeOffsets.push(timeOffset); + } + + resp.once("aborted", () => { + cb(); + reqError = reqError || new Error(`Request aborted by server on ${method} ${url}`); + reqError.req = req; + reject(reqError); + }); + + let response = ""; + + const _respStream = resp; + + _respStream.on("data", (str) => { + response += str; + }).on("error", (err) => { + reqError = err; + req.abort(); + }).once("end", () => { + const now = Date.now(); + + if (resp.headers["x-ratelimit-limit"]) { + this.ratelimits[route].limit = +resp.headers["x-ratelimit-limit"]; + } + + if (method !== "GET" && (resp.headers["x-ratelimit-remaining"] === undefined || resp.headers["x-ratelimit-limit"] === undefined) && this.ratelimits[route].limit !== 1) { + this.emit("debug", `Missing ratelimit headers for SequentialBucket(${this.ratelimits[route].remaining}/${this.ratelimits[route].limit}) with non-default limit\n` + + `${resp.statusCode} ${resp.headers["content-type"]}: ${method} ${route} | ${resp.headers["cf-ray"]}\n` + + "content-type = " + "\n" + + "x-ratelimit-remaining = " + resp.headers["x-ratelimit-remaining"] + "\n" + + "x-ratelimit-limit = " + resp.headers["x-ratelimit-limit"] + "\n" + + "x-ratelimit-reset = " + resp.headers["x-ratelimit-reset"] + "\n" + + "x-ratelimit-global = " + resp.headers["x-ratelimit-global"]); + } + + this.ratelimits[route].remaining = resp.headers["x-ratelimit-remaining"] === undefined ? 1 : +resp.headers["x-ratelimit-remaining"] || 0; + + if (resp.headers["retry-after"]) { + if (resp.headers["x-ratelimit-global"]) { + this.globalBlock = true; + setTimeout(() => this.globalUnblock(), +resp.headers["retry-after"] || 1); + } + else { + this.ratelimits[route].reset = (+resp.headers["retry-after"] || 1) + now; + } + } + else if (resp.headers["x-ratelimit-reset"]) { + if ((~route.lastIndexOf("/reactions/:id")) && (+resp.headers["x-ratelimit-reset"] * 1000 - headerNow) === 1000) { + this.ratelimits[route].reset = Math.max(now + 250 - this.latencyRef.timeOffset, now); + } + else { + this.ratelimits[route].reset = Math.max(+resp.headers["x-ratelimit-reset"] * 1000 - this.latencyRef.timeOffset, now); + } + } + else { + this.ratelimits[route].reset = now; + } + + if (resp.statusCode !== 429) { + this.emit("debug", `${body && body.content} ${now} ${route} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${this.ratelimits[route].reset} (${this.ratelimits[route].reset - now}ms left)`); + } + + if (resp.statusCode >= 300) { + if (resp.statusCode === 429) { + this.emit("debug", `${resp.headers["x-ratelimit-global"] ? "Global" : "Unexpected"} 429 (╯°□°)╯︵ ┻━┻: ${response}\n${body && body.content} ${now} ${route} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${this.ratelimits[route].reset} (${this.ratelimits[route].reset - now}ms left)`); + if (resp.headers["retry-after"]) { + setTimeout(() => { + cb(); + this.request(method, url, body, options, route, true).then(resolve).catch(reject); + }, +resp.headers["retry-after"]); + return; + } + else { + cb(); + this.request(method, url, body, options, route, true).then(resolve).catch(reject); + return; + } + } + else if (resp.statusCode === 502 && ++attempts < 4) { + this.emit("debug", "A wild 502 appeared! Thanks CloudFlare!"); + setTimeout(() => { + this.request(method, url, body, options, route, true).then(resolve).catch(reject); + }, Math.floor(Math.random() * 1900 + 100)); + return cb(); + } + cb(); + + if (response.length > 0) { + if (resp.headers["content-type"] === "application/json") { + try { + response = JSON.parse(response); + } + catch (err) { + reject(err); + return; + } + } + } + + let { stack } = _stackHolder; + if (stack.startsWith("Error\n")) { + stack = stack.substring(6); + } + let err; + if (response.code) { + err = new DiscordRESTError(req, resp, response, stack); + } + else { + err = new DiscordHTTPError(req, resp, response, stack); + } + reject(err); + return; + } + + if (response.length > 0) { + if (resp.headers["content-type"] === "application/json") { + try { + response = JSON.parse(response); + } + catch (err) { + cb(); + reject(err); + return; + } + } + } + + cb(); + resolve(response); + }); + }); + + req.setTimeout(this.requestTimeout, () => { + reqError = new Error(`Request timed out (>${this.requestTimeout}ms) on ${method} ${url}`); + req.abort(); + }); + + req.end(data); + }; + + if (this.globalBlock && (options.auth)) { + this.readyQueue.push(() => { + if (! this.ratelimits[route]) { + this.ratelimits[route] = new SequentialBucket(1, this.latencyRef); + } + this.ratelimits[route].queue(actualCall, short); + }); + } + else { + if (! this.ratelimits[route]) { + this.ratelimits[route] = new SequentialBucket(1, this.latencyRef); + } + this.ratelimits[route].queue(actualCall, short); + } + }); + } +} + +module.exports = RequestHandler; diff --git a/node_modules/discord-oauth2/lib/eris/util/SequentialBucket.js b/node_modules/discord-oauth2/lib/eris/util/SequentialBucket.js new file mode 100644 index 0000000..9a3dca9 --- /dev/null +++ b/node_modules/discord-oauth2/lib/eris/util/SequentialBucket.js @@ -0,0 +1,105 @@ +/* + The MIT License (MIT) + + Copyright (c) 2016-2020 abalabahaha + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +"use strict"; + +/** +* Ratelimit requests and release in sequence +* @prop {Number} limit How many tokens the bucket can consume in the current interval +* @prop {Number} remaining How many tokens the bucket has left in the current interval +* @prop {Number} reset Timestamp of next reset +* @prop {Boolean} processing Whether the queue is being processed +*/ +class SequentialBucket { + /** + * Construct a SequentialBucket + * @arg {Number} tokenLimit The max number of tokens the bucket can consume per interval + * @arg {Object} [latencyRef] An object + * @arg {Number} latencyRef.latency Interval between consuming tokens + */ + constructor(limit, latencyRef = { latency: 0 }) { + this.limit = this.remaining = limit; + this.resetInterval = 0; + this.reset = 0; + this.processing = false; + this.latencyRef = latencyRef; + this._queue = []; + } + + /** + * Queue something in the SequentialBucket + * @arg {Function} func A function to call when a token can be consumed. The function will be passed a callback argument, which must be called to allow the bucket to continue to work + */ + queue(func, short) { + if (short) { + this._queue.unshift(func); + } + else { + this._queue.push(func); + } + this.check(); + } + + check(override) { + if (this._queue.length === 0) { + if (this.processing) { + clearTimeout(this.processing); + this.processing = false; + } + return; + } + if (this.processing && !override) { + return; + } + const now = Date.now(); + const offset = this.latencyRef.latency + (this.latencyRef.offset || 0); + if (!this.reset) { + this.reset = now - offset; + this.remaining = this.limit; + } + else if (this.reset < now - offset) { + this.reset = now - offset + (this.resetInterval || 0); + this.remaining = this.limit; + } + this.last = now; + if (this.remaining <= 0) { + this.processing = setTimeout(() => { + this.processing = false; + this.check(true); + }, Math.max(0, (this.reset || 0) - now) + offset); + return; + } + --this.remaining; + this.processing = true; + this._queue.shift()(() => { + if (this._queue.length > 0) { + this.check(true); + } + else { + this.processing = false; + } + }); + } +} + +module.exports = SequentialBucket; diff --git a/node_modules/discord-oauth2/lib/oauth.js b/node_modules/discord-oauth2/lib/oauth.js new file mode 100644 index 0000000..b1cb3e0 --- /dev/null +++ b/node_modules/discord-oauth2/lib/oauth.js @@ -0,0 +1,218 @@ +"use strict"; + +const RequestHandler = require("./eris/rest/RequestHandler"); + +/** + * Make requests to discord's OAuth2 API + * @extends requestHandler +*/ +class OAuth extends RequestHandler { + /** + * + * @arg {Object} opts + * @arg {String?} opts.version The version of the Discord API to use. Defaults to v7. + * @arg {Number} [opts.requestTimeout=15000] A number of milliseconds before requests are considered timed out + * @arg {Number} [opts.latencyThreshold=30000] The average request latency at which the RequestHandler will start emitting latency errors + * @arg {Number} [opts.ratelimiterOffset=0] A number of milliseconds to offset the ratelimit timing calculations by + * @arg {String?} opts.clientId Your application's client id + * @arg {String?} opts.clientSecret Your application's client secret + * @arg {String?} opts.redirectUri Your URL redirect uri + * @arg {String?} opts.credentials Base64 encoding of the UTF-8 encoded credentials string of your application + */ + constructor(opts = {}) { + super({ + version: opts.version || "v7", + requestTimeout: opts.requestTimeout || 15000, + latencyThreshold: opts.latencyThreshold || 30000, + ratelimiterOffset: opts.ratelimiterOffset || 0, + }); + + this.client_id = opts.clientId; + this.client_secret = opts.clientSecret; + this.redirect_uri = opts.redirectUri; + + this.credentials = opts.credentials; + } + + _encode(obj) { + let string = ""; + + for (const [key, value] of Object.entries(obj)) { + if (!value) continue; + string += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`; + } + + return string.substring(1); + } + + /** + * Exchange the code returned by discord in the query for the user access token + * If specified, can also use refresh_token to get a new valid token + * Read discord's OAuth2 documentation for a full example (https://discord.com/developers/docs/topics/oauth2) + * @arg {Object} opts The object containing the parameters for the request + * @arg {String?} opts.clientId Your application's client id + * @arg {String?} opts.clientSecret Your application's client secret + * @arg {String} opts.grantType Either authorization_code or refresh_token + * @arg {String?} opts.code The code from the querystring + * @arg {String?} opts.refreshToken The user's refresh token + * @arg {String?} opts.redirectUri Your URL redirect uri + * @arg {String} opts.scope The scopes requested in your authorization url, space-delimited + * @returns {Promise} + */ + tokenRequest(opts = {}) { + const obj = { + client_id: opts.clientId || this.client_id, + client_secret: opts.clientSecret || this.client_secret, + grant_type: undefined, + code: undefined, + refresh_token: undefined, + redirect_uri: opts.redirectUri || this.redirect_uri, + scope: opts.scope instanceof Array ? opts.scope.join(" ") : opts.scope, + }; + + if (opts.grantType === "authorization_code") { + obj.code = opts.code; + obj.grant_type = opts.grantType; + } + else if (opts.grantType === "refresh_token") { + obj.refresh_token = opts.refreshToken; + obj.grant_type = opts.grantType; + } + else throw new Error("Invalid grant_type provided, it must be either authorization_code or refresh_token"); + + const encoded_string = this._encode(obj); + + return this.request("POST", "/oauth2/token", encoded_string, { + contentType: "application/x-www-form-urlencoded", + }); + } + + /** + * Revoke the user access token + * @arg {String} access_token The user access token + * @arg {String} credentials Base64 encoding of the UTF-8 encoded credentials string of your application + * @returns {Promise} + */ + revokeToken(access_token, credentials) { + if (!credentials && !this.credentials) throw new Error("Missing credentials for revokeToken method"); + return this.request("POST", "/oauth2/token/revoke", `token=${access_token}`, { + auth: { + type: "Basic", + creds: credentials || this.credentials, + }, + contentType: "application/x-www-form-urlencoded", + }); + } + + /** + * Request basic user data + * Requires the `identify` scope + * @arg {String} access_token The user access token + * @returns {Promise} + */ + getUser(access_token) { + return this.request("GET", "/users/@me", undefined, { + auth: { + type: "Bearer", + creds: access_token, + }, + contentType: "application/json", + }); + } + + /** + * Request all the guilds the user is in + * Requires the `guilds` scope + * @arg {String} access_token The user access token + * @returns {Promise} + */ + getUserGuilds(access_token) { + return this.request("GET", "/users/@me/guilds", undefined, { + auth: { + type: "Bearer", + creds: access_token, + }, + contentType: "application/json", + }); + } + + /** + * Request a user's connections + * Requires the `connections` scope + * @arg {String} access_token The user access token + * @returns {Promise} + */ + getUserConnections(access_token) { + return this.request("GET", "/users/@me/connections", undefined, { + auth: { + type: "Bearer", + creds: access_token, + }, + contentType: "application/json", + }); + } + + /** + * Force a user to join a guild + * Requires the `guilds.join` scope + * @arg {Object} opts + * @arg {String} opts.guildId The ID of the guild to join + * @arg {String} opts.userId The ID of the user to be added to the guild + * @arg {Boolean?} opts.deaf Whether the user is deafened in voice channels + * @arg {Boolean?} opts.mute Whether the user is muted in voice channels + * @arg {String?} opts.nickname Value to set users nickname to + * @arg {String[]?} opts.roles Array of role ids the member is assigned + * @arg {String} opts.accessToken The user access token + * @arg {String} opts.botToken The token of the bot used to authenticate + * @returns {Promise} + */ + addMember(opts) { + return this.request("PUT", `/guilds/${opts.guildId}/members/${opts.userId}`, { + deaf: opts.deaf, + mute: opts.mute, + nick: opts.nickname, + roles: opts.roles, + access_token: opts.accessToken, + }, { + auth: { + type: "Bot", + creds: opts.botToken, + }, + contentType: "application/json", + }); + } + + /** + * + * @arg {Object} opts + * @arg {String} opts.clientId Your application's client id + * @arg {String?} opts.prompt Controls how existing authorizations are handled, either consent or none (for passthrough scopes authorization is always required). + * @arg {String?} opts.redirectUri Your URL redirect uri + * @arg {String?} opts.responseType The response type, either code or token (token is for client-side web applications only). Defaults to code + * @arg {String | Array} opts.scope The scopes for your URL + * @arg {String?} opts.state A unique cryptographically secure string (https://discord.com/developers/docs/topics/oauth2#state-and-security) + * @arg {Number?} opts.permissions The permissions number for the bot invite (only with bot scope) (https://discord.com/developers/docs/topics/permissions) + * @arg {String?} opts.guildId The guild id to pre-fill the bot invite (only with bot scope) + * @arg {Boolean?} opts.disableGuildSelect Disallows the user from changing the guild for the bot invite, either true or false (only with bot scope) + * @returns {String} + */ + generateAuthUrl(opts = {}) { + const obj = { + client_id: opts.clientId || this.client_id, + prompt: opts.prompt || undefined, + redirect_uri: opts.redirectUri || this.redirect_uri, + response_type: opts.responseType || "code", + scope: opts.scope instanceof Array ? opts.scope.join(" ") : opts.scope, + permissions: opts.permissions || undefined, + guild_id: opts.guildId || undefined, + disable_guild_select: opts.disableGuildSelect || undefined, + state: opts.state || undefined, + }; + + const encoded_string = this._encode(obj); + + return `https://discord.com/api/oauth2/authorize?${encoded_string}`; + } +} + +module.exports = OAuth; diff --git a/node_modules/discord-oauth2/package.json b/node_modules/discord-oauth2/package.json new file mode 100644 index 0000000..4363b2c --- /dev/null +++ b/node_modules/discord-oauth2/package.json @@ -0,0 +1,56 @@ +{ + "_from": "discord-oauth2", + "_id": "discord-oauth2@2.7.1", + "_inBundle": false, + "_integrity": "sha512-8PiGsieFxujS6FqcDrWtrunhy5LQGIZC6n1Bi58CP/1/rusqxplj3tXMQ2DBZtw/oyDUrJwSBsYGSr7IJJwTeg==", + "_location": "/discord-oauth2", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "discord-oauth2", + "name": "discord-oauth2", + "escapedName": "discord-oauth2", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/discord-oauth2/-/discord-oauth2-2.7.1.tgz", + "_shasum": "d71958d6f321c7bebec859ee60fc316fefef7eb7", + "_spec": "discord-oauth2", + "_where": "/home/sigonasr2/divar/server2", + "author": { + "name": "reboxer" + }, + "bugs": { + "url": "https://github.com/reboxer/discord-oauth2/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "Easily interact with discord's oauth2 API", + "devDependencies": { + "eslint": "7.30.0" + }, + "homepage": "https://github.com/reboxer/discord-oauth2#readme", + "keywords": [ + "api", + "discord", + "discordapp", + "oauth2" + ], + "license": "MIT", + "main": "index.js", + "name": "discord-oauth2", + "repository": { + "type": "git", + "url": "git+https://github.com/reboxer/discord-oauth2.git" + }, + "scripts": { + "lint": "eslint --ext .js ./" + }, + "version": "2.7.1" +} diff --git a/node_modules/secrethash/package.json b/node_modules/secrethash/package.json deleted file mode 100644 index 74d747a..0000000 --- a/node_modules/secrethash/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "secrethash", - "version": "1.0.0", - "main": "secrethash.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "description": "" -} diff --git a/package-lock.json b/package-lock.json index 93ad951..40065fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,6 +104,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "discord-oauth2": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/discord-oauth2/-/discord-oauth2-2.7.1.tgz", + "integrity": "sha512-8PiGsieFxujS6FqcDrWtrunhy5LQGIZC6n1Bi58CP/1/rusqxplj3tXMQ2DBZtw/oyDUrJwSBsYGSr7IJJwTeg==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", diff --git a/package.json b/package.json index 1cf029a..a9d2f85 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "license": "ISC", "dependencies": { "axios": "^0.21.1", + "discord-oauth2": "^2.7.1", "express": "^4.17.1", "fs": "0.0.1-security", "http": "0.0.1-security", diff --git a/server.js b/server.js index 83c721b..b049ef0 100644 --- a/server.js +++ b/server.js @@ -4,7 +4,13 @@ var http = require('http'); var https = require('https'); const fs = require('fs'); -const sh = require('secrethash'); +const sh = require('./secrethash'); +const disc = require("discord-oauth2"); +var refreshToken="" +const discord = new disc({ + clientId: "885738904685281291", + clientSecret: process.env.NGSPLANNER_CLIENT_SECRET, + redirectUri: "https://localhost:3000#/login",}); var key = fs.readFileSync('./projectdivar.com/privkey1.pem'); var cert = fs.readFileSync('./projectdivar.com/cert1.pem'); @@ -427,6 +433,41 @@ for (var test of ["","/test"]) { res.status(500).send(err.message) }) }) + + app.get(PREFIX+test+"/userData",(req,res)=>{ + if (req.query.token) { + discord.tokenRequest({ + code:req.query.token, + grantType: "authorization_code", + scope: ["identify", "email"], + }) + .then(res2=>{return discord.getUser(res2.access_token)}) + .then(res2=>{ + //Sample output. We can register the user from here. + //{"id":"176012829076226048","username":"sigonasr2","avatar":"c19b2f2a9d530f9f99efa3b1b573d7ef","discriminator":"6262","public_flags":0,"flags":0,"banner":"e0668c23567d5b58e88ea916306ec6d5","banner_color":null,"accent_color":null,"locale":"en-US","mfa_enabled":false,"premium_type":2,"email":"sigonasr2@gmail.com","verified":true} + /*var obj = { + body:{ + recoveryhash:res2.id, + password:req.query.token, + username:res2.username, + avatar:"https://cdn.discordapp.com/avatars/"+res2.id+"/"+res2.avatar+".png", + userID:res2.id, + email:res2.email + } + } + registerUsers(obj,res)*/ + res2.token=sh(req.query.token) + res.status(200).json(res2) + }) + .catch((err)=>{ + //console.log(err.response) + res.status(500).send("Everything is not fine") + }) + //res.status(200).send("Everything is fine") + } else { + res.status(500).send("Everything is not fine") + } + }) } function CreateDynamicEndpoints() { @@ -824,8 +865,8 @@ function registerUsers(req,res){ return db.query('select * from users where recovery_hash=$1 limit 1',[req.body.recoveryhash]) .then((data)=>{ if (data.rows.length>0) { - db.query('update users set password_hash=$2 where id=$1',[data.rows[0].id,req.body.password]) - db2.query('update users set password_hash=$2 where id=$1',[data.rows[0].id,req.body.password]) + db.query('update users set username=$3,password_hash=$2 where id=$1',[data.rows[0].id,req.body.password,req.body.username]) + db2.query('update users set username=$3,password_hash=$2 where id=$1',[data.rows[0].id,req.body.password,req.body.username]) res.status(200).json({verified:true}) } else { res.status(200).json({verified:true}) @@ -847,10 +888,11 @@ function registerUsers(req,res){ }) } else { console.log("User with email '"+req.body.email+"' already exists assume it's not a google account. Overwriting...") - db.query('update users set password_hash=$1,avatar=$2,recovery_hash=$3 where id=$4 returning id',[req.body.password,req.body.avatar,req.body.userID,data.rows[0].id]) + //console.log(req.body.password) + db.query('update users set password_hash=$1,avatar=$2,recovery_hash=$3,username=$5 where id=$4 returning id',[req.body.password,req.body.avatar,req.body.userID,data.rows[0].id,req.body.username]) .then((data)=>{ if (data.rows.length>0) { - db2.query('update users set password_hash=$1,avatar=$2,recovery_hash=$3 where id=$4 returning id',[req.body.password,req.body.avatar,req.body.userID,data.rows[0].id]) + db2.query('update users set password_hash=$1,avatar=$2,recovery_hash=$3,username=$5 where id=$4 returning id',[req.body.password,req.body.avatar,req.body.userID,data.rows[0].id,req.body.username]) } }) res.status(200).json({verified:true}) @@ -865,9 +907,10 @@ function registerUsers(req,res){ } app.post(PREFIX+"/registerUser",registerUsers) +app.post(PREFIX+"/test/registerUser",registerUsers) -app.post(PREFIX+"/validUser",(req,res)=>{ - //console.log(sh.SecretHash("098f6bcd4621d373cade4e832627b4f6")) +function validUser(req,res) { + //console.log(sh("098f6bcd4621d373cade4e832627b4f6")) if (req.body.recoveryhash&&req.body.password) { //A recovery hash means this is an external login. Try seeing if it matches something. db.query('select * from users where recovery_hash=$1 and password_hash=$2 limit 1',[req.body.recoveryhash,req.body.password]) @@ -882,7 +925,7 @@ app.post(PREFIX+"/validUser",(req,res)=>{ res.status(500).send(err.message) }) } else { - db.query('select * from users where username=$1 and password_hash=$2 limit 1',[req.body.username,sh.SecretHash(req.body.password)]) + db.query('select * from users where username=$1 and password_hash=$2 limit 1',[req.body.username,sh(req.body.password)]) .then((data)=>{ if (data.rows.length>0) { res.status(200).json({verified:true}) @@ -894,7 +937,10 @@ app.post(PREFIX+"/validUser",(req,res)=>{ res.status(500).send(err.message) }) } -}) +} + +app.post(PREFIX+"/validUser",validUser) +app.post(PREFIX+"/test/validUser",validUser) app.post(PREFIX+"/saveskilltree",(req,res)=>{ db4.query('select * from password where password=$1',[req.body.pass]) @@ -953,7 +999,7 @@ function submitBuild(req,res,db,send) { db.query('select users.username from builds join users on users_id=users.id where builds.id=$1',[req.body.id]) .then((data)=>{ console.log(data.rows) - if (data.rows.length>0&&data.rows[0].username===req.body.username&&data.rows[0].password_hash===sh.SecretHash(req.body.pass)) { + if (data.rows.length>0&&data.rows[0].username===req.body.username&&data.rows[0].password_hash===sh(req.body.pass)) { return db.query('update builds set creator=$1,build_name=$2,class1=(SELECT id from class WHERE name=$3 limit 1),class2=(SELECT id from class WHERE name=$4 limit 1),last_modified=$5,data=$6 where id=$7 returning id',[req.body.creator,req.body.build_name,req.body.class1,req.body.class2,new Date(),req.body.data,req.body.id]) .then((data)=>{ if (send) {