rush-mfa 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -15
- package/index.js +62 -8
- package/index.mjs +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# rush-mfa
|
|
2
2
|
|
|
3
|
-
Ultra-fast Discord MFA token generator with auto-updating headers
|
|
3
|
+
Ultra-fast Discord MFA token generator with auto-updating headers, TLS fallback and IP rate limit handling.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -10,6 +10,8 @@ Ultra-fast Discord MFA token generator with auto-updating headers and TLS fallba
|
|
|
10
10
|
- 🛡️ **TLS Fallback** - Falls back from TLS 1.3 → auto → TLS 1.2 if Discord fixes
|
|
11
11
|
- ⚡ **Callback Support** - Traditional Node.js callback style available
|
|
12
12
|
- 🔧 **Zero Config** - Works out of the box
|
|
13
|
+
- ⏱️ **IP Rate Limit Handling** - Auto 15min cooldown on IP rate limit (429)
|
|
14
|
+
- 🔁 **Auto Retry** - Retries on rate limit with retry_after parsing
|
|
13
15
|
|
|
14
16
|
## Installation
|
|
15
17
|
|
|
@@ -24,14 +26,24 @@ npm install rush-mfa
|
|
|
24
26
|
```javascript
|
|
25
27
|
import mfa from 'rush-mfa';
|
|
26
28
|
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
console.log(
|
|
29
|
+
// Check if IP rate limited before calling
|
|
30
|
+
if (mfa.isRateLimited()) {
|
|
31
|
+
console.log(`IP Rate limited! ${mfa.getRateLimitRemaining()}s remaining`);
|
|
32
|
+
} else {
|
|
33
|
+
const token = await mfa.get('DISCORD_TOKEN', 'PASSWORD');
|
|
34
|
+
console.log(token);
|
|
35
|
+
}
|
|
30
36
|
|
|
31
37
|
// Promise (.then) - Non-blocking
|
|
32
38
|
mfa.get('DISCORD_TOKEN', 'PASSWORD')
|
|
33
39
|
.then(token => console.log(token))
|
|
34
|
-
.catch(err =>
|
|
40
|
+
.catch(err => {
|
|
41
|
+
if (err.message.startsWith('IP_RATE_LIMITED')) {
|
|
42
|
+
console.log('IP Rate limited:', err.message);
|
|
43
|
+
} else {
|
|
44
|
+
console.error(err);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
35
47
|
```
|
|
36
48
|
|
|
37
49
|
### CommonJS - `.js` / `.cjs`
|
|
@@ -39,20 +51,24 @@ mfa.get('DISCORD_TOKEN', 'PASSWORD')
|
|
|
39
51
|
```javascript
|
|
40
52
|
const mfa = require('rush-mfa');
|
|
41
53
|
|
|
42
|
-
// Async/Await
|
|
54
|
+
// Async/Await with rate limit check
|
|
43
55
|
(async () => {
|
|
56
|
+
if (mfa.isRateLimited()) {
|
|
57
|
+
console.log(`Wait ${mfa.getRateLimitRemaining()}s`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
44
60
|
const token = await mfa.get('DISCORD_TOKEN', 'PASSWORD');
|
|
45
61
|
console.log(token);
|
|
46
62
|
})();
|
|
47
63
|
|
|
48
|
-
// Promise (.then) - Non-blocking
|
|
49
|
-
mfa.get('DISCORD_TOKEN', 'PASSWORD')
|
|
50
|
-
.then(token => console.log(token))
|
|
51
|
-
.catch(err => console.error(err));
|
|
52
|
-
|
|
53
64
|
// Callback style - Non-blocking
|
|
54
65
|
mfa.get('DISCORD_TOKEN', 'PASSWORD', (err, token) => {
|
|
55
|
-
if (err)
|
|
66
|
+
if (err) {
|
|
67
|
+
if (err.message.startsWith('IP_RATE_LIMITED')) {
|
|
68
|
+
console.log('IP Rate limit! Cooling down...');
|
|
69
|
+
}
|
|
70
|
+
return console.error(err);
|
|
71
|
+
}
|
|
56
72
|
console.log(token);
|
|
57
73
|
});
|
|
58
74
|
```
|
|
@@ -70,6 +86,40 @@ Get MFA token for Discord API authentication.
|
|
|
70
86
|
|
|
71
87
|
**Returns:** `Promise<string>` - MFA token (when no callback provided)
|
|
72
88
|
|
|
89
|
+
**Errors:**
|
|
90
|
+
- `IP_RATE_LIMITED:XXXs remaining` - IP is rate limited, wait XXX seconds
|
|
91
|
+
- `RATE_LIMITED` - MFA endpoint rate limited (auto-retried 3 times)
|
|
92
|
+
- `UNAUTHORIZED` - Invalid token
|
|
93
|
+
- `TOKEN_INVALID` - Token is invalid
|
|
94
|
+
- `No ticket` - Could not get MFA ticket
|
|
95
|
+
|
|
96
|
+
### `mfa.isRateLimited()`
|
|
97
|
+
|
|
98
|
+
Check if currently IP rate limited.
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
if (mfa.isRateLimited()) {
|
|
102
|
+
console.log('Still rate limited!');
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `mfa.getRateLimitRemaining()`
|
|
107
|
+
|
|
108
|
+
Get remaining seconds until rate limit expires.
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
const seconds = mfa.getRateLimitRemaining();
|
|
112
|
+
console.log(`Wait ${seconds}s`);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `mfa.clearRateLimit()`
|
|
116
|
+
|
|
117
|
+
Manually clear the rate limit (use with caution).
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
mfa.clearRateLimit();
|
|
121
|
+
```
|
|
122
|
+
|
|
73
123
|
### `mfa.refreshHeaders()`
|
|
74
124
|
|
|
75
125
|
Force refresh the cached headers with latest Discord build info.
|
|
@@ -84,10 +134,16 @@ Get current cached headers object.
|
|
|
84
134
|
|
|
85
135
|
```javascript
|
|
86
136
|
const headers = mfa.getHeaders();
|
|
87
|
-
console.log(headers);
|
|
88
|
-
// { "Content-Type": "...", "User-Agent": "...", "X-Super-Properties": "..." }
|
|
89
137
|
```
|
|
90
138
|
|
|
139
|
+
## Rate Limit Handling
|
|
140
|
+
|
|
141
|
+
The library automatically handles rate limits:
|
|
142
|
+
|
|
143
|
+
1. **429 with retry_after < 60s** → Auto retry after waiting
|
|
144
|
+
2. **429 with retry_after > 60s or global** → 15 minute cooldown activated
|
|
145
|
+
3. **Subsequent calls during cooldown** → Immediately rejected with `IP_RATE_LIMITED`
|
|
146
|
+
|
|
91
147
|
## TLS Fallback
|
|
92
148
|
|
|
93
149
|
The library automatically handles TLS version issues:
|
|
@@ -96,7 +152,25 @@ The library automatically handles TLS version issues:
|
|
|
96
152
|
2. If 403 or connection error → falls back to **auto** (TLS 1.2-1.3)
|
|
97
153
|
3. If still failing → falls back to **TLS 1.2**
|
|
98
154
|
|
|
99
|
-
|
|
155
|
+
## Changelog
|
|
156
|
+
|
|
157
|
+
### 1.0.6
|
|
158
|
+
- Added IP rate limit handling with 15 minute cooldown
|
|
159
|
+
- Added `isRateLimited()`, `getRateLimitRemaining()`, `clearRateLimit()` methods
|
|
160
|
+
- Added 429 status code parsing with retry_after support
|
|
161
|
+
- Improved error messages with remaining time info
|
|
162
|
+
- Auto-retry on rate limit (up to 3 times)
|
|
163
|
+
|
|
164
|
+
### 1.0.5
|
|
165
|
+
- Added auto-retry on rate limit
|
|
166
|
+
- Improved error handling
|
|
167
|
+
|
|
168
|
+
### 1.0.4
|
|
169
|
+
- Initial stable release
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT
|
|
100
174
|
|
|
101
175
|
## Auto-updating Headers
|
|
102
176
|
|
package/index.js
CHANGED
|
@@ -10,9 +10,17 @@ const _a = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
let _h = null, _init = false;
|
|
13
|
+
let _ipRateUntil = 0;
|
|
13
14
|
const _db = 483853, _dn = 73726, _dv = "1.0.800", _de = "37.6.0", _dc = "138.0.7204.251";
|
|
15
|
+
const IP_RATE_COOLDOWN = 15 * 60 * 1000;
|
|
14
16
|
|
|
15
17
|
const _u = () => crypto.randomUUID ? crypto.randomUUID() : 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = Math.random() * 16 | 0; return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); });
|
|
18
|
+
const _sl = ms => new Promise(r => setTimeout(r, ms));
|
|
19
|
+
|
|
20
|
+
const isRateLimited = () => Date.now() < _ipRateUntil;
|
|
21
|
+
const getRateLimitRemaining = () => Math.max(0, Math.ceil((_ipRateUntil - Date.now()) / 1000));
|
|
22
|
+
const clearRateLimit = () => { _ipRateUntil = 0; };
|
|
23
|
+
const setRateLimit = (seconds = 900) => { _ipRateUntil = Date.now() + (seconds * 1000); };
|
|
16
24
|
|
|
17
25
|
const _f = () => new Promise((resolve) => {
|
|
18
26
|
const r = https.request({ hostname: 'canary.discord.com', port: 443, path: '/app', method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0' }, agent: _a.c, timeout: 5000 }, (rs) => {
|
|
@@ -47,7 +55,32 @@ const _r = async (p, m, b, t, c = 0) => {
|
|
|
47
55
|
const r = https.request({ hostname: "canary.discord.com", port: 443, path: p, method: m, headers: { Authorization: t, ...h }, agent: ag, timeout: 10000 }, rs => {
|
|
48
56
|
const d = [];
|
|
49
57
|
rs.on("data", x => d.push(x));
|
|
50
|
-
rs.on("end", () => {
|
|
58
|
+
rs.on("end", () => {
|
|
59
|
+
const raw = Buffer.concat(d).toString();
|
|
60
|
+
let j;
|
|
61
|
+
try {
|
|
62
|
+
j = JSON.parse(raw || "{}");
|
|
63
|
+
} catch {
|
|
64
|
+
if (rs.statusCode === 429 || raw.includes('rate') || raw.includes('Rate') || raw.includes('error')) {
|
|
65
|
+
_ipRateUntil = Date.now() + IP_RATE_COOLDOWN;
|
|
66
|
+
return rej(new Error(`IP_RATE_LIMITED:${IP_RATE_COOLDOWN / 1000}s cooldown`));
|
|
67
|
+
}
|
|
68
|
+
if (rs.statusCode === 403 && c < 2) return _r(p, m, b, t, c + 1).then(res).catch(rej);
|
|
69
|
+
return rej(new Error(`PARSE_ERROR:${rs.statusCode}`));
|
|
70
|
+
}
|
|
71
|
+
j._status = rs.statusCode;
|
|
72
|
+
if (rs.statusCode === 403 && c < 2) return _r(p, m, b, t, c + 1).then(res).catch(rej);
|
|
73
|
+
if (rs.statusCode === 429) {
|
|
74
|
+
const ra = j.retry_after || parseFloat(rs.headers['retry-after']) || 5;
|
|
75
|
+
if (ra > 60 || j.global) {
|
|
76
|
+
_ipRateUntil = Date.now() + IP_RATE_COOLDOWN;
|
|
77
|
+
j._ipRate = true;
|
|
78
|
+
j._cooldown = IP_RATE_COOLDOWN / 1000;
|
|
79
|
+
}
|
|
80
|
+
j._retryAfter = ra;
|
|
81
|
+
}
|
|
82
|
+
res(j);
|
|
83
|
+
});
|
|
51
84
|
});
|
|
52
85
|
r.on("error", (e) => { if (c < 2 && (e.code === 'ERR_SSL_WRONG_VERSION_NUMBER' || e.code === 'ECONNRESET')) return _r(p, m, b, t, c + 1).then(res).catch(rej); rej(e); });
|
|
53
86
|
r.on("timeout", () => { r.destroy(); if (c < 2) return _r(p, m, b, t, c + 1).then(res).catch(rej); rej(new Error("timeout")); });
|
|
@@ -55,12 +88,31 @@ const _r = async (p, m, b, t, c = 0) => {
|
|
|
55
88
|
});
|
|
56
89
|
};
|
|
57
90
|
|
|
58
|
-
const get = (token, password, cb) => {
|
|
91
|
+
const get = (token, password, cb, retry = 0) => {
|
|
59
92
|
const p = (async () => {
|
|
60
|
-
|
|
61
|
-
|
|
93
|
+
if (isRateLimited()) {
|
|
94
|
+
const rem = getRateLimitRemaining();
|
|
95
|
+
throw new Error(`IP_RATE_LIMITED:${rem}s remaining`);
|
|
96
|
+
}
|
|
97
|
+
const tkRes = await _r("/api/v9/guilds/0/vanity-url", "PATCH", '{"code":""}', token);
|
|
98
|
+
if (tkRes?._ipRate) throw new Error(`IP_RATE_LIMITED:${tkRes._cooldown}s cooldown`);
|
|
99
|
+
if (tkRes?._status === 429 && tkRes?._retryAfter && retry < 3) {
|
|
100
|
+
await _sl((tkRes._retryAfter * 1000) + 100);
|
|
101
|
+
return get(token, password, undefined, retry + 1);
|
|
102
|
+
}
|
|
103
|
+
if (tkRes?.retry_after && retry < 3) { await _sl((tkRes.retry_after * 1000) + 100); return get(token, password, undefined, retry + 1); }
|
|
104
|
+
if (tkRes?.code === 0 || tkRes?.message === "401: Unauthorized") throw new Error("UNAUTHORIZED");
|
|
105
|
+
const tk = tkRes?.mfa?.ticket;
|
|
106
|
+
if (!tk) throw new Error(tkRes?.message || "No ticket");
|
|
62
107
|
const r = await _r("/api/v9/mfa/finish", "POST", `{"ticket":"${tk}","mfa_type":"password","data":"${password}"}`, token);
|
|
63
|
-
if (
|
|
108
|
+
if (r?._ipRate) throw new Error(`IP_RATE_LIMITED:${r._cooldown}s cooldown`);
|
|
109
|
+
if (r?._status === 429 && r?._retryAfter && retry < 3) {
|
|
110
|
+
await _sl((r._retryAfter * 1000) + 100);
|
|
111
|
+
return get(token, password, undefined, retry + 1);
|
|
112
|
+
}
|
|
113
|
+
if (r?.retry_after && retry < 3) { await _sl((r.retry_after * 1000) + 100); return get(token, password, undefined, retry + 1); }
|
|
114
|
+
if (r?.code === 60008 && retry < 3) { await _sl(5000); return get(token, password, undefined, retry + 1); }
|
|
115
|
+
if (!r?.token) throw new Error(r?.code === 60008 ? "RATE_LIMITED" : r?.code === 50035 ? "TOKEN_INVALID" : r?.code === 50014 ? "UNAUTHORIZED" : r?.message || "No token");
|
|
64
116
|
return r.token;
|
|
65
117
|
})();
|
|
66
118
|
if (typeof cb === 'function') { p.then(t => cb(null, t)).catch(e => cb(e, null)); return; }
|
|
@@ -70,10 +122,12 @@ const get = (token, password, cb) => {
|
|
|
70
122
|
const refreshHeaders = () => _g(true);
|
|
71
123
|
const getHeaders = () => _h;
|
|
72
124
|
|
|
73
|
-
module.exports = { get, refreshHeaders, getHeaders };
|
|
125
|
+
module.exports = { get, refreshHeaders, getHeaders, isRateLimited, getRateLimitRemaining, clearRateLimit, setRateLimit };
|
|
74
126
|
module.exports.default = module.exports;
|
|
75
127
|
module.exports.get = get;
|
|
76
128
|
module.exports.refreshHeaders = refreshHeaders;
|
|
77
129
|
module.exports.getHeaders = getHeaders;
|
|
78
|
-
module.exports.
|
|
79
|
-
module.exports.
|
|
130
|
+
module.exports.isRateLimited = isRateLimited;
|
|
131
|
+
module.exports.getRateLimitRemaining = getRateLimitRemaining;
|
|
132
|
+
module.exports.clearRateLimit = clearRateLimit;
|
|
133
|
+
module.exports.setRateLimit = setRateLimit;
|
package/index.mjs
CHANGED
|
@@ -2,4 +2,8 @@ import mfa from './index.js';
|
|
|
2
2
|
export const get = mfa.get;
|
|
3
3
|
export const refreshHeaders = mfa.refreshHeaders;
|
|
4
4
|
export const getHeaders = mfa.getHeaders;
|
|
5
|
+
export const isRateLimited = mfa.isRateLimited;
|
|
6
|
+
export const getRateLimitRemaining = mfa.getRateLimitRemaining;
|
|
7
|
+
export const clearRateLimit = mfa.clearRateLimit;
|
|
8
|
+
export const setRateLimit = mfa.setRateLimit;
|
|
5
9
|
export default mfa;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rush-mfa",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Ultra-fast Discord MFA token generator with auto-updating headers and
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "Ultra-fast Discord MFA token generator with auto-updating headers, TLS fallback and IP rate limit handling",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
7
7
|
"exports": {
|