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 <contact@amroz.xyz>
This commit is contained in:
Elysia 2023-03-29 18:38:43 +07:00
parent bd2475b3c6
commit f69b36cab5
4 changed files with 37 additions and 11 deletions

View File

@ -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.

View File

@ -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",

View File

@ -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<string>} [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);
});

6
typings/index.d.ts vendored
View File

@ -4078,9 +4078,9 @@ export class GuildManager extends CachedManager<Snowflake, Guild, GuildResolvabl
}
export interface BruteforceOptions {
dictionary: string[];
limit: number;
delay: number;
limit?: number;
delay?: number;
depth?: number;
}
export class GuildMemberManager extends CachedManager<Snowflake, GuildMember, GuildMemberResolvable> {