dankgrinder 6.46.0 → 7.6.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/farm.js +17 -22
- package/lib/commands/farmVision.js +71 -107
- 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 +303 -282
- package/lib/rawLogger.js +206 -102
- package/lib/structures.js +26 -19
- package/package.json +1 -1
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
|
+
}
|
|
2589
|
+
|
|
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;
|
|
2588
2597
|
}
|
|
2589
2598
|
|
|
2590
|
-
|
|
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}`);
|
|
@@ -3217,21 +3151,39 @@ async function start(apiKey, apiUrl) {
|
|
|
3217
3151
|
console.log(` ${checks.join(' ')}`);
|
|
3218
3152
|
console.log('');
|
|
3219
3153
|
|
|
3220
|
-
// ── Phase 1: Login
|
|
3154
|
+
// ── Phase 1: Login with per-account inline rendering ─────────────────────────
|
|
3221
3155
|
const startupTw = process.stdout.columns || 90;
|
|
3222
|
-
const colNum = 4;
|
|
3223
|
-
const colSts = 3;
|
|
3156
|
+
const colNum = 4; // " #"
|
|
3157
|
+
const colSts = 3; // "ST"
|
|
3224
3158
|
const colName = Math.min(24, Math.max(12, Math.floor(startupTw * 0.25)));
|
|
3225
3159
|
const colGuild = Math.min(18, Math.max(8, Math.floor(startupTw * 0.2)));
|
|
3226
3160
|
const colCmds = 8;
|
|
3227
3161
|
const loginVis = colNum + colSts + colName + colGuild + colCmds + 10;
|
|
3228
3162
|
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3163
|
+
const loginStates = accounts.map((acc, i) => ({
|
|
3164
|
+
name: acc.label || acc.id || '?',
|
|
3165
|
+
done: false,
|
|
3166
|
+
failed: false,
|
|
3167
|
+
worker: null,
|
|
3168
|
+
}));
|
|
3169
|
+
|
|
3170
|
+
let loginLines = [];
|
|
3171
|
+
loginLines.push(` ${'─'.repeat(loginVis)}`);
|
|
3172
|
+
for (let i = 0; i < loginStates.length; i++) {
|
|
3173
|
+
const s = loginStates[i];
|
|
3174
|
+
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
3175
|
+
const name = s.name.substring(0, colName).padEnd(colName);
|
|
3176
|
+
const guild = c.dim + '···'.padEnd(colGuild) + c.reset;
|
|
3177
|
+
const cmds = c.dim + '···'.padEnd(colCmds) + c.reset;
|
|
3178
|
+
loginLines.push(` ${num} ${c.dim}··${c.reset} ${name} ${guild} ${cmds}`);
|
|
3179
|
+
}
|
|
3180
|
+
loginLines.push(` ${'─'.repeat(loginVis)}`);
|
|
3181
|
+
for (const l of loginLines) console.log(l);
|
|
3182
|
+
|
|
3183
|
+
// Dynamically capture the starting row of the login table via DSR
|
|
3184
|
+
let loginBaseRow = 1;
|
|
3185
|
+
const captureLoginRow = () => new Promise(resolve => {
|
|
3186
|
+
process.stdout.write(MARKER);
|
|
3235
3187
|
const chunks = [];
|
|
3236
3188
|
const handler = (chunk) => {
|
|
3237
3189
|
chunks.push(chunk);
|
|
@@ -3239,52 +3191,51 @@ async function start(apiKey, apiUrl) {
|
|
|
3239
3191
|
const m = raw.match(/\x1b\[(\d+);\d+R/);
|
|
3240
3192
|
if (m) {
|
|
3241
3193
|
process.stdin.removeListener('data', handler);
|
|
3242
|
-
|
|
3194
|
+
loginBaseRow = parseInt(m[1], 10) + 1;
|
|
3243
3195
|
resolve();
|
|
3244
3196
|
}
|
|
3245
3197
|
};
|
|
3246
3198
|
process.stdin.on('data', handler);
|
|
3247
3199
|
setTimeout(resolve, 50);
|
|
3248
3200
|
});
|
|
3249
|
-
await
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
const
|
|
3253
|
-
const dataStartRow = tableTopRow + 1; // first account row
|
|
3254
|
-
const borderBotRow = tableTopRow + accounts.length + 1; // bottom border
|
|
3255
|
-
const bottomRow = borderBotRow + 1; // cursor final position after table
|
|
3256
|
-
|
|
3257
|
-
// Print initial table using explicit row positioning
|
|
3258
|
-
process.stdout.write(`\x1b[${borderTopRow};1H ${'─'.repeat(loginVis)}`);
|
|
3259
|
-
for (let i = 0; i < accounts.length; i++) {
|
|
3260
|
-
const row = dataStartRow + i;
|
|
3261
|
-
const name = (accounts[i].label || accounts[i].id || '?').substring(0, colName).padEnd(colName);
|
|
3262
|
-
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
3263
|
-
process.stdout.write(`\x1b[${row};1H ${num} ${c.dim}··${c.reset} ${name} ${c.dim}${'···'.padEnd(colGuild)}${c.reset} ${c.dim}${'···'.padEnd(colCmds)}${c.reset}\x1b[K`);
|
|
3264
|
-
}
|
|
3265
|
-
process.stdout.write(`\x1b[${borderBotRow};1H ${'─'.repeat(loginVis)}\x1b[K`);
|
|
3201
|
+
await captureLoginRow();
|
|
3202
|
+
|
|
3203
|
+
let loginPending = new Array(accounts.length).fill(true);
|
|
3204
|
+
const moveToRow = (row) => process.stdout.write(`\x1b[${row};1H`);
|
|
3266
3205
|
|
|
3267
|
-
// Spinner: updates rows inline using absolute row numbers
|
|
3268
3206
|
const drawLoginSpinners = () => {
|
|
3269
|
-
for (
|
|
3207
|
+
for (let i = 0; i < loginPending.length; i++) {
|
|
3208
|
+
if (!loginPending[i]) continue;
|
|
3270
3209
|
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3271
|
-
const name = (accounts[i].label || accounts[i].id || '?').substring(0, colName).padEnd(colName);
|
|
3272
3210
|
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
3273
|
-
|
|
3274
|
-
|
|
3211
|
+
const name = loginStates[i].name.substring(0, colName).padEnd(colName);
|
|
3212
|
+
const guild = c.dim + 'logging in...'.substring(0, colGuild) + c.reset;
|
|
3213
|
+
const cmds = c.dim + '···'.padEnd(colCmds) + c.reset;
|
|
3214
|
+
const row = loginBaseRow + 1 + i; // +1 skips the top border line
|
|
3215
|
+
moveToRow(row);
|
|
3216
|
+
process.stdout.write(` ${num} ${rgb(139, 92, 246)}${spin}${c.reset} ${name} ${guild} ${cmds}\x1b[K`);
|
|
3217
|
+
}
|
|
3218
|
+
// Move cursor back to bottom to avoid overwriting the bottom border
|
|
3219
|
+
const lastRow = loginBaseRow + 1 + accounts.length + 1;
|
|
3220
|
+
moveToRow(lastRow);
|
|
3275
3221
|
};
|
|
3276
|
-
const
|
|
3222
|
+
const loginSpinnerInterval = setInterval(drawLoginSpinners, 80);
|
|
3223
|
+
|
|
3224
|
+
const finalizeLoginLine = (idx, worker) => {
|
|
3225
|
+
if (!loginPending[idx]) return;
|
|
3226
|
+
loginPending[idx] = false;
|
|
3227
|
+
const s = loginStates[idx];
|
|
3228
|
+
s.done = true;
|
|
3229
|
+
s.worker = worker;
|
|
3277
3230
|
|
|
3278
|
-
const finalizeLoginRow = (idx, worker) => {
|
|
3279
|
-
if (!pendingSet.has(idx)) return;
|
|
3280
|
-
pendingSet.delete(idx);
|
|
3281
3231
|
const num = `${c.dim}${(idx + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
3282
|
-
const name = (worker.username ||
|
|
3232
|
+
const name = (worker.username || s.name || '?').substring(0, colName).padEnd(colName);
|
|
3283
3233
|
let sts, guild, cmds;
|
|
3284
3234
|
if (worker._tokenInvalid) {
|
|
3285
3235
|
sts = `${rgb(239, 68, 68)}✗${c.reset}`;
|
|
3286
3236
|
guild = 'INVALID'.padEnd(colGuild);
|
|
3287
3237
|
cmds = '···'.padEnd(colCmds);
|
|
3238
|
+
s.failed = true;
|
|
3288
3239
|
} else if (worker.channel) {
|
|
3289
3240
|
sts = `${rgb(52, 211, 153)}✓${c.reset}`;
|
|
3290
3241
|
const gn = (worker.channel.guild?.name || worker.channel.guild?.id || 'DM').substring(0, colGuild);
|
|
@@ -3295,7 +3246,9 @@ async function start(apiKey, apiUrl) {
|
|
|
3295
3246
|
guild = 'timeout'.padEnd(colGuild);
|
|
3296
3247
|
cmds = '···'.padEnd(colCmds);
|
|
3297
3248
|
}
|
|
3298
|
-
|
|
3249
|
+
const row = loginBaseRow + 1 + idx; // +1 skips the top border line
|
|
3250
|
+
moveToRow(row);
|
|
3251
|
+
process.stdout.write(` ${num} ${sts} ${name} ${c.dim}${guild}${c.reset} ${c.dim}${cmds}${c.reset}\x1b[K`);
|
|
3299
3252
|
};
|
|
3300
3253
|
|
|
3301
3254
|
const parsedGapMin = Number.parseInt(String(process.env.LOGIN_GAP_MIN_MS || '50'), 10);
|
|
@@ -3313,34 +3266,58 @@ async function start(apiKey, apiUrl) {
|
|
|
3313
3266
|
const worker = new AccountWorker(acc, i + idx);
|
|
3314
3267
|
workers.push(worker);
|
|
3315
3268
|
workerMap.set(acc.id, worker);
|
|
3269
|
+
loginStates[i + idx].worker = worker;
|
|
3316
3270
|
await worker.start();
|
|
3317
|
-
|
|
3271
|
+
finalizeLoginLine(i + idx, worker);
|
|
3318
3272
|
}));
|
|
3319
3273
|
if (i + BATCH_SIZE < accounts.length) await new Promise(r => setTimeout(r, randomLoginGap()));
|
|
3320
3274
|
hintGC();
|
|
3321
3275
|
}
|
|
3322
3276
|
|
|
3323
|
-
clearInterval(
|
|
3277
|
+
clearInterval(loginSpinnerInterval);
|
|
3278
|
+
const loginDone = workers.filter(w => !w._tokenInvalid && w.channel).length;
|
|
3324
3279
|
const invalidWorkers = workers.filter(w => w._tokenInvalid);
|
|
3325
3280
|
const timedOutWorkers = workers.filter(w => !w.channel && !w._tokenInvalid);
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
// Clear bottom border row, move to new line, print login complete
|
|
3329
|
-
process.stdout.write(`\x1b[${borderBotRow};1H\x1b[2K\n ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}Login complete${c.reset} ${rgb(52, 211, 153)}${loginDone}${c.reset}${c.dim}/${c.reset}${c.white}${accounts.length}${c.reset} ${c.dim}accounts connected${c.reset}\n`);
|
|
3281
|
+
console.log(`\r\x1b[2K ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}Login complete${c.reset} ${rgb(52, 211, 153)}${loginDone}${c.reset}${c.dim}/${c.reset}${c.white}${accounts.length}${c.reset} ${c.dim}accounts connected${c.reset}`);
|
|
3282
|
+
console.log('');
|
|
3330
3283
|
if (invalidWorkers.length > 0) {
|
|
3331
|
-
|
|
3284
|
+
log('warn', `${rgb(239, 68, 68)}${invalidWorkers.length} account(s) have INVALID tokens:${c.reset}`);
|
|
3285
|
+
for (const w of invalidWorkers) log('error', ` ✗ ${w.account.label || w.account.id} — token is invalid or expired`);
|
|
3286
|
+
console.log('');
|
|
3332
3287
|
}
|
|
3333
3288
|
if (timedOutWorkers.length > 0) log('warn', `${timedOutWorkers.length} account(s) timed out during login (will retry in background)`);
|
|
3334
|
-
console.log('');
|
|
3335
3289
|
|
|
3336
|
-
|
|
3290
|
+
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
3337
3291
|
|
|
3292
|
+
// ── Phase 2: Inventory check — spinner for pending count, results inline ─────────
|
|
3338
3293
|
const iColNum = 4;
|
|
3339
3294
|
const iColName = Math.min(22, Math.max(12, Math.floor(startupTw * 0.22)));
|
|
3340
3295
|
const iColItems = 8;
|
|
3341
3296
|
const iColVal = 16;
|
|
3342
3297
|
const invVis = 7 + iColNum + iColName + iColItems + iColVal + 12;
|
|
3343
3298
|
|
|
3299
|
+
// Print a unique marker, query its position, then overwrite it with the table
|
|
3300
|
+
process.stdout.write(MARKER);
|
|
3301
|
+
let invBaseRow = 1;
|
|
3302
|
+
const captureRow = () => new Promise(resolve => {
|
|
3303
|
+
const chunks = [];
|
|
3304
|
+
const handler = (chunk) => {
|
|
3305
|
+
chunks.push(chunk);
|
|
3306
|
+
const raw = chunks.join('');
|
|
3307
|
+
const m = raw.match(/\x1b\[(\d+);\d+R/);
|
|
3308
|
+
if (m) {
|
|
3309
|
+
process.stdin.removeListener('data', handler);
|
|
3310
|
+
invBaseRow = parseInt(m[1], 10) + 1; // +1: first account row is after marker
|
|
3311
|
+
resolve();
|
|
3312
|
+
}
|
|
3313
|
+
};
|
|
3314
|
+
process.stdin.on('data', handler);
|
|
3315
|
+
setTimeout(resolve, 50);
|
|
3316
|
+
});
|
|
3317
|
+
await captureRow();
|
|
3318
|
+
|
|
3319
|
+
// Now print the inventory table starting at invBaseRow
|
|
3320
|
+
const invMoveToRow = (row) => process.stdout.write(`\x1b[${row};1H`);
|
|
3344
3321
|
console.log(` ${'─'.repeat(invVis)}`);
|
|
3345
3322
|
for (let i = 0; i < activeWorkers.length; i++) {
|
|
3346
3323
|
const w = activeWorkers[i];
|
|
@@ -3350,31 +3327,40 @@ async function start(apiKey, apiUrl) {
|
|
|
3350
3327
|
}
|
|
3351
3328
|
console.log(` ${'─'.repeat(invVis)}`);
|
|
3352
3329
|
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3330
|
+
let invDone = 0, invFailed = 0, invPending = activeWorkers.length;
|
|
3331
|
+
const drawInvProgress = () => {
|
|
3332
|
+
if (invPending === 0) return;
|
|
3333
|
+
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3334
|
+
const pct = activeWorkers.length > 0 ? ((activeWorkers.length - invPending) / activeWorkers.length) : 0;
|
|
3335
|
+
const barW = Math.min(20, startupTw - 40);
|
|
3336
|
+
const filled = Math.round(pct * barW);
|
|
3337
|
+
const bar = rgb(34, 211, 238) + '█'.repeat(filled) + rgb(50, 50, 70) + '░'.repeat(barW - filled) + c.reset;
|
|
3338
|
+
const pctStr = `${Math.round(pct * 100)}%`;
|
|
3339
|
+
invMoveToRow(invBaseRow);
|
|
3340
|
+
process.stdout.write(` ${rgb(34, 211, 238)}${spin}${c.reset} ${c.dim}Inventory...${c.reset} ${bar} ${c.bold}${rgb(52, 211, 153)}${activeWorkers.length - invPending}${c.reset}${c.dim}/${c.reset}${c.white}${activeWorkers.length}${c.reset} ${c.dim}${pctStr}${c.reset} \x1b[K`);
|
|
3341
|
+
};
|
|
3342
|
+
const invSpinnerInterval = setInterval(drawInvProgress, 80);
|
|
3360
3343
|
|
|
3361
|
-
|
|
3362
|
-
// Re-print table with results
|
|
3363
|
-
console.log(` ${'─'.repeat(invVis)}`);
|
|
3364
|
-
for (let i = 0; i < activeWorkers.length; i++) {
|
|
3365
|
-
const invRes = invResults[i] || { ok: false };
|
|
3366
|
-
const w = activeWorkers[i];
|
|
3344
|
+
await Promise.all(activeWorkers.map(async (w, i) => {
|
|
3367
3345
|
const num = `${c.dim}${(i + 1).toString().padStart(iColNum - 1)}${c.reset}`;
|
|
3368
3346
|
const name = (w.username || w.account.label || '?').substring(0, iColName).padEnd(iColName);
|
|
3347
|
+
let invRes;
|
|
3348
|
+
try { invRes = await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3, silent: true }); }
|
|
3349
|
+
catch { invRes = { ok: false }; }
|
|
3350
|
+
invPending--;
|
|
3369
3351
|
const items = invRes?.ok ? (invRes.result?.items?.length || 0) : 0;
|
|
3370
3352
|
const val = invRes?.ok ? (invRes.result?.totalValue || 0) : 0;
|
|
3371
3353
|
const sts = invRes?.ok ? `${rgb(52, 211, 153)}✓${c.reset}` : `${rgb(239, 68, 68)}✗${c.reset}`;
|
|
3372
3354
|
const itemStr = `${items}`.padEnd(iColItems);
|
|
3373
3355
|
const valStr = invRes?.ok ? `${c.green}⏣${val.toLocaleString()}${c.reset}` : `${c.dim}···${c.reset}`;
|
|
3374
|
-
|
|
3356
|
+
const row = invBaseRow + 1 + i;
|
|
3357
|
+
invMoveToRow(row);
|
|
3358
|
+
process.stdout.write(` ${num} ${sts} ${name} ${itemStr} ${valStr.padEnd(iColVal + 5)}\x1b[K`);
|
|
3375
3359
|
if (invRes?.ok) invDone++; else invFailed++;
|
|
3376
|
-
}
|
|
3377
|
-
|
|
3360
|
+
}));
|
|
3361
|
+
|
|
3362
|
+
clearInterval(invSpinnerInterval);
|
|
3363
|
+
process.stdout.write(`\r\x1b[2K`);
|
|
3378
3364
|
|
|
3379
3365
|
if (invFailed > 0) {
|
|
3380
3366
|
console.log(` ${rgb(239, 68, 68)}✗${c.reset} ${c.bold}Inventory incomplete${c.reset} ${rgb(52, 211, 153)}${invDone}${c.reset}${c.dim}/${c.reset}${c.white}${activeWorkers.length}${c.reset} ${c.dim}done, ${rgb(239, 68, 68)}${invFailed} failed${c.reset}`);
|
|
@@ -3384,7 +3370,7 @@ async function start(apiKey, apiUrl) {
|
|
|
3384
3370
|
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}Inventory complete${c.reset} ${rgb(52, 211, 153)}${invDone}/${activeWorkers.length}${c.reset} ${c.dim}all clear${c.reset}`);
|
|
3385
3371
|
console.log('');
|
|
3386
3372
|
|
|
3387
|
-
// ── Phase 2.5: Balance check —
|
|
3373
|
+
// ── Phase 2.5: Balance check — inline table, single spinner for progress ─────────
|
|
3388
3374
|
const bColNum = 4;
|
|
3389
3375
|
const bColName = Math.min(22, Math.max(12, Math.floor(startupTw * 0.22)));
|
|
3390
3376
|
const bColWallet = 12;
|
|
@@ -3393,6 +3379,27 @@ async function start(apiKey, apiUrl) {
|
|
|
3393
3379
|
const bColLs = 4;
|
|
3394
3380
|
const balVis = 7 + bColNum + bColName + bColWallet + bColBank + bColTotal + bColLs + 14;
|
|
3395
3381
|
|
|
3382
|
+
// Capture starting row for balance phase
|
|
3383
|
+
process.stdout.write(MARKER);
|
|
3384
|
+
let balBaseRow = 1;
|
|
3385
|
+
const balCaptureRow = () => new Promise(resolve => {
|
|
3386
|
+
const chunks = [];
|
|
3387
|
+
const handler = (chunk) => {
|
|
3388
|
+
chunks.push(chunk);
|
|
3389
|
+
const raw = chunks.join('');
|
|
3390
|
+
const m = raw.match(/\x1b\[(\d+);\d+R/);
|
|
3391
|
+
if (m) {
|
|
3392
|
+
process.stdin.removeListener('data', handler);
|
|
3393
|
+
balBaseRow = parseInt(m[1], 10) + 1;
|
|
3394
|
+
resolve();
|
|
3395
|
+
}
|
|
3396
|
+
};
|
|
3397
|
+
process.stdin.on('data', handler);
|
|
3398
|
+
setTimeout(resolve, 50);
|
|
3399
|
+
});
|
|
3400
|
+
await balCaptureRow();
|
|
3401
|
+
|
|
3402
|
+
const balMoveToRow = (row) => process.stdout.write(`\x1b[${row};1H`);
|
|
3396
3403
|
console.log(` ${'─'.repeat(balVis)}`);
|
|
3397
3404
|
for (let i = 0; i < activeWorkers.length; i++) {
|
|
3398
3405
|
const w = activeWorkers[i];
|
|
@@ -3402,14 +3409,22 @@ async function start(apiKey, apiUrl) {
|
|
|
3402
3409
|
}
|
|
3403
3410
|
console.log(` ${'─'.repeat(balVis)}`);
|
|
3404
3411
|
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3412
|
+
let balDone = 0, balPending = activeWorkers.length;
|
|
3413
|
+
const drawBalProgress = () => {
|
|
3414
|
+
if (balPending === 0) return;
|
|
3415
|
+
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3416
|
+
const pct = activeWorkers.length > 0 ? ((activeWorkers.length - balPending) / activeWorkers.length) : 0;
|
|
3417
|
+
const barW = Math.min(20, startupTw - 40);
|
|
3418
|
+
const filled = Math.round(pct * barW);
|
|
3419
|
+
const bar = rgb(251, 191, 36) + '█'.repeat(filled) + rgb(50, 50, 70) + '░'.repeat(barW - filled) + c.reset;
|
|
3420
|
+
balMoveToRow(balBaseRow);
|
|
3421
|
+
process.stdout.write(` ${rgb(251, 191, 36)}${spin}${c.reset} ${c.dim}Balance...${c.reset} ${bar} ${c.bold}${rgb(52, 211, 153)}${activeWorkers.length - balPending}${c.reset}${c.dim}/${c.reset}${c.white}${activeWorkers.length}${c.reset} \x1b[K`);
|
|
3422
|
+
};
|
|
3423
|
+
const balSpinnerInterval = setInterval(drawBalProgress, 80);
|
|
3408
3424
|
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
const w = activeWorkers[i];
|
|
3425
|
+
await Promise.all(activeWorkers.map(async (w, i) => {
|
|
3426
|
+
try { await w.checkBalance(true); } catch {}
|
|
3427
|
+
balPending--;
|
|
3413
3428
|
const num = `${c.dim}${(i + 1).toString().padStart(bColNum - 1)}${c.reset}`;
|
|
3414
3429
|
const name = (w.username || w.account.label || '?').substring(0, bColName).padEnd(bColName);
|
|
3415
3430
|
const wallet = w.stats?.balance || 0;
|
|
@@ -3419,9 +3434,14 @@ async function start(apiKey, apiUrl) {
|
|
|
3419
3434
|
const walletStr = `${c.green}⏣${wallet.toLocaleString()}${c.reset}`;
|
|
3420
3435
|
const bankStr = `${c.cyan}⏣${bank.toLocaleString()}${c.reset}`;
|
|
3421
3436
|
const totalStr = `${c.bold}⏣${(wallet + bank).toLocaleString()}${c.reset}`;
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3437
|
+
const row = balBaseRow + 1 + i;
|
|
3438
|
+
balMoveToRow(row);
|
|
3439
|
+
process.stdout.write(` ${num} ${rgb(52, 211, 153)}✓${c.reset} ${name} ${walletStr.padEnd(bColWallet + 4)} ${bankStr.padEnd(bColBank + 4)} ${totalStr.padEnd(bColTotal + 3)} ${lsColor}♥${ls}${c.reset}\x1b[K`);
|
|
3440
|
+
balDone++;
|
|
3441
|
+
}));
|
|
3442
|
+
|
|
3443
|
+
clearInterval(balSpinnerInterval);
|
|
3444
|
+
process.stdout.write(`\r\x1b[2K`);
|
|
3425
3445
|
|
|
3426
3446
|
let totalWallet = 0, totalBank = 0, noLifesaverAccounts = [];
|
|
3427
3447
|
for (const w of activeWorkers) {
|
|
@@ -3435,6 +3455,7 @@ async function start(apiKey, apiUrl) {
|
|
|
3435
3455
|
}
|
|
3436
3456
|
console.log('');
|
|
3437
3457
|
|
|
3458
|
+
|
|
3438
3459
|
// Phase 2.75: Check DM history for deaths/level-ups (sequential, fast)
|
|
3439
3460
|
console.log(` ${rgb(139, 92, 246)}${BRAILLE_SPIN[0]}${c.reset} ${c.dim}Checking DM history...${c.reset}`);
|
|
3440
3461
|
let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [];
|