From f69b36cab52f5ad327e9236e0143fbed3828b5c7 Mon Sep 17 00:00:00 2001 From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com> Date: Wed, 29 Mar 2023 18:38:43 +0700 Subject: [PATCH] feat: Better GuildMemberManager#fetchBruteforce permutations tried with Midjourney server, was able to scrape over 60k members in 15 minutes (and more) Co-Authored-By: MrBoombastic --- Document/FetchGuildMember.md | 2 +- package.json | 1 + src/managers/GuildMemberManager.js | 39 ++++++++++++++++++++++++------ typings/index.d.ts | 6 ++--- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Document/FetchGuildMember.md b/Document/FetchGuildMember.md index 25b5558..238fe33 100644 --- a/Document/FetchGuildMember.md +++ b/Document/FetchGuildMember.md @@ -128,11 +128,11 @@ Suppose you're in a guild with 1000 members and want to fetch the member list ba ____________________________________ ## Search for Members by Query #### Usage -> Dictionary of query parameters: [Here](https://github.com/Merubokkusu/Discord-S.C.U.M/blob/master/examples/searchGuildMembers.py#L37) 1) run the function: ```js guild.members.fetchBruteforce({ delay: 500, + depth: 1, // ['a', 'b', 'c', 'd', ...] or ['aa', 'ab', 'ac', 'ad', ...] if depth is 2, ... }) ``` A wait time of at least 0.5 is needed to prevent the brute forcer from rate limiting too often. In the event that the brute forcer does get rate limited, some time will be lost reconnecting. diff --git a/package.json b/package.json index 0f65939..1810aa3 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "discord-api-types": "^0.37.36", "form-data": "^4.0.0", "json-bigint": "^1.0.0", + "lodash.permutations": "^1.0.0", "node-fetch": "^2.6.9", "safe-base64": "^2.0.1-0", "set-cookie-parser": "^2.6.0", diff --git a/src/managers/GuildMemberManager.js b/src/managers/GuildMemberManager.js index deb419c..2c53131 100644 --- a/src/managers/GuildMemberManager.js +++ b/src/managers/GuildMemberManager.js @@ -3,6 +3,8 @@ const { Buffer } = require('node:buffer'); const { setTimeout } = require('node:timers'); const { Collection } = require('@discordjs/collection'); +require('lodash.permutations'); +const _ = require('lodash'); const CachedManager = require('./CachedManager'); const { Error, TypeError, RangeError } = require('../errors'); const BaseGuildVoiceChannel = require('../structures/BaseGuildVoiceChannel'); @@ -203,6 +205,7 @@ class GuildMemberManager extends CachedManager { return this.fetchBruteforce({ delay: 50, skipWarn: true, + depth: 2, }); } } @@ -468,9 +471,9 @@ class GuildMemberManager extends CachedManager { /** * Options used to fetch multiple members from a guild. * @typedef {Object} BruteforceOptions - * @property {Array} [dictionary] Limit fetch to members with similar usernames {@link https://github.com/Merubokkusu/Discord-S.C.U.M/blob/master/examples/searchGuildMembers.py#L37} * @property {number} [limit=100] Maximum number of members per request * @property {number} [delay=500] Timeout for new requests in ms + * @property {number} [depth=1] Permutations length */ /** @@ -485,26 +488,48 @@ class GuildMemberManager extends CachedManager { */ fetchBruteforce(options = {}) { // eslint-disable-next-line - let dictionary = 'abcdefghijklmnopqrstuvwxyz0123456789!"#$%&\'()*+,-./:;<=>?@[]^_`{|}~ '.split(''); + let defaultQuery = 'abcdefghijklmnopqrstuvwxyz0123456789!"#$%&\'()*+,-./:;<=>?@[]^_`{|}~'; + let dictionary; let limit = 100; let delay = 500; - if (options?.dictionary) dictionary = options?.dictionary; + let depth = 1; if (options?.limit) limit = options?.limit; if (options?.delay) delay = options?.delay; - if (!Array.isArray(dictionary)) throw new TypeError('INVALID_TYPE', 'dictionary', 'Array', true); + if (options?.depth) depth = options?.depth; if (typeof limit !== 'number') throw new TypeError('INVALID_TYPE', 'limit', 'Number'); if (limit < 1 || limit > 100) throw new RangeError('INVALID_RANGE_QUERY_MEMBER'); if (typeof delay !== 'number') throw new TypeError('INVALID_TYPE', 'delay', 'Number'); + if (typeof depth !== 'number') throw new TypeError('INVALID_TYPE', 'depth', 'Number'); + if (depth < 1) throw new RangeError('INVALID_RANGE_QUERY_MEMBER'); + if (depth > 2) { + console.warn(`[WARNING] GuildMemberManager#fetchBruteforce: depth greater than 2, can lead to very slow speeds`); + } if (delay < 500 && !options?.skipWarn) { console.warn( `[WARNING] GuildMemberManager#fetchBruteforce: delay is less than 500ms, this may cause rate limits.`, ); } + let skipValues = []; // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { - for (const query of dictionary) { - await this._fetchMany({ query, limit }).catch(reject); - await this.guild.client.sleep(delay); + for (let i = 1; i <= depth; i++) { + dictionary = _(defaultQuery) + .permutations(i) + .map(v => _.join(v, '')) + .value(); + for (const query of dictionary) { + this.client.emit( + 'debug', + `[INFO] GuildMemberManager#fetchBruteforce: Querying ${query}, Skip: [${skipValues.join(', ')}]`, + ); + if (skipValues.some(v => query.startsWith(v))) continue; + await this._fetchMany({ query, limit }) + .then(members => { + if (members.size === 0) skipValues.push(query); + }) + .catch(reject); + await this.guild.client.sleep(delay); + } } resolve(this.guild.members.cache); }); diff --git a/typings/index.d.ts b/typings/index.d.ts index 2533bdf..f75c4f2 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -4078,9 +4078,9 @@ export class GuildManager extends CachedManager {