diff --git a/src/client/Client.js b/src/client/Client.js index b8ec2ac..c92fed7 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -40,7 +40,7 @@ const Options = require('../util/Options'); const Permissions = require('../util/Permissions'); const DiscordAuthWebsocket = require('../util/RemoteAuth'); const Sweepers = require('../util/Sweepers'); -const { testImportModule } = require('../util/Util'); +const { getProxyObject } = require('../util/Util'); /** * The main hub for interacting with the Discord API, and the starting point for any bot. @@ -1021,10 +1021,9 @@ class Client extends BaseClient { } else if ( options && options.proxy && - typeof options.proxy === 'string' && - testImportModule('proxy-agent') === false + typeof options.proxy === 'string' ) { - throw new Error('MISSING_MODULE', 'proxy-agent', 'npm install proxy-agent@5'); + getProxyObject(options.proxy); } if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount < 1) { throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number greater than or equal to 1'); diff --git a/src/client/websocket/WebSocketShard.js b/src/client/websocket/WebSocketShard.js index a72a2ad..49ff8a2 100644 --- a/src/client/websocket/WebSocketShard.js +++ b/src/client/websocket/WebSocketShard.js @@ -5,6 +5,7 @@ const { setTimeout, setInterval, clearTimeout } = require('node:timers'); const WebSocket = require('../../WebSocket'); const { Status, Events, ShardEvents, Opcodes, WSEvents, WSCodes } = require('../../util/Constants'); const Intents = require('../../util/Intents'); +const { getProxyObject } = require('../../util/Util'); const STATUS_KEYS = Object.keys(Status); const CONNECTION_STATE = Object.keys(WebSocket.WebSocket); @@ -280,8 +281,7 @@ class WebSocketShard extends EventEmitter { let args = { handshakeTimeout: 30_000 }; if (client.options.proxy.length > 0) { - const ProxyAgent = require('proxy-agent'); - args.agent = new ProxyAgent(client.options.proxy); + args.agent = getProxyObject(client.options.proxy); this.debug(`Using proxy ${client.options.proxy}`, args); } // Adding a handshake timeout to just make sure no zombie connection appears. diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index a75e722..3edebb8 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -7,6 +7,7 @@ const makeFetchCookie = require('fetch-cookie'); const FormData = require('form-data'); const fetchOriginal = require('node-fetch'); const { CookieJar } = require('tough-cookie'); +const { getProxyObject } = require('../util/Util'); const cookieJar = new CookieJar(); const fetch = makeFetchCookie(fetchOriginal, cookieJar); @@ -35,8 +36,7 @@ class APIRequest { make(captchaKey = undefined, captchaRqtoken = undefined) { if (agent === null) { if (typeof this.client.options.proxy === 'string' && this.client.options.proxy.length > 0) { - const ProxyAgent = require('proxy-agent'); - agent = new ProxyAgent(this.client.options.proxy); + agent = getProxyObject(this.client.options.proxy); } else if (this.client.options.http.agent instanceof https.Agent) { agent = this.client.options.http.agent; agent.keepAlive = true; diff --git a/src/util/Util.js b/src/util/Util.js index 5c581d1..f0d956a 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -5,7 +5,7 @@ const process = require('node:process'); const { Collection } = require('@discordjs/collection'); const fetch = require('node-fetch'); const { Colors } = require('./Constants'); -const { RangeError, TypeError } = require('../errors'); +const { RangeError, TypeError, Error: DJSError } = require('../errors'); const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k); const isObject = d => typeof d === 'object' && d !== null; @@ -751,6 +751,52 @@ class Util extends null { } } + static getProxyObject(proxy) { + const protocol = new URL(proxy).protocol.slice(0, -1); + const mapObject = { + http: 'https', // Cuz we can't use http for discord + https: 'https', + socks4: 'socks', + socks5: 'socks', + 'pac+http': 'pac', + 'pac+https': 'pac', + }; + const proxyType = mapObject[protocol]; + switch (proxyType) { + case 'https': { + if (!Util.testImportModule('https-proxy-agent')) { + throw new DJSError('MISSING_MODULE', 'https-proxy-agent', 'npm install https-proxy-agent'); + } + const httpsProxyAgent = require('https-proxy-agent'); + return new httpsProxyAgent.HttpsProxyAgent(proxy); + } + + case 'socks': { + if (!Util.testImportModule('socks-proxy-agent')) { + throw new DJSError('MISSING_MODULE', 'socks-proxy-agent', 'npm install socks-proxy-agent'); + } + const socksProxyAgent = require('socks-proxy-agent'); + return new socksProxyAgent.SocksProxyAgent(proxy); + } + + case 'pac': { + if (!Util.testImportModule('pac-proxy-agent')) { + throw new DJSError('MISSING_MODULE', 'pac-proxy-agent', 'npm install pac-proxy-agent'); + } + const pacProxyAgent = require('pac-proxy-agent'); + return new pacProxyAgent.PacProxyAgent(proxy); + } + + default: { + if (!Util.testImportModule('proxy-agent')) { + throw new DJSError('MISSING_MODULE', 'proxy-agent', 'npm install proxy-agent@5'); + } + const proxyAgent = require('proxy-agent'); + return new proxyAgent(proxy); + } + } + } + /** * Gets an array of the channel types that can be moved in the channel group. For example, a GuildText channel would * return an array containing the types that can be ordered within the text channels (always at the top), and a voice