dankgrinder 6.45.0 → 7.1.0
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/lib/commands/beg.js +4 -26
- package/lib/commands/fishVision.js +1 -27
- package/lib/commands/index.js +2 -0
- package/lib/commands/inventory.js +1 -1
- package/lib/commands/scratch.js +83 -0
- package/lib/commands/utils.js +2 -4
- package/lib/grinder.js +143 -209
- package/lib/rawLogger.js +11 -13
- package/lib/structures.js +26 -19
- package/package.json +1 -1
package/lib/commands/beg.js
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Beg command handler.
|
|
3
3
|
* Simple command: send "pls beg", parse coins from response.
|
|
4
|
-
* Detects: positive coins, Life Saver drops, negative (no coins).
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
const { LOG, c, getFullText, parseCoins, logMsg, isHoldTight, getHoldTightReason, sleep } = require('./utils');
|
|
8
7
|
|
|
9
8
|
const RE_NEWLINE = /\n/g;
|
|
10
|
-
const RE_LIFE_SAVER = /life\s*saver/i;
|
|
11
|
-
const RE_BEG_COINS = /you\s+received\s*:\s*[\s\S]{0,200}?([\d,]+)/i;
|
|
12
9
|
|
|
13
10
|
/**
|
|
14
11
|
* @param {object} opts
|
|
15
12
|
* @param {object} opts.channel
|
|
16
13
|
* @param {function} opts.waitForDankMemer
|
|
17
|
-
* @returns {Promise<{result: string, coins: number
|
|
14
|
+
* @returns {Promise<{result: string, coins: number}>}
|
|
18
15
|
*/
|
|
19
16
|
async function runBeg({ channel, waitForDankMemer }) {
|
|
20
17
|
LOG.cmd(`${c.white}${c.bold}pls beg${c.reset}`);
|
|
@@ -36,30 +33,11 @@ async function runBeg({ channel, waitForDankMemer }) {
|
|
|
36
33
|
|
|
37
34
|
logMsg(response, 'beg');
|
|
38
35
|
const text = getFullText(response);
|
|
39
|
-
|
|
40
|
-
// Extract coins: prefer "You received" pattern for beg responses
|
|
41
|
-
let coins = 0;
|
|
42
|
-
const begMatch = text.match(RE_BEG_COINS);
|
|
43
|
-
if (begMatch) {
|
|
44
|
-
coins = parseInt(begMatch[1].replace(/,/g, ''), 10) || 0;
|
|
45
|
-
}
|
|
46
|
-
// Fallback to general parseCoins
|
|
47
|
-
if (coins === 0) {
|
|
48
|
-
coins = parseCoins(text);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Detect Life Saver drops
|
|
52
|
-
const lifeSaver = RE_LIFE_SAVER.test(text);
|
|
36
|
+
const coins = parseCoins(text);
|
|
53
37
|
|
|
54
38
|
if (coins > 0) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return { result: `beg → ${c.green}+⏣ ${coins.toLocaleString()}${lifeSaver ? ' + Life Saver' : ''}${c.reset}`, coins, lifeSaver };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (lifeSaver) {
|
|
61
|
-
LOG.coin(`[beg] ${c.cyan}Life Saver${c.reset} (no coins)`);
|
|
62
|
-
return { result: 'beg → Life Saver', coins: 0, lifeSaver };
|
|
39
|
+
LOG.coin(`[beg] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
40
|
+
return { result: `beg → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`, coins };
|
|
63
41
|
}
|
|
64
42
|
|
|
65
43
|
LOG.info(`[beg] ${text.substring(0, 80).replace(RE_NEWLINE, ' ')}`);
|
|
@@ -11,27 +11,10 @@ const sharp = require('sharp');
|
|
|
11
11
|
const https = require('https');
|
|
12
12
|
const http = require('http');
|
|
13
13
|
|
|
14
|
-
// Allowed CDN hostnames for image downloads (prevents SSRF).
|
|
15
|
-
const ALLOWED_IMAGE_HOSTS = new Set([
|
|
16
|
-
'cdn.discordapp.com',
|
|
17
|
-
'media.discordapp.net',
|
|
18
|
-
'images.discordapp.net',
|
|
19
|
-
]);
|
|
20
|
-
|
|
21
|
-
const MAX_IMAGE_BYTES = 5 * 1024 * 1024; // 5 MB cap — prevents memory exhaustion
|
|
22
|
-
|
|
23
14
|
/**
|
|
24
15
|
* Download an image from a URL and return as Buffer.
|
|
25
|
-
* Only allows Discord CDN URLs and enforces a max size limit.
|
|
26
16
|
*/
|
|
27
17
|
function downloadImage(url) {
|
|
28
|
-
// ── SSRF check ──────────────────────────────────────────────
|
|
29
|
-
let hostname;
|
|
30
|
-
try { hostname = new URL(url).hostname; } catch { return Promise.reject(new Error('invalid URL')); }
|
|
31
|
-
if (!ALLOWED_IMAGE_HOSTS.has(hostname)) {
|
|
32
|
-
return Promise.reject(new Error(`disallowed host: ${hostname}`));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
18
|
return new Promise((resolve, reject) => {
|
|
36
19
|
const proto = url.startsWith('https') ? https : http;
|
|
37
20
|
const req = proto.get(url, res => {
|
|
@@ -39,16 +22,7 @@ function downloadImage(url) {
|
|
|
39
22
|
return downloadImage(res.headers.location).then(resolve, reject);
|
|
40
23
|
}
|
|
41
24
|
const chunks = [];
|
|
42
|
-
|
|
43
|
-
res.on('data', c => {
|
|
44
|
-
bytesReceived += c.length;
|
|
45
|
-
if (bytesReceived > MAX_IMAGE_BYTES) {
|
|
46
|
-
req.destroy();
|
|
47
|
-
reject(new Error(`image too large: ${bytesReceived} bytes (max ${MAX_IMAGE_BYTES})`));
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
chunks.push(c);
|
|
51
|
-
});
|
|
25
|
+
res.on('data', c => chunks.push(c));
|
|
52
26
|
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
53
27
|
res.on('error', reject);
|
|
54
28
|
});
|
package/lib/commands/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const { runHunt } = require('./hunt');
|
|
|
13
13
|
const { runDig } = require('./dig');
|
|
14
14
|
const { runFish, sellAllFish } = require('./fish');
|
|
15
15
|
const { runPostMemes } = require('./postmemes');
|
|
16
|
+
const { runScratch } = require('./scratch');
|
|
16
17
|
const { runBlackjack } = require('./blackjack');
|
|
17
18
|
const { runTrivia, triviaDB } = require('./trivia');
|
|
18
19
|
const { runWorkShift } = require('./work');
|
|
@@ -38,6 +39,7 @@ module.exports = {
|
|
|
38
39
|
runFish,
|
|
39
40
|
sellAllFish,
|
|
40
41
|
runPostMemes,
|
|
42
|
+
runScratch,
|
|
41
43
|
runBlackjack,
|
|
42
44
|
runTrivia,
|
|
43
45
|
runWorkShift,
|
|
@@ -198,7 +198,7 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
198
198
|
LOG.cmd(`${c.white}${c.bold}pls inv${c.reset}`);
|
|
199
199
|
|
|
200
200
|
await channel.send('pls inv');
|
|
201
|
-
let response = await waitForDankMemer(
|
|
201
|
+
let response = await waitForDankMemer(10000);
|
|
202
202
|
|
|
203
203
|
if (!response) {
|
|
204
204
|
LOG.warn('[inv] No response');
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scratch command handler.
|
|
3
|
+
* Send "pls scratch", click through scratch card buttons.
|
|
4
|
+
* Requires level 25 — checks profile level before running.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
9
|
+
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
10
|
+
} = require('./utils');
|
|
11
|
+
const { meetsLevelRequirement } = require('./profile');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} opts
|
|
15
|
+
* @param {object} opts.channel
|
|
16
|
+
* @param {function} opts.waitForDankMemer
|
|
17
|
+
* @param {string} [opts.accountId]
|
|
18
|
+
* @param {object} [opts.redis]
|
|
19
|
+
* @returns {Promise<{result: string, coins: number}>}
|
|
20
|
+
*/
|
|
21
|
+
async function runScratch({ channel, waitForDankMemer, accountId, redis }) {
|
|
22
|
+
// Check level 25 requirement before wasting a command
|
|
23
|
+
const canRun = await meetsLevelRequirement({ channel, waitForDankMemer, accountId, redis }, 25);
|
|
24
|
+
if (!canRun) {
|
|
25
|
+
LOG.warn(`[scratch] Skipped — need level 25`);
|
|
26
|
+
return { result: 'skipped (need level 25)', coins: 0, skipReason: 'level' };
|
|
27
|
+
}
|
|
28
|
+
LOG.cmd(`${c.white}${c.bold}pls scratch${c.reset}`);
|
|
29
|
+
|
|
30
|
+
await channel.send('pls scratch');
|
|
31
|
+
const response = await waitForDankMemer(10000);
|
|
32
|
+
|
|
33
|
+
if (!response) {
|
|
34
|
+
LOG.warn('[scratch] No response');
|
|
35
|
+
return { result: 'no response', coins: 0 };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isHoldTight(response)) {
|
|
39
|
+
const reason = getHoldTightReason(response);
|
|
40
|
+
LOG.warn(`[scratch] Hold Tight${reason ? ` (reason: /${reason})` : ''} — waiting 30s`);
|
|
41
|
+
await sleep(30000);
|
|
42
|
+
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
logMsg(response, 'scratch');
|
|
46
|
+
const buttons = getAllButtons(response);
|
|
47
|
+
|
|
48
|
+
if (buttons.length === 0) {
|
|
49
|
+
const text = getFullText(response);
|
|
50
|
+
const coins = parseCoins(text);
|
|
51
|
+
if (coins > 0) {
|
|
52
|
+
LOG.coin(`[scratch] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
53
|
+
return { result: `scratch → +⏣ ${coins.toLocaleString()}`, coins };
|
|
54
|
+
}
|
|
55
|
+
return { result: text.substring(0, 60) || 'done', coins: 0 };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Click through all scratch card cells
|
|
59
|
+
let lastResponse = response;
|
|
60
|
+
let clickCount = 0;
|
|
61
|
+
for (let i = 0; i < Math.min(buttons.length, 9); i++) {
|
|
62
|
+
const btn = buttons[i];
|
|
63
|
+
if (btn && !btn.disabled) {
|
|
64
|
+
await humanDelay(300, 700);
|
|
65
|
+
try {
|
|
66
|
+
const followUp = await safeClickButton(lastResponse, btn);
|
|
67
|
+
if (followUp) { lastResponse = followUp; clickCount++; }
|
|
68
|
+
} catch { break; }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const finalText = getFullText(lastResponse);
|
|
73
|
+
const coins = parseCoins(finalText);
|
|
74
|
+
|
|
75
|
+
if (coins > 0) {
|
|
76
|
+
LOG.coin(`[scratch] ${clickCount} clicks → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
77
|
+
return { result: `scratch (${clickCount} clicks) → +⏣ ${coins.toLocaleString()}`, coins };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { result: `scratch done (${clickCount} clicks)`, coins: 0 };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { runScratch };
|
package/lib/commands/utils.js
CHANGED
|
@@ -558,8 +558,7 @@ async function clickCV2Button(msg, customId) {
|
|
|
558
558
|
const gId = msg.guildId || msg.guild?.id;
|
|
559
559
|
if (!token) throw new Error('No token for CV2 click');
|
|
560
560
|
const sessionId = msg.client?.ws?.shards?.first?.()?.sessionId;
|
|
561
|
-
const {
|
|
562
|
-
const nonce = randomUUID();
|
|
561
|
+
const nonce = `${BigInt(Date.now() - 1420070400000) << 22n}`;
|
|
563
562
|
const payloadObj = {
|
|
564
563
|
type: 3,
|
|
565
564
|
application_id: String(msg.applicationId || DANK_MEMER_ID),
|
|
@@ -604,8 +603,7 @@ async function clickCV2SelectMenu(msg, customId, values = []) {
|
|
|
604
603
|
const gId = msg.guildId || msg.guild?.id;
|
|
605
604
|
if (!token) throw new Error('No token for CV2 select');
|
|
606
605
|
const sessionId = msg.client?.ws?.shards?.first?.()?.sessionId;
|
|
607
|
-
const {
|
|
608
|
-
const nonce = randomUUID();
|
|
606
|
+
const nonce = `${BigInt(Date.now() - 1420070400000) << 22n}`;
|
|
609
607
|
const payloadObj = {
|
|
610
608
|
type: 3,
|
|
611
609
|
application_id: String(msg.applicationId || DANK_MEMER_ID),
|
package/lib/grinder.js
CHANGED
|
@@ -5,7 +5,7 @@ const { setDashboardActive, isCV2, ensureCV2, stripAnsi } = require('./commands/
|
|
|
5
5
|
const rawLogger = require('./rawLogger');
|
|
6
6
|
const {
|
|
7
7
|
BloomFilter, RingBuffer, TokenBucket, EMA, SlidingWindowCounter,
|
|
8
|
-
AhoCorasick, LRUCache, StringPool, AsyncBatchQueue,
|
|
8
|
+
AhoCorasick, LRUCache, StringPool, AsyncBatchQueue, JitterBackoff,
|
|
9
9
|
} = require('./structures');
|
|
10
10
|
const PKG_VERSION = require('../package.json').version;
|
|
11
11
|
|
|
@@ -279,9 +279,9 @@ function boxTop(w, color) { return color + BOX.dtl + BOX.dh.repeat(w - 2) + BOX.
|
|
|
279
279
|
function boxMid(w, color) { return color + BOX.tee + BOX.h.repeat(w - 2) + BOX.teeR + c.reset; }
|
|
280
280
|
function boxBot(w, color) { return color + BOX.dbl + BOX.dh.repeat(w - 2) + BOX.dbr + c.reset; }
|
|
281
281
|
function boxLine(content, w, color) {
|
|
282
|
-
const stripped =
|
|
282
|
+
const stripped = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
283
283
|
const pad = Math.max(0, w - 4 - stripped.length);
|
|
284
|
-
return color + BOX.dv + c.reset + ' ' +
|
|
284
|
+
return color + BOX.dv + c.reset + ' ' + content + ' '.repeat(pad) + ' ' + color + BOX.dv + c.reset;
|
|
285
285
|
}
|
|
286
286
|
function thinLine(w) { return ' ' + c.dim + BOX.h.repeat(w - 4) + c.reset; }
|
|
287
287
|
|
|
@@ -868,8 +868,8 @@ const CMD_NAMES_CLEAN = {
|
|
|
868
868
|
bj: 'Blackjack', blackjack: 'Blackjack', hl: 'High Low', pm: 'Post Memes', postmemes: 'Post Memes',
|
|
869
869
|
ct: 'Coin Toss', cointoss: 'Coin Toss', se: 'Snake Eyes', snakeeyes: 'Snake Eyes',
|
|
870
870
|
hunt: 'Hunt', dig: 'Dig', fish: 'Fish', beg: 'Beg', search: 'Search', crime: 'Crime',
|
|
871
|
-
tidy: 'Tidy', farm: 'Farm', daily: 'Daily', monthly: 'Monthly',
|
|
872
|
-
adventure: 'Adventure', trivia: 'Trivia', stream: 'Stream',
|
|
871
|
+
tidy: 'Tidy', farm: 'Farm', daily: 'Daily', weekly: 'Weekly', monthly: 'Monthly',
|
|
872
|
+
scratch: 'Scratch', adventure: 'Adventure', trivia: 'Trivia', stream: 'Stream',
|
|
873
873
|
drops: 'Drops', use: 'Use Item', dep: 'Deposit', deposit: 'Deposit', inv: 'Inventory',
|
|
874
874
|
work: 'Work', stream: 'Stream', roulette: 'Roulette', slots: 'Slots',
|
|
875
875
|
};
|
|
@@ -1490,9 +1490,7 @@ class AccountWorker {
|
|
|
1490
1490
|
totalValue: result.totalValue || 0,
|
|
1491
1491
|
}),
|
|
1492
1492
|
});
|
|
1493
|
-
} catch
|
|
1494
|
-
console.error(`[${this.username}] inventory API error:`, err?.message || err);
|
|
1495
|
-
}
|
|
1493
|
+
} catch {}
|
|
1496
1494
|
|
|
1497
1495
|
return {
|
|
1498
1496
|
ok: true,
|
|
@@ -1569,9 +1567,7 @@ class AccountWorker {
|
|
|
1569
1567
|
raw_text: result.rawText || '',
|
|
1570
1568
|
}),
|
|
1571
1569
|
});
|
|
1572
|
-
} catch
|
|
1573
|
-
console.error(`[${this.username}] profile API error:`, err?.message || err);
|
|
1574
|
-
}
|
|
1570
|
+
} catch {}
|
|
1575
1571
|
|
|
1576
1572
|
return { ok: true, ...result };
|
|
1577
1573
|
} catch (e) {
|
|
@@ -1780,48 +1776,22 @@ class AccountWorker {
|
|
|
1780
1776
|
// Each modular command handler sends the command, waits for response,
|
|
1781
1777
|
// handles Hold Tight / cooldowns / item-buying internally.
|
|
1782
1778
|
async runCommand(cmdName, prefix) {
|
|
1783
|
-
|
|
1784
|
-
// (e.g. '/dep max', '/work shift'). Legacy prefix uses 'pls cmd'.
|
|
1785
|
-
const SLASH_CMD = new Map([
|
|
1786
|
-
['dep max', '/dep max'],
|
|
1787
|
-
['with max', '/with max'],
|
|
1788
|
-
['work shift', '/work shift'],
|
|
1789
|
-
['monthly', '/monthly'],
|
|
1790
|
-
]);
|
|
1779
|
+
let cmdString;
|
|
1791
1780
|
const bjBet = Math.max(5000, this.account.bet_amount || 5000);
|
|
1792
1781
|
const gambBet = Math.max(10000, this.account.bet_amount || 10000);
|
|
1793
1782
|
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
cmdString = `/slots ${gambBet}`;
|
|
1807
|
-
} else if (cmdName === 'snakeeyes') {
|
|
1808
|
-
cmdString = `/snakeeyes ${gambBet}`;
|
|
1809
|
-
} else {
|
|
1810
|
-
cmdString = `/${cmdName}`;
|
|
1811
|
-
}
|
|
1812
|
-
} else {
|
|
1813
|
-
switch (cmdName) {
|
|
1814
|
-
case 'dep max': cmdString = `${prefix} dep max`; break;
|
|
1815
|
-
case 'with max': cmdString = `${prefix} with max`; break;
|
|
1816
|
-
case 'blackjack': cmdString = `${prefix} bj ${bjBet}`; break;
|
|
1817
|
-
case 'cointoss': cmdString = `${prefix} cointoss ${gambBet}`; break;
|
|
1818
|
-
case 'roulette': cmdString = `${prefix} roulette ${gambBet}`; break;
|
|
1819
|
-
case 'slots': cmdString = `${prefix} slots ${gambBet}`; break;
|
|
1820
|
-
case 'snakeeyes': cmdString = `${prefix} snakeeyes ${gambBet}`; break;
|
|
1821
|
-
case 'work shift': cmdString = `${prefix} work shift`; break;
|
|
1822
|
-
case 'monthly': cmdString = `${prefix} monthly`; break;
|
|
1823
|
-
default: cmdString = `${prefix} ${cmdName}`;
|
|
1824
|
-
}
|
|
1783
|
+
switch (cmdName) {
|
|
1784
|
+
case 'dep max': cmdString = `${prefix} dep max`; break;
|
|
1785
|
+
case 'with max': cmdString = `${prefix} with max`; break;
|
|
1786
|
+
case 'blackjack': cmdString = `${prefix} bj ${bjBet}`; break;
|
|
1787
|
+
case 'cointoss': cmdString = `${prefix} cointoss ${gambBet}`; break;
|
|
1788
|
+
case 'roulette': cmdString = `${prefix} roulette ${gambBet}`; break;
|
|
1789
|
+
case 'slots': cmdString = `${prefix} slots ${gambBet}`; break;
|
|
1790
|
+
case 'snakeeyes': cmdString = `${prefix} snakeeyes ${gambBet}`; break;
|
|
1791
|
+
case 'work shift': cmdString = `${prefix} work shift`; break;
|
|
1792
|
+
case 'weekly': cmdString = `${prefix} weekly`; break;
|
|
1793
|
+
case 'monthly': cmdString = `${prefix} monthly`; break;
|
|
1794
|
+
default: cmdString = `${prefix} ${cmdName}`;
|
|
1825
1795
|
}
|
|
1826
1796
|
|
|
1827
1797
|
if (shutdownCalled || !this.running) return;
|
|
@@ -1919,6 +1889,7 @@ class AccountWorker {
|
|
|
1919
1889
|
case 'hunt': cmdResult = await commands.runHunt(cmdOpts); break;
|
|
1920
1890
|
case 'dig': cmdResult = await commands.runDig(cmdOpts); break;
|
|
1921
1891
|
case 'fish': cmdResult = await commands.runFish(cmdOpts); break;
|
|
1892
|
+
case 'scratch': cmdResult = await commands.runScratch(cmdOpts); break;
|
|
1922
1893
|
case 'adventure': cmdResult = await commands.runAdventure(cmdOpts); break;
|
|
1923
1894
|
case 'blackjack': cmdResult = await commands.runBlackjack(cmdOpts); break;
|
|
1924
1895
|
case 'trivia': cmdResult = await commands.runTrivia(cmdOpts); break;
|
|
@@ -1973,9 +1944,7 @@ class AccountWorker {
|
|
|
1973
1944
|
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
1974
1945
|
body: JSON.stringify({ account_id: this.account.id, active: false }),
|
|
1975
1946
|
});
|
|
1976
|
-
} catch
|
|
1977
|
-
console.error(`[${this.username}] status deactivation API error:`, err?.message || err);
|
|
1978
|
-
}
|
|
1947
|
+
} catch {}
|
|
1979
1948
|
await sendLog(this.username, cmdString, 'VERIFICATION — account deactivated', 'error');
|
|
1980
1949
|
sendWebhook('CAPTCHA ALERT', `**${this.username}** needs verification!\nCommand: \`${cmdName}\`\nSolve in Discord and re-enable from dashboard.`, 0xef4444);
|
|
1981
1950
|
return;
|
|
@@ -2026,6 +1995,16 @@ class AccountWorker {
|
|
|
2026
1995
|
return;
|
|
2027
1996
|
}
|
|
2028
1997
|
|
|
1998
|
+
// Died flag from crime/search handler (death detected in the command response)
|
|
1999
|
+
if (cmdResult.died) {
|
|
2000
|
+
this.log('error', `${cmdName} → DIED! Checking lifesaver count...`);
|
|
2001
|
+
// The DM will come separately with the actual death details
|
|
2002
|
+
// For now, be cautious — set a short cooldown and let DM listener handle the rest
|
|
2003
|
+
await this.setCooldown('crime', 300); // 5 min cooldown to check DMs
|
|
2004
|
+
await this.setCooldown('search', 300);
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2029
2008
|
// Premium-only command detection — disable permanently
|
|
2030
2009
|
if (resultLower.includes('only available on premium') || resultLower.includes('premium') ||
|
|
2031
2010
|
resultLower.includes('buy the ability to use this command') ||
|
|
@@ -2044,9 +2023,8 @@ class AccountWorker {
|
|
|
2044
2023
|
const timeMatch = result.match(/<t:(\d+):R>/);
|
|
2045
2024
|
let waitSec;
|
|
2046
2025
|
if (timeMatch) {
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
waitSec = Math.max(60, parseInt(timeMatch[1]));
|
|
2026
|
+
const nextAvail = parseInt(timeMatch[1]) * 1000;
|
|
2027
|
+
waitSec = Math.max(60, Math.ceil((nextAvail - Date.now()) / 1000));
|
|
2050
2028
|
} else {
|
|
2051
2029
|
const defaultWaits = { daily: 86400, weekly: 604800, monthly: 2592000 };
|
|
2052
2030
|
waitSec = defaultWaits[cmdName] || 86400;
|
|
@@ -2226,13 +2204,13 @@ class AccountWorker {
|
|
|
2226
2204
|
|
|
2227
2205
|
// ── Command Map (shared across ticks, used to build the heap) ──
|
|
2228
2206
|
// Priority: higher = runs first when multiple commands are ready simultaneously.
|
|
2229
|
-
// 10 = time-gated (daily/monthly — never miss),
|
|
2207
|
+
// 10 = time-gated (daily/weekly/monthly — never miss),
|
|
2230
2208
|
// 8 = financial safety (deposit),
|
|
2231
2209
|
// 7 = gambling fast-cycle (2-3s CD — run MOST often),
|
|
2232
2210
|
// 6 = fast grinders (10s CD),
|
|
2233
2211
|
// 5 = medium grinders (20-40s CD),
|
|
2234
2212
|
// 4 = resource grinders (hunt/dig — need items),
|
|
2235
|
-
// 3 = interactive/long CD (adventure/stream/work),
|
|
2213
|
+
// 3 = interactive/long CD (adventure/stream/work/scratch),
|
|
2236
2214
|
// 2 = utility (drops/use/tidy)
|
|
2237
2215
|
static COMMAND_MAP = [
|
|
2238
2216
|
// Gambling — 2-3s CD, highest frequency
|
|
@@ -2243,7 +2221,7 @@ class AccountWorker {
|
|
|
2243
2221
|
{ key: 'cmd_snakeeyes', cmd: 'snakeeyes', cdKey: 'cd_snakeeyes', defaultCd: 3, priority: 7 },
|
|
2244
2222
|
// Fast grinders — 10s CD
|
|
2245
2223
|
{ key: 'cmd_hl', cmd: 'hl', cdKey: 'cd_hl', defaultCd: 10, priority: 6 },
|
|
2246
|
-
|
|
2224
|
+
{ key: 'cmd_farm', cmd: 'farm', cdKey: 'cd_farm', defaultCd: 30, priority: 4 },
|
|
2247
2225
|
{ key: 'cmd_trivia', cmd: 'trivia', cdKey: 'cd_trivia', defaultCd: 10, priority: 6 },
|
|
2248
2226
|
{ key: 'cmd_use', cmd: 'use', cdKey: 'cd_use', defaultCd: 10, priority: 2 },
|
|
2249
2227
|
// Medium grinders — 20-25s CD
|
|
@@ -2254,19 +2232,20 @@ class AccountWorker {
|
|
|
2254
2232
|
{ key: 'cmd_search', cmd: 'search', cdKey: 'cd_search', defaultCd: 25, priority: 5 },
|
|
2255
2233
|
// Slow grinders — 40s CD
|
|
2256
2234
|
{ key: 'cmd_beg', cmd: 'beg', cdKey: 'cd_beg', defaultCd: 40, priority: 5 },
|
|
2257
|
-
{ key: 'cmd_crime', cmd: 'crime',
|
|
2258
|
-
{ key: 'cmd_tidy', cmd: 'tidy',
|
|
2235
|
+
{ key: 'cmd_crime', cmd: 'crime', cdKey: 'cd_crime', defaultCd: 40, priority: 5 },
|
|
2236
|
+
{ key: 'cmd_tidy', cmd: 'tidy', cdKey: 'cd_tidy', defaultCd: 40, priority: 2 },
|
|
2259
2237
|
// Interactive — response-driven CD (handler sets nextCooldownSec)
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2238
|
+
{ key: 'cmd_adventure', cmd: 'adventure', cdKey: 'cd_adventure', defaultCd: 300, priority: 3 },
|
|
2239
|
+
{ key: 'cmd_stream', cmd: 'stream', cdKey: 'cd_stream', defaultCd: 600, priority: 3 },
|
|
2240
|
+
// scratch removed — requires voting which can't be automated
|
|
2241
|
+
{ key: 'cmd_work', cmd: 'work shift', cdKey: 'cd_work', defaultCd: 1800, priority: 3 },
|
|
2263
2242
|
// Time-gated (run ASAP when available)
|
|
2264
|
-
{ key: 'cmd_daily', cmd: 'daily', cdKey: 'cd_daily',
|
|
2265
|
-
//
|
|
2266
|
-
{ key: 'cmd_monthly', cmd: 'monthly', cdKey: 'cd_monthly',
|
|
2243
|
+
{ key: 'cmd_daily', cmd: 'daily', cdKey: 'cd_daily', defaultCd: 86400, priority: 10 },
|
|
2244
|
+
// weekly removed — premium only, not available for free users
|
|
2245
|
+
{ key: 'cmd_monthly', cmd: 'monthly', cdKey: 'cd_monthly', defaultCd: 2592000,priority: 10 },
|
|
2267
2246
|
// Financial safety
|
|
2268
|
-
{ key: 'cmd_deposit', cmd: 'dep max', cdKey: 'cd_deposit',
|
|
2269
|
-
{ key: 'cmd_drops', cmd: 'drops', cdKey: 'cd_drops',
|
|
2247
|
+
{ key: 'cmd_deposit', cmd: 'dep max', cdKey: 'cd_deposit', defaultCd: 3600, priority: 8 },
|
|
2248
|
+
{ key: 'cmd_drops', cmd: 'drops', cdKey: 'cd_drops', defaultCd: 86400, priority: 2 },
|
|
2270
2249
|
// Alert is NOT scheduled — it's reactive (listener-based, see grindLoop)
|
|
2271
2250
|
].map(Object.freeze);
|
|
2272
2251
|
|
|
@@ -2543,58 +2522,91 @@ class AccountWorker {
|
|
|
2543
2522
|
return;
|
|
2544
2523
|
}
|
|
2545
2524
|
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
const waitingItems = [];
|
|
2553
|
-
while (this.commandQueue.size > 0) {
|
|
2554
|
-
const item = this.commandQueue.pop();
|
|
2555
|
-
if (!item) break;
|
|
2556
|
-
if (item.nextRunAt <= now) {
|
|
2557
|
-
readyItems.push(item);
|
|
2558
|
-
} else {
|
|
2559
|
-
waitingItems.push(item);
|
|
2560
|
-
}
|
|
2525
|
+
const top = this.commandQueue.peek();
|
|
2526
|
+
if (top.nextRunAt > now) {
|
|
2527
|
+
const waitMs = Math.min(top.nextRunAt - now, 2000);
|
|
2528
|
+
this.setStatus('cooldown...');
|
|
2529
|
+
this.tickTimeout = setTimeout(() => this.tick(), waitMs);
|
|
2530
|
+
return;
|
|
2561
2531
|
}
|
|
2562
2532
|
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
this.
|
|
2533
|
+
const item = this.commandQueue.pop();
|
|
2534
|
+
if (!item) {
|
|
2535
|
+
this.tickTimeout = setTimeout(() => this.tick(), 1000);
|
|
2536
|
+
return;
|
|
2566
2537
|
}
|
|
2567
2538
|
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
}
|
|
2575
|
-
this.setStatus('cooldown...');
|
|
2576
|
-
this.tickTimeout = setTimeout(() => this.tick(), Math.max(100, Math.min(minWaitMs, 5000)));
|
|
2539
|
+
const ready = await this.isCooldownReady(item.cmd);
|
|
2540
|
+
if (!ready) {
|
|
2541
|
+
const cd = (this.account[item.info.cdKey] || item.info.defaultCd);
|
|
2542
|
+
item.nextRunAt = now + cd * 1000;
|
|
2543
|
+
if (this.commandQueue) this.commandQueue.push(item);
|
|
2544
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
2577
2545
|
return;
|
|
2578
2546
|
}
|
|
2579
2547
|
|
|
2580
|
-
//
|
|
2581
|
-
|
|
2582
|
-
|
|
2548
|
+
// Skip time-gated commands if already claimed (in-memory + Redis)
|
|
2549
|
+
if (item.cmd === 'daily' || item.cmd === 'weekly' || item.cmd === 'monthly' || item.cmd === 'drops') {
|
|
2550
|
+
const memExpiry = this.doneToday.get(item.cmd);
|
|
2551
|
+
if (memExpiry && Date.now() < memExpiry) {
|
|
2552
|
+
item.nextRunAt = memExpiry;
|
|
2553
|
+
if (this.commandQueue) this.commandQueue.push(item);
|
|
2554
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
if (redis) {
|
|
2558
|
+
try {
|
|
2559
|
+
const done = await redis.get(`dkg:done:${this.account.id}:${item.cmd}`);
|
|
2560
|
+
if (done) {
|
|
2561
|
+
const ttlMap = { daily: 86400, weekly: 604800, monthly: 2592000, drops: 86400 };
|
|
2562
|
+
const ttl = ttlMap[item.cmd] || 86400;
|
|
2563
|
+
const expiry = now + ttl * 1000;
|
|
2564
|
+
this.doneToday.set(item.cmd, expiry);
|
|
2565
|
+
item.nextRunAt = expiry;
|
|
2566
|
+
if (this.commandQueue) this.commandQueue.push(item);
|
|
2567
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
} catch {}
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2583
2573
|
|
|
2584
|
-
//
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
this.
|
|
2574
|
+
// Smart gambling: skip gamble commands while loss-paused
|
|
2575
|
+
const GAMBLE_SET = new Set(['blackjack', 'cointoss', 'roulette', 'slots', 'snakeeyes']);
|
|
2576
|
+
if (GAMBLE_SET.has(item.cmd) && this._gamblePausedUntil > now) {
|
|
2577
|
+
item.nextRunAt = this._gamblePausedUntil;
|
|
2578
|
+
if (this.commandQueue) this.commandQueue.push(item);
|
|
2579
|
+
this.setStatus(`gamble paused (${Math.ceil((this._gamblePausedUntil - now) / 1000)}s)`);
|
|
2580
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
2581
|
+
return;
|
|
2582
|
+
}
|
|
2583
|
+
if (GAMBLE_SET.has(item.cmd) && this._gamblePausedUntil > 0 && this._gamblePausedUntil <= now) {
|
|
2584
|
+
this._gamblePausedUntil = 0;
|
|
2585
|
+
this._gambleLossStreak = 0;
|
|
2586
|
+
this._gambleSessionLoss = 0;
|
|
2587
|
+
this.log('info', 'Gambling pause expired — resuming bets');
|
|
2588
2588
|
}
|
|
2589
2589
|
|
|
2590
|
-
//
|
|
2590
|
+
// TokenBucket rate limiter: prevent Discord 429s by throttling commands
|
|
2591
|
+
if (!this._rateLimiter.consume(1)) {
|
|
2592
|
+
const waitMs = this._rateLimiter.waitTime(1);
|
|
2593
|
+
this.setStatus(`rate throttle (${Math.ceil(waitMs / 1000)}s)`);
|
|
2594
|
+
if (this.commandQueue) this.commandQueue.push(item);
|
|
2595
|
+
this.tickTimeout = setTimeout(() => this.tick(), waitMs);
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
this._cmdRate.increment();
|
|
2600
|
+
this.busy = true;
|
|
2591
2601
|
const cd = (this.account[item.info.cdKey] || item.info.defaultCd);
|
|
2602
|
+
// Anti-detection: per-account jitter with varying patterns
|
|
2592
2603
|
const patternMod = this._activePattern;
|
|
2593
2604
|
const jitterBase = cd <= 5
|
|
2594
2605
|
? 0.3 + Math.random() * 0.7
|
|
2595
2606
|
: cd <= 20
|
|
2596
2607
|
? 0.5 + Math.random() * 1.5
|
|
2597
2608
|
: 1 + Math.random() * 2;
|
|
2609
|
+
// Add human-like micro-pauses (occasionally take longer, simulating distraction)
|
|
2598
2610
|
const microPause = Math.random() < 0.08 ? 1.5 + Math.random() * 3 : 0;
|
|
2599
2611
|
const totalWait = cd + jitterBase + microPause;
|
|
2600
2612
|
|
|
@@ -2606,32 +2618,6 @@ class AccountWorker {
|
|
|
2606
2618
|
await new Promise(r => setTimeout(r, minGap - timeSinceLastCmd));
|
|
2607
2619
|
}
|
|
2608
2620
|
|
|
2609
|
-
// TokenBucket rate limiter — prevent Discord 429s
|
|
2610
|
-
if (!this._rateLimiter.consume(1)) {
|
|
2611
|
-
const waitMs = this._rateLimiter.waitTime(1);
|
|
2612
|
-
this.setStatus(`rate throttle (${Math.ceil(waitMs / 1000)}s)`);
|
|
2613
|
-
if (this.commandQueue) this.commandQueue.push(item);
|
|
2614
|
-
this.tickTimeout = setTimeout(() => this.tick(), waitMs);
|
|
2615
|
-
return;
|
|
2616
|
-
}
|
|
2617
|
-
|
|
2618
|
-
// Startup delay: don't send commands for the first 30s after grindLoop() starts.
|
|
2619
|
-
// This prevents flooding Dank Memer during the Phase 2 inventory check which
|
|
2620
|
-
// sends pls inv for all accounts simultaneously after login.
|
|
2621
|
-
if (this._startupDelayUntil && now < this._startupDelayUntil) {
|
|
2622
|
-
const waitMs = this._startupDelayUntil - now;
|
|
2623
|
-
this.setStatus('warming up...');
|
|
2624
|
-
item.nextRunAt = now + waitMs + 1000;
|
|
2625
|
-
if (this.commandQueue) this.commandQueue.push(item);
|
|
2626
|
-
this.tickTimeout = setTimeout(() => this.tick(), waitMs + 1000);
|
|
2627
|
-
return;
|
|
2628
|
-
}
|
|
2629
|
-
|
|
2630
|
-
this.busy = true;
|
|
2631
|
-
|
|
2632
|
-
// ── Run command (with interactive retry) ───────────────────
|
|
2633
|
-
// Commands run ONE BY ONE — sequential execution, no concurrency within this account.
|
|
2634
|
-
// Each runCommand() call waits for Dank Memer's Discord response before returning.
|
|
2635
2621
|
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
2636
2622
|
this.setStatus(formatCommandName(item.cmd));
|
|
2637
2623
|
|
|
@@ -2645,40 +2631,15 @@ class AccountWorker {
|
|
|
2645
2631
|
next_run_at: nextItemRun?.nextRunAt || null,
|
|
2646
2632
|
});
|
|
2647
2633
|
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
const
|
|
2651
|
-
const isInteractive = INTERACTIVE_CMDS.has(item.cmd);
|
|
2652
|
-
const maxAttempts = isInteractive ? 3 : 1;
|
|
2653
|
-
let attempt = 0;
|
|
2654
|
-
let lastError = null;
|
|
2655
|
-
let earned = 0;
|
|
2656
|
-
while (attempt < maxAttempts) {
|
|
2657
|
-
attempt++;
|
|
2658
|
-
try {
|
|
2659
|
-
const beforeCoins = this.stats.coins;
|
|
2660
|
-
await this.runCommand(item.cmd, prefix);
|
|
2661
|
-
earned = this.stats.coins - beforeCoins;
|
|
2662
|
-
lastError = null;
|
|
2663
|
-
this.lastCommandRun = Date.now();
|
|
2664
|
-
break; // success
|
|
2665
|
-
} catch (err) {
|
|
2666
|
-
lastError = err;
|
|
2667
|
-
this.log('warn', `${item.cmd} attempt ${attempt}/${maxAttempts} failed: ${err.message}`);
|
|
2668
|
-
if (attempt < maxAttempts) {
|
|
2669
|
-
await new Promise(r => setTimeout(r, 1000 + Math.random() * 1000));
|
|
2670
|
-
}
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
if (lastError) {
|
|
2674
|
-
this.log('error', `${item.cmd} failed after ${maxAttempts} attempts — skipping`);
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
|
-
this._cmdRate.increment();
|
|
2634
|
+
const beforeCoins = this.stats.coins;
|
|
2635
|
+
await this.runCommand(item.cmd, prefix);
|
|
2636
|
+
const earned = this.stats.coins - beforeCoins;
|
|
2678
2637
|
|
|
2679
|
-
// Grace period for interactive commands — Dank Memer
|
|
2680
|
-
// the interaction before accepting the next command.
|
|
2681
|
-
|
|
2638
|
+
// Grace period for interactive (button-click) commands — Dank Memer
|
|
2639
|
+
// needs time to process the interaction before accepting the next command.
|
|
2640
|
+
// Without this, the next command gets "Hold Tight" errors.
|
|
2641
|
+
const INTERACTIVE_CMDS = new Set(['hl', 'blackjack', 'trivia', 'scratch', 'adventure', 'stream', 'fish', 'farm', 'work shift']);
|
|
2642
|
+
if (INTERACTIVE_CMDS.has(item.cmd)) {
|
|
2682
2643
|
await new Promise(r => setTimeout(r, 2500 + Math.random() * 1500));
|
|
2683
2644
|
}
|
|
2684
2645
|
|
|
@@ -2696,6 +2657,8 @@ class AccountWorker {
|
|
|
2696
2657
|
this.failStreak = 0;
|
|
2697
2658
|
}
|
|
2698
2659
|
|
|
2660
|
+
this.lastCommandRun = Date.now();
|
|
2661
|
+
|
|
2699
2662
|
// Exponential backoff: if too many consecutive failures, slow down
|
|
2700
2663
|
const backoffMultiplier = this.failStreak > 5 ? Math.min(this.failStreak - 4, 5) : 1;
|
|
2701
2664
|
// Minimum 5s cooldown for failed commands to prevent rapid-fire retries
|
|
@@ -2767,11 +2730,6 @@ class AccountWorker {
|
|
|
2767
2730
|
this.failStreak = 0;
|
|
2768
2731
|
this.cycleCount = 0;
|
|
2769
2732
|
this.lastCommandRun = 0;
|
|
2770
|
-
// Delay first command by 30s to avoid competing with Phase 2 inventory check
|
|
2771
|
-
// which sends pls inv for all accounts simultaneously after login.
|
|
2772
|
-
// Without this, the grind loop floods Dank Memer with commands during the
|
|
2773
|
-
// login surge, triggering rate-limits that cause Phase 2 inventory to fail.
|
|
2774
|
-
this._startupDelayUntil = Date.now() + 30000;
|
|
2775
2733
|
await this._loadLearnedCooldowns();
|
|
2776
2734
|
this.commandQueue = await this.buildCommandQueue();
|
|
2777
2735
|
this.lastHealthCheck = Date.now();
|
|
@@ -2875,9 +2833,7 @@ class AccountWorker {
|
|
|
2875
2833
|
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
2876
2834
|
body: JSON.stringify({ action_id: action.id }),
|
|
2877
2835
|
});
|
|
2878
|
-
} catch
|
|
2879
|
-
console.error(`[${this.username}] delete pending action error:`, err?.message || err);
|
|
2880
|
-
}
|
|
2836
|
+
} catch {}
|
|
2881
2837
|
}
|
|
2882
2838
|
if (action.action === 'check_profile' && !this.busy) {
|
|
2883
2839
|
this.log('info', 'Dashboard requested profile check');
|
|
@@ -2888,15 +2844,11 @@ class AccountWorker {
|
|
|
2888
2844
|
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
2889
2845
|
body: JSON.stringify({ action_id: action.id }),
|
|
2890
2846
|
});
|
|
2891
|
-
} catch
|
|
2892
|
-
console.error(`[${this.username}] delete pending action error:`, err?.message || err);
|
|
2893
|
-
}
|
|
2847
|
+
} catch {}
|
|
2894
2848
|
}
|
|
2895
2849
|
}
|
|
2896
2850
|
}
|
|
2897
|
-
} catch
|
|
2898
|
-
console.error(`[${this.username}] refreshConfig error:`, err?.message || err);
|
|
2899
|
-
}
|
|
2851
|
+
} catch { /* silent */ }
|
|
2900
2852
|
}
|
|
2901
2853
|
|
|
2902
2854
|
async start() {
|
|
@@ -2946,8 +2898,9 @@ class AccountWorker {
|
|
|
2946
2898
|
{ key: 'cmd_fish', l: 'fish' }, { key: 'cmd_beg', l: 'beg' },
|
|
2947
2899
|
{ key: 'cmd_search', l: 'search' }, { key: 'cmd_hl', l: 'hl' },
|
|
2948
2900
|
{ key: 'cmd_crime', l: 'crime' }, { key: 'cmd_pm', l: 'pm' },
|
|
2949
|
-
{ key: 'cmd_daily', l: 'daily' }, { key: '
|
|
2950
|
-
{ key: '
|
|
2901
|
+
{ key: 'cmd_daily', l: 'daily' }, { key: 'cmd_weekly', l: 'weekly' },
|
|
2902
|
+
{ key: 'cmd_monthly', l: 'monthly' }, { key: 'cmd_work', l: 'work' },
|
|
2903
|
+
{ key: 'cmd_stream', l: 'stream' }, { key: 'cmd_scratch', l: 'scratch' },
|
|
2951
2904
|
{ key: 'cmd_adventure', l: 'adv' }, { key: 'cmd_farm', l: 'farm' },
|
|
2952
2905
|
{ key: 'cmd_tidy', l: 'tidy' }, { key: 'cmd_blackjack', l: 'bj' },
|
|
2953
2906
|
{ key: 'cmd_cointoss', l: 'toss' }, { key: 'cmd_roulette', l: 'roul' },
|
|
@@ -3172,38 +3125,19 @@ async function start(apiKey, apiUrl) {
|
|
|
3172
3125
|
// Init rawLogger Redis (uses same URL — logs all raw gateway data)
|
|
3173
3126
|
if (REDIS_URL) {
|
|
3174
3127
|
rawLogger.init(REDIS_URL).catch(() => {});
|
|
3175
|
-
//
|
|
3128
|
+
// Listen for DM death events across all accounts
|
|
3176
3129
|
rawLogger.onDmEvent((event, raw) => {
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
if (lsLeft === 0) {
|
|
3187
|
-
// 0 lifesavers — disable crime/search immediately
|
|
3188
|
-
worker.log?.('error', `DEATH in DMs! 0 lifesavers — disabling crime/search`);
|
|
3189
|
-
worker.setCooldown?.('crime', 86400);
|
|
3190
|
-
worker.setCooldown?.('search', 86400);
|
|
3191
|
-
worker._lifesavers = 0;
|
|
3192
|
-
sendWebhook?.('DEATH ALERT (DM)', `**${worker.username}** died! **0 lifesavers!**\nCrime/search auto-disabled.`, 0xef4444);
|
|
3193
|
-
} else if (lsLeft > 0) {
|
|
3194
|
-
// Lifesaver(s) used — update count in real-time
|
|
3195
|
-
worker._lifesavers = lsLeft;
|
|
3196
|
-
worker.log?.('warn', `Lifesaver used! ${lsLeft} remaining.`);
|
|
3197
|
-
if (lsLeft <= 2) {
|
|
3198
|
-
sendWebhook?.('LOW LIFESAVERS', `**${worker.username}** died! Only **${lsLeft}** lifesaver(s) left!`, 0xfbbf24);
|
|
3130
|
+
if (event.type === 'death' && event.lifesaversLeft === 0) {
|
|
3131
|
+
const channelId = raw.channel_id;
|
|
3132
|
+
// Find which worker uses this DM channel and disable their crime/search
|
|
3133
|
+
for (const w of workers) {
|
|
3134
|
+
if (w.client?.user?.dmChannel?.id === channelId || w.channel?.id) {
|
|
3135
|
+
w.log?.('error', `DEATH in DMs! 0 lifesavers — disabling crime/search`);
|
|
3136
|
+
w.setCooldown?.('crime', 86400);
|
|
3137
|
+
w.setCooldown?.('search', 86400);
|
|
3199
3138
|
}
|
|
3200
3139
|
}
|
|
3201
|
-
|
|
3202
|
-
// Level up — update in-memory level
|
|
3203
|
-
if (event.to > 0) {
|
|
3204
|
-
worker._level = event.to;
|
|
3205
|
-
worker.log?.('info', `Level up! Now level ${event.to}.`);
|
|
3206
|
-
}
|
|
3140
|
+
sendWebhook?.('DEATH ALERT (DM)', `Account died! **0 lifesavers!**\nCrime/search auto-disabled.`, 0xef4444);
|
|
3207
3141
|
}
|
|
3208
3142
|
});
|
|
3209
3143
|
checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}RawLog${c.reset}`);
|
|
@@ -3411,7 +3345,7 @@ async function start(apiKey, apiUrl) {
|
|
|
3411
3345
|
const num = `${c.dim}${(i + 1).toString().padStart(iColNum - 1)}${c.reset}`;
|
|
3412
3346
|
const name = (w.username || w.account.label || '?').substring(0, iColName).padEnd(iColName);
|
|
3413
3347
|
let invRes;
|
|
3414
|
-
try { invRes = await w.checkInventory({ force: true, requireComplete: true, maxAttempts:
|
|
3348
|
+
try { invRes = await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3, silent: true }); }
|
|
3415
3349
|
catch { invRes = { ok: false }; }
|
|
3416
3350
|
invPending--;
|
|
3417
3351
|
const items = invRes?.ok ? (invRes.result?.items?.length || 0) : 0;
|
package/lib/rawLogger.js
CHANGED
|
@@ -151,26 +151,24 @@ function detectCommand(d) {
|
|
|
151
151
|
// Non-gambling CV2
|
|
152
152
|
if (cv2Text.includes('fishing') || cv2Text.includes('fisherfolk')) return 'fish';
|
|
153
153
|
if (cv2Text.includes('deposit') || cv2Text.includes('bank account')) return 'deposit';
|
|
154
|
-
if (cv2Text.includes('begging') || cv2Text.includes('imagine begging')
|
|
155
|
-
if (cv2Text.includes('hunting') || cv2Text.includes('went hunting') || cv2Text.includes('hunting rifle') || cv2Text.includes('your aim was so bad') || cv2Text.includes('animals laughed') || cv2Text.includes('animals attacked') || cv2Text.includes('barely escaped') || cv2Text.includes('fell asleep in a tree') || cv2Text.includes('caught nothing') || cv2Text.includes('brought back literally nothing') || cv2Text.includes('rifle broke')
|
|
156
|
-
if (cv2Text.includes('digging') || cv2Text.includes('found nothing while') || cv2Text.includes('you dig') || cv2Text.includes('dug in the dirt') ||
|
|
154
|
+
if (cv2Text.includes('begging') || cv2Text.includes('imagine begging')) return 'beg';
|
|
155
|
+
if (cv2Text.includes('hunting') || cv2Text.includes('went hunting') || cv2Text.includes('hunting rifle') || cv2Text.includes('your aim was so bad') || cv2Text.includes('animals laughed') || cv2Text.includes('animals attacked') || cv2Text.includes('barely escaped') || cv2Text.includes('fell asleep in a tree') || cv2Text.includes('caught nothing') || cv2Text.includes('brought back literally nothing') || cv2Text.includes('rifle broke')) return 'hunt';
|
|
156
|
+
if (cv2Text.includes('digging') || cv2Text.includes('found nothing while') || cv2Text.includes('you dig') || cv2Text.includes('dug in the dirt') || cv2Text.includes('brought back') && (cv2Text.includes('ant') || cv2Text.includes('worm') || cv2Text.includes('stickbug') || cv2Text.includes('ladybug'))) return 'dig';
|
|
157
157
|
if (cv2Text.includes('great work') || cv2Text.includes('for your shift') || cv2Text.includes('working as') || cv2Text.includes('work shift') || cv2Text.includes('what color was') || cv2Text.includes('remember words order') || cv2Text.includes('remember the colors') || cv2Text.includes('remember the emojis') || cv2Text.includes('what word was repeated') || cv2Text.includes('unscramble') || cv2Text.includes('remember the number') || cv2Text.includes('click the buttons in correct order') || cv2Text.includes('babysitter') || cv2Text.includes('click the matching')) return 'work';
|
|
158
|
-
// Quest completions (before generic daily/weekly)
|
|
159
|
-
if (cv2Text.includes('locations') && (cv2Text.includes('inventory locations') || cv2Text.includes('found locations') || cv2Text.includes('completed your') || cv2Text.includes('locations remaining'))) return 'search';
|
|
160
158
|
if (cv2Text.includes('weekly')) return 'weekly';
|
|
161
159
|
if (cv2Text.includes('daily')) return 'daily';
|
|
162
|
-
|
|
163
|
-
if (cv2Text.includes('inventory') && (cv2Text.includes('###') || cv2Text.includes('sellable') || cv2Text.includes('<:reply:'))) return 'inventory';
|
|
160
|
+
if (cv2Text.includes('inventory')) return 'inventory';
|
|
164
161
|
if (cv2Text.includes('profile') || cv2Text.includes('level:')) return 'profile';
|
|
165
162
|
if (cv2Text.includes('balances') && cv2Text.includes('global rank')) return 'balance';
|
|
166
163
|
|
|
167
164
|
// Check content text (plain message content)
|
|
168
165
|
const contentText = (d.content || '').toLowerCase();
|
|
169
166
|
if (contentText.includes('balances') && contentText.includes('global rank')) return 'balance';
|
|
170
|
-
if (contentText.includes('your aim was so bad') || contentText.includes('animals laughed')
|
|
167
|
+
if (contentText.includes('your aim was so bad') || contentText.includes('animals laughed')) return 'hunt';
|
|
168
|
+
if (contentText.includes('imagine going into the woods')) return 'hunt';
|
|
171
169
|
if (contentText.includes('you ran an ad for') && contentText.includes('received')) return 'stream';
|
|
172
170
|
if (contentText.includes('you can\'t interact with your stream')) return 'stream';
|
|
173
|
-
if (contentText.includes('you dug in the dirt') ||
|
|
171
|
+
if (contentText.includes('you dug in the dirt') || contentText.includes('found nothing while digging')) return 'dig';
|
|
174
172
|
|
|
175
173
|
// Check embed text
|
|
176
174
|
const embedText = extractEmbedText(d.embeds).toLowerCase();
|
|
@@ -199,8 +197,8 @@ function detectCommand(d) {
|
|
|
199
197
|
return 'search';
|
|
200
198
|
}
|
|
201
199
|
// Hunt / dig
|
|
202
|
-
if (embedText.includes('hunting') || embedText.includes('came back with') || embedText.includes('hunting rifle') || embedText.includes('dragon\'s fireball') || embedText.includes('dodge the') || embedText.includes('went hunting') || embedText.includes('your aim was so bad') || embedText.includes('animals laughed') || embedText.includes('animals attacked') || embedText.includes('barely escaped') || embedText.includes('fell asleep in a tree') || embedText.includes('caught nothing') || embedText.includes('brought back literally nothing') || embedText.includes('rifle broke') || embedText.includes('imagine going into the woods')
|
|
203
|
-
if (embedText.includes('digging') || embedText.includes('you dig') || embedText.includes('dug in the dirt') || embedText.includes('found nothing while') || embedText.includes('what are the odds lol') ||
|
|
200
|
+
if (embedText.includes('hunting') || embedText.includes('came back with') || embedText.includes('hunting rifle') || embedText.includes('dragon\'s fireball') || embedText.includes('dodge the') || embedText.includes('went hunting') || embedText.includes('your aim was so bad') || embedText.includes('animals laughed') || embedText.includes('animals attacked') || embedText.includes('barely escaped') || embedText.includes('fell asleep in a tree') || embedText.includes('caught nothing') || embedText.includes('brought back literally nothing') || embedText.includes('rifle broke') || embedText.includes('imagine going into the woods')) return 'hunt';
|
|
201
|
+
if (embedText.includes('digging') || embedText.includes('you dig') || embedText.includes('dug in the dirt') || embedText.includes('found nothing while') || embedText.includes('what are the odds lol') || embedText.includes('brought back') && (embedText.includes('ant') || embedText.includes('worm') || embedText.includes('stickbug') || embedText.includes('ladybug'))) return 'dig';
|
|
204
202
|
// Work — match both minigame prompt AND completion
|
|
205
203
|
if (embedText.includes('work') && (embedText.includes('shift') || embedText.includes('mini-game') || embedText.includes('color') || embedText.includes('what color') || embedText.includes('babysitter') || embedText.includes('great work') || embedText.includes('for your shift'))) return 'work';
|
|
206
204
|
if (embedText.includes('you were given') && embedText.includes('shift')) return 'work';
|
|
@@ -209,7 +207,7 @@ function detectCommand(d) {
|
|
|
209
207
|
// Postmemes
|
|
210
208
|
if (embedText.includes('pick a meme') || embedText.includes('meme posting')) return 'postmemes';
|
|
211
209
|
// Stream
|
|
212
|
-
if (embedText.includes('stream manager') || embedText.includes('go live') || embedText.includes('what game do you want to stream') || embedText.includes('you ran an ad for') ||
|
|
210
|
+
if (embedText.includes('stream manager') || embedText.includes('go live') || embedText.includes('what game do you want to stream') || embedText.includes('you ran an ad for') || embedText.includes('you received') && embedText.includes('from your sponsors') || embedText.includes('### chat') && embedText.includes('hasanbabi')) return 'stream';
|
|
213
211
|
if (embedText.includes('you can\'t interact with your stream') || embedText.includes('stream can last')) return 'stream';
|
|
214
212
|
// Deposit
|
|
215
213
|
if (embedText.includes('deposited') && embedText.includes('bank balance')) return 'deposit';
|
|
@@ -230,7 +228,7 @@ function detectCommand(d) {
|
|
|
230
228
|
// Farm
|
|
231
229
|
if (embedText.includes('farm') && (embedText.includes('harvest') || embedText.includes('plant') || embedText.includes('hoe') || embedText.includes('water'))) return 'farm';
|
|
232
230
|
// Beg
|
|
233
|
-
if (embedText.includes('begging')
|
|
231
|
+
if (embedText.includes('begging')) return 'beg';
|
|
234
232
|
// Daily/weekly quest
|
|
235
233
|
if (embedText.includes('daily quest')) return 'daily';
|
|
236
234
|
// Fish
|
package/lib/structures.js
CHANGED
|
@@ -443,25 +443,6 @@ class MinHeap {
|
|
|
443
443
|
|
|
444
444
|
peek() { return this.heap[0]; }
|
|
445
445
|
|
|
446
|
-
/**
|
|
447
|
-
* Remove a specific node from the heap by reference.
|
|
448
|
-
* Used by CommandScheduler.unschedule() to cancel a pending scheduled command.
|
|
449
|
-
* O(n) — linear scan since we store node references in a Map.
|
|
450
|
-
*/
|
|
451
|
-
remove(node) {
|
|
452
|
-
const idx = this.heap.indexOf(node);
|
|
453
|
-
if (idx === -1) return false;
|
|
454
|
-
const last = this.heap.pop();
|
|
455
|
-
if (idx < this.heap.length) {
|
|
456
|
-
this.heap[idx] = last;
|
|
457
|
-
// Bubble up or sink down depending on where the removed item was
|
|
458
|
-
if (this._bubbleUp(idx) === idx) {
|
|
459
|
-
this._sinkDown(idx);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
446
|
_bubbleUp(i) {
|
|
466
447
|
while (i > 0) {
|
|
467
448
|
const parent = (i - 1) >>> 1;
|
|
@@ -699,6 +680,31 @@ class AsyncBatchQueue {
|
|
|
699
680
|
destroy() { if (this._timer) clearTimeout(this._timer); this.queue.length = 0; }
|
|
700
681
|
}
|
|
701
682
|
|
|
683
|
+
// ═══════════════════════════════════════════════════════════════
|
|
684
|
+
// JitterBackoff – Decorrelated jitter (AWS-style), O(1)
|
|
685
|
+
// Standard exponential backoff causes "thundering herd" when 10K
|
|
686
|
+
// accounts retry simultaneously. Decorrelated jitter spreads them:
|
|
687
|
+
// sleep = min(cap, random_between(base, sleep_prev * 3))
|
|
688
|
+
// This is the recommended strategy from AWS Architecture Blog.
|
|
689
|
+
// ═══════════════════════════════════════════════════════════════
|
|
690
|
+
class JitterBackoff {
|
|
691
|
+
constructor(baseMs = 1000, capMs = 30000) {
|
|
692
|
+
this.baseMs = baseMs;
|
|
693
|
+
this.capMs = capMs;
|
|
694
|
+
this._sleep = baseMs;
|
|
695
|
+
this.attempt = 0;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
next() {
|
|
699
|
+
this._sleep = Math.min(this.capMs, this.baseMs + Math.random() * (this._sleep * 3 - this.baseMs));
|
|
700
|
+
this.attempt++;
|
|
701
|
+
return this._sleep;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
reset() { this._sleep = this.baseMs; this.attempt = 0; }
|
|
705
|
+
get current() { return this._sleep; }
|
|
706
|
+
}
|
|
707
|
+
|
|
702
708
|
module.exports = {
|
|
703
709
|
BloomFilter,
|
|
704
710
|
LRUCache,
|
|
@@ -715,4 +721,5 @@ module.exports = {
|
|
|
715
721
|
ObjectPool,
|
|
716
722
|
TimerWheel,
|
|
717
723
|
AsyncBatchQueue,
|
|
724
|
+
JitterBackoff,
|
|
718
725
|
};
|