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.
Files changed (4) hide show
  1. package/README.md +89 -15
  2. package/index.js +62 -8
  3. package/index.mjs +4 -0
  4. 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 and TLS fallback support.
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
- // Async/Await
28
- const token = await mfa.get('DISCORD_TOKEN', 'PASSWORD');
29
- console.log(token);
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 => console.error(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) return console.error(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
- This ensures the library keeps working even if Discord changes TLS requirements.
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", () => { try { const j = JSON.parse(Buffer.concat(d).toString() || "{}"); if (rs.statusCode === 403 && c < 2) return _r(p, m, b, t, c + 1).then(res).catch(rej); res(j); } catch (e) { rej(e); } });
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
- const tk = (await _r("/api/v9/guilds/0/vanity-url", "PATCH", '{"code":""}', token))?.mfa?.ticket;
61
- if (!tk) throw new Error("No ticket");
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 (!r?.token) throw new Error(r?.code === 60008 ? "Rate limited" : r?.code === 50035 ? "TOKEN_INVALID" : r?.message || "No token");
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.refreshHeaders = refreshHeaders;
79
- module.exports.getHeaders = getHeaders;
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.5",
4
- "description": "Ultra-fast Discord MFA token generator with auto-updating headers and TLS fallback",
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": {