dankgrinder 7.72.0 → 7.74.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/grinder.js +48 -316
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -94,10 +94,20 @@ const c = {
|
|
|
94
94
|
};
|
|
95
95
|
|
|
96
96
|
const WORKER_COLORS = [c.cyan, c.magenta, c.yellow, c.green, c.blue, c.red];
|
|
97
|
-
// Unique marker written to stdout so we can query cursor position via DSR response
|
|
98
|
-
const MARKER = '\x1b[6n\x1b[@@MARKER@@';
|
|
99
97
|
const DANK_MEMER_ID = '270904126974590976';
|
|
100
98
|
|
|
99
|
+
|
|
100
|
+
// Simple uptime formatter
|
|
101
|
+
function formatUptime() {
|
|
102
|
+
const s = Math.floor((Date.now() - startTime) / 1000);
|
|
103
|
+
if (s < 60) return `${s}s`;
|
|
104
|
+
const m = Math.floor(s / 60);
|
|
105
|
+
if (m < 60) return `${m}m ${s % 60}s`;
|
|
106
|
+
const h = Math.floor(m / 60);
|
|
107
|
+
if (h < 24) return `${h}h ${m % 60}m`;
|
|
108
|
+
const d = Math.floor(h / 24);
|
|
109
|
+
return `${d}d ${h % 24}h`;
|
|
110
|
+
}
|
|
101
111
|
// ── Safe options for search/crime ──────────────────────────
|
|
102
112
|
// Object.freeze → V8 marks these as immutable, enabling inline caching
|
|
103
113
|
// and preventing accidental mutation across 10K worker instances.
|
|
@@ -272,17 +282,6 @@ function progressBar(value, max, width, filledColor, emptyColor) {
|
|
|
272
282
|
return rgb(fc[0], fc[1], fc[2]) + '█'.repeat(filled) + rgb(ec[0], ec[1], ec[2]) + '░'.repeat(empty) + c.reset;
|
|
273
283
|
}
|
|
274
284
|
|
|
275
|
-
// ── Animated braille spinner frames ──────────────────────────
|
|
276
|
-
const BRAILLE_SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
277
|
-
const BLOCK_SPIN = ['▉', '▊', '▋', '▌', '▍', '▎', '▏', '▎', '▍', '▌', '▋', '▊'];
|
|
278
|
-
const PULSE_CHARS = ['○', '◎', '●', '◉', '●', '◎'];
|
|
279
|
-
function getSpinner(type = 'braille') {
|
|
280
|
-
const now = Math.floor(Date.now() / 80);
|
|
281
|
-
if (type === 'block') return BLOCK_SPIN[now % BLOCK_SPIN.length];
|
|
282
|
-
if (type === 'pulse') return PULSE_CHARS[now % PULSE_CHARS.length];
|
|
283
|
-
return BRAILLE_SPIN[now % BRAILLE_SPIN.length];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
285
|
// ── Box drawing helpers ──────────────────────────────────────
|
|
287
286
|
const BOX = {
|
|
288
287
|
tl: '╭', tr: '╮', bl: '╰', br: '╯',
|
|
@@ -1389,9 +1388,7 @@ class AccountWorker {
|
|
|
1389
1388
|
|
|
1390
1389
|
// Update Redis with findings
|
|
1391
1390
|
if (redis) {
|
|
1392
|
-
|
|
1393
|
-
await redis.set(`dkg:level:${this.account.id}`, String(currentLevel), 'EX', 2592000);
|
|
1394
|
-
this._level = currentLevel;
|
|
1391
|
+
try {
|
|
1395
1392
|
if (currentLevel > 0) {
|
|
1396
1393
|
await redis.set(`dkg:level:${this.account.id}`, String(currentLevel), 'EX', 2592000);
|
|
1397
1394
|
this._level = currentLevel;
|
|
@@ -1404,17 +1401,19 @@ class AccountWorker {
|
|
|
1404
1401
|
await redis.set(`raw:alert:no-lifesaver:${this.channel?.id}`, '1', 'EX', 86400);
|
|
1405
1402
|
}
|
|
1406
1403
|
}
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1404
|
+
} catch { /* Redis errors non-fatal */ }
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
return { deaths, levelUps, currentLevel, lifesavers: lastLifesaverCount, dmChannelId: dm.id };
|
|
1408
|
+
} catch (e) {
|
|
1409
|
+
lastError = e;
|
|
1410
|
+
if (attempt < maxRetries - 1) {
|
|
1411
|
+
await new Promise(r => setTimeout(r, delays[attempt]));
|
|
1414
1412
|
}
|
|
1415
1413
|
}
|
|
1416
|
-
return { deaths: 0, levelUps: 0, currentLevel: 0, lifesavers: -1 };
|
|
1417
1414
|
}
|
|
1415
|
+
return { deaths: 0, levelUps: 0, currentLevel: 0, lifesavers: -1 };
|
|
1416
|
+
}
|
|
1418
1417
|
|
|
1419
1418
|
// ── Run Single Command ──────────────────────────────────────
|
|
1420
1419
|
// Each modular command handler sends the command, waits for response,
|
|
@@ -2749,10 +2748,6 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2749
2748
|
REDIS_URL = process.env.REDIS_URL || '';
|
|
2750
2749
|
WEBHOOK_URL = process.env.WEBHOOK_URL || '';
|
|
2751
2750
|
|
|
2752
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
2753
|
-
const tw = Math.min(process.stdout.columns || 80, 78);
|
|
2754
|
-
const bar = c.dim + '─'.repeat(tw) + c.reset;
|
|
2755
|
-
|
|
2756
2751
|
// Detect zlib-sync availability
|
|
2757
2752
|
let hasZlib = false;
|
|
2758
2753
|
try { require('zlib-sync'); hasZlib = true; } catch {}
|
|
@@ -2880,110 +2875,8 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2880
2875
|
console.log(` ${checks.join(' ')}`);
|
|
2881
2876
|
console.log('');
|
|
2882
2877
|
|
|
2883
|
-
// ── Phase 1: Login
|
|
2884
|
-
|
|
2885
|
-
const colNum = 4; // " #"
|
|
2886
|
-
const colSts = 3; // "ST"
|
|
2887
|
-
const colName = Math.min(24, Math.max(12, Math.floor(startupTw * 0.25)));
|
|
2888
|
-
const colGuild = Math.min(18, Math.max(8, Math.floor(startupTw * 0.2)));
|
|
2889
|
-
const colCmds = 8;
|
|
2890
|
-
const loginVis = colNum + colSts + colName + colGuild + colCmds + 10;
|
|
2891
|
-
|
|
2892
|
-
const loginStates = accounts.map((acc, i) => ({
|
|
2893
|
-
name: acc.label || acc.id || '?',
|
|
2894
|
-
done: false,
|
|
2895
|
-
failed: false,
|
|
2896
|
-
worker: null,
|
|
2897
|
-
}));
|
|
2898
|
-
|
|
2899
|
-
let loginLines = [];
|
|
2900
|
-
loginLines.push(` ${'─'.repeat(loginVis)}`);
|
|
2901
|
-
for (let i = 0; i < loginStates.length; i++) {
|
|
2902
|
-
const s = loginStates[i];
|
|
2903
|
-
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
2904
|
-
const name = s.name.substring(0, colName).padEnd(colName);
|
|
2905
|
-
const guild = c.dim + '···'.padEnd(colGuild) + c.reset;
|
|
2906
|
-
const cmds = c.dim + '···'.padEnd(colCmds) + c.reset;
|
|
2907
|
-
loginLines.push(` ${num} ${c.dim}··${c.reset} ${name} ${guild} ${cmds}`);
|
|
2908
|
-
}
|
|
2909
|
-
loginLines.push(` ${'─'.repeat(loginVis)}`);
|
|
2910
|
-
for (const l of loginLines) console.log(l);
|
|
2911
|
-
|
|
2912
|
-
// Dynamically capture the starting row of the login table via DSR.
|
|
2913
|
-
// Write MARKER to stderr (not stdout) to avoid PTY cooked-mode echoing
|
|
2914
|
-
// of the visible "@MARKER@@" text portion, which was causing the DSR
|
|
2915
|
-
// response to be swallowed or delayed.
|
|
2916
|
-
let loginBaseRow = 1;
|
|
2917
|
-
const captureLoginRow = () => new Promise(resolve => {
|
|
2918
|
-
const chunks = [];
|
|
2919
|
-
const handler = (chunk) => {
|
|
2920
|
-
chunks.push(chunk);
|
|
2921
|
-
const raw = chunks.join('');
|
|
2922
|
-
const m = raw.match(/\x1b\[(\d+);\d+R/);
|
|
2923
|
-
if (m) {
|
|
2924
|
-
process.stdin.removeListener('data', handler);
|
|
2925
|
-
loginBaseRow = parseInt(m[1], 10) + 1;
|
|
2926
|
-
resolve();
|
|
2927
|
-
}
|
|
2928
|
-
};
|
|
2929
|
-
process.stdin.on('data', handler);
|
|
2930
|
-
// Write to stderr so PTY doesn't echo the visible MARKER text to stdout
|
|
2931
|
-
process.stderr.write(MARKER);
|
|
2932
|
-
setTimeout(resolve, 50);
|
|
2933
|
-
});
|
|
2934
|
-
await captureLoginRow();
|
|
2935
|
-
|
|
2936
|
-
let loginPending = new Array(accounts.length).fill(true);
|
|
2937
|
-
const moveToRow = (row) => process.stdout.write(`\x1b[${row};1H`);
|
|
2938
|
-
|
|
2939
|
-
const drawLoginSpinners = () => {
|
|
2940
|
-
for (let i = 0; i < loginPending.length; i++) {
|
|
2941
|
-
if (!loginPending[i]) continue;
|
|
2942
|
-
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
2943
|
-
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
2944
|
-
const name = loginStates[i].name.substring(0, colName).padEnd(colName);
|
|
2945
|
-
const guild = c.dim + 'logging in...'.substring(0, colGuild) + c.reset;
|
|
2946
|
-
const cmds = c.dim + '···'.padEnd(colCmds) + c.reset;
|
|
2947
|
-
const row = loginBaseRow + 1 + i; // +1 skips the top border line
|
|
2948
|
-
moveToRow(row);
|
|
2949
|
-
process.stdout.write(` ${num} ${rgb(139, 92, 246)}${spin}${c.reset} ${name} ${guild} ${cmds}\x1b[K`);
|
|
2950
|
-
}
|
|
2951
|
-
// Move cursor back to bottom to avoid overwriting the bottom border
|
|
2952
|
-
const lastRow = loginBaseRow + 1 + accounts.length + 1;
|
|
2953
|
-
moveToRow(lastRow);
|
|
2954
|
-
};
|
|
2955
|
-
const loginSpinnerInterval = setInterval(drawLoginSpinners, 80);
|
|
2956
|
-
|
|
2957
|
-
const finalizeLoginLine = (idx, worker) => {
|
|
2958
|
-
if (!loginPending[idx]) return;
|
|
2959
|
-
loginPending[idx] = false;
|
|
2960
|
-
const s = loginStates[idx];
|
|
2961
|
-
s.done = true;
|
|
2962
|
-
s.worker = worker;
|
|
2963
|
-
|
|
2964
|
-
const num = `${c.dim}${(idx + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
2965
|
-
const name = (worker.username || s.name || '?').substring(0, colName).padEnd(colName);
|
|
2966
|
-
let sts, guild, cmds;
|
|
2967
|
-
if (worker._tokenInvalid) {
|
|
2968
|
-
sts = `${rgb(239, 68, 68)}✗${c.reset}`;
|
|
2969
|
-
guild = 'INVALID'.padEnd(colGuild);
|
|
2970
|
-
cmds = '···'.padEnd(colCmds);
|
|
2971
|
-
s.failed = true;
|
|
2972
|
-
} else if (worker.channel) {
|
|
2973
|
-
sts = `${rgb(52, 211, 153)}✓${c.reset}`;
|
|
2974
|
-
const gn = (worker.channel.guild?.name || worker.channel.guild?.id || 'DM').substring(0, colGuild);
|
|
2975
|
-
guild = gn.padEnd(colGuild);
|
|
2976
|
-
cmds = `${worker.stats?.commands || 0}`.padEnd(colCmds);
|
|
2977
|
-
} else {
|
|
2978
|
-
sts = `${rgb(251, 146, 60)}⏳${c.reset}`;
|
|
2979
|
-
guild = 'timeout'.padEnd(colGuild);
|
|
2980
|
-
cmds = '···'.padEnd(colCmds);
|
|
2981
|
-
}
|
|
2982
|
-
const row = loginBaseRow + 1 + idx; // +1 skips the top border line
|
|
2983
|
-
moveToRow(row);
|
|
2984
|
-
process.stdout.write(` ${num} ${sts} ${name} ${c.dim}${guild}${c.reset} ${c.dim}${cmds}${c.reset}\x1b[K`);
|
|
2985
|
-
};
|
|
2986
|
-
|
|
2878
|
+
// ── Phase 1: Login accounts ─────────────────────────────────────────
|
|
2879
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} Logging in ${accounts.length} account(s)...`);
|
|
2987
2880
|
const parsedGapMin = Number.parseInt(String(process.env.LOGIN_GAP_MIN_MS || '50'), 10);
|
|
2988
2881
|
const parsedGapMax = Number.parseInt(String(process.env.LOGIN_GAP_MAX_MS || '150'), 10);
|
|
2989
2882
|
const LOGIN_GAP_MIN_MS = Number.isFinite(parsedGapMin) && parsedGapMin >= 0 ? parsedGapMin : 50;
|
|
@@ -2999,203 +2892,58 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2999
2892
|
const worker = new AccountWorker(acc, i + idx);
|
|
3000
2893
|
workers.push(worker);
|
|
3001
2894
|
workerMap.set(acc.id, worker);
|
|
3002
|
-
loginStates[i + idx].worker = worker;
|
|
3003
2895
|
await worker.start();
|
|
3004
|
-
finalizeLoginLine(i + idx, worker);
|
|
3005
2896
|
}));
|
|
3006
2897
|
if (i + BATCH_SIZE < accounts.length) await new Promise(r => setTimeout(r, randomLoginGap()));
|
|
3007
2898
|
hintGC();
|
|
3008
2899
|
}
|
|
3009
2900
|
|
|
3010
|
-
clearInterval(loginSpinnerInterval);
|
|
3011
2901
|
const loginDone = workers.filter(w => !w._tokenInvalid && w.channel).length;
|
|
3012
2902
|
const invalidWorkers = workers.filter(w => w._tokenInvalid);
|
|
3013
2903
|
const timedOutWorkers = workers.filter(w => !w.channel && !w._tokenInvalid);
|
|
3014
|
-
console.log(
|
|
3015
|
-
console.log('');
|
|
2904
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${loginDone}/${accounts.length} accounts connected`);
|
|
3016
2905
|
if (invalidWorkers.length > 0) {
|
|
3017
|
-
log('warn', `${rgb(239, 68, 68)}${invalidWorkers.length} account(s) have INVALID tokens
|
|
3018
|
-
for (const w of invalidWorkers) log('error', `
|
|
3019
|
-
console.log('');
|
|
2906
|
+
log('warn', `${rgb(239, 68, 68)}${invalidWorkers.length} account(s) have INVALID tokens`);
|
|
2907
|
+
for (const w of invalidWorkers) log('error', ` ${w.account.label || w.account.id} — token invalid or expired`);
|
|
3020
2908
|
}
|
|
3021
|
-
if (timedOutWorkers.length > 0) log('warn', `${timedOutWorkers.length} account(s) timed out during login
|
|
2909
|
+
if (timedOutWorkers.length > 0) log('warn', `${timedOutWorkers.length} account(s) timed out during login`);
|
|
3022
2910
|
|
|
3023
2911
|
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
3024
2912
|
|
|
3025
|
-
// ── Phase 2: Inventory check
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
// Print a unique marker, query its position, then overwrite it with the table
|
|
3033
|
-
// Set up stdin handler BEFORE writing MARKER (same fix as Phase 1 — avoids race)
|
|
3034
|
-
let invBaseRow = 1;
|
|
3035
|
-
const captureRow = () => new Promise(resolve => {
|
|
3036
|
-
const chunks = [];
|
|
3037
|
-
const handler = (chunk) => {
|
|
3038
|
-
chunks.push(chunk);
|
|
3039
|
-
const raw = chunks.join('');
|
|
3040
|
-
const m = raw.match(/\x1b\[(\d+);\d+R/);
|
|
3041
|
-
if (m) {
|
|
3042
|
-
process.stdin.removeListener('data', handler);
|
|
3043
|
-
invBaseRow = parseInt(m[1], 10) + 1; // +1: first account row is after marker
|
|
3044
|
-
resolve();
|
|
3045
|
-
}
|
|
3046
|
-
};
|
|
3047
|
-
process.stdin.on('data', handler);
|
|
3048
|
-
// Write to stderr so PTY doesn't echo the visible MARKER text to stdout
|
|
3049
|
-
process.stderr.write(MARKER);
|
|
3050
|
-
setTimeout(resolve, 50);
|
|
3051
|
-
});
|
|
3052
|
-
await captureRow();
|
|
3053
|
-
|
|
3054
|
-
// Now print the inventory table starting at invBaseRow
|
|
3055
|
-
const invMoveToRow = (row) => process.stdout.write(`\x1b[${row};1H`);
|
|
3056
|
-
console.log(` ${'─'.repeat(invVis)}`);
|
|
3057
|
-
for (let i = 0; i < activeWorkers.length; i++) {
|
|
3058
|
-
const w = activeWorkers[i];
|
|
3059
|
-
const num = `${c.dim}${(i + 1).toString().padStart(iColNum - 1)}${c.reset}`;
|
|
3060
|
-
const name = (w.username || w.account.label || '?').substring(0, iColName).padEnd(iColName);
|
|
3061
|
-
console.log(` ${num} ${c.dim}··${c.reset} ${name} ${c.dim}${'checking...'.padEnd(iColItems)}${c.reset} ${c.dim}${'···'.padEnd(iColVal)}${c.reset}`);
|
|
3062
|
-
}
|
|
3063
|
-
console.log(` ${'─'.repeat(invVis)}`);
|
|
3064
|
-
|
|
3065
|
-
let invDone = 0, invFailed = 0, invPending = activeWorkers.length;
|
|
3066
|
-
const drawInvProgress = () => {
|
|
3067
|
-
if (invPending === 0) return;
|
|
3068
|
-
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3069
|
-
const pct = activeWorkers.length > 0 ? ((activeWorkers.length - invPending) / activeWorkers.length) : 0;
|
|
3070
|
-
const barW = Math.min(20, startupTw - 40);
|
|
3071
|
-
const filled = Math.round(pct * barW);
|
|
3072
|
-
const bar = rgb(34, 211, 238) + '█'.repeat(filled) + rgb(50, 50, 70) + '░'.repeat(barW - filled) + c.reset;
|
|
3073
|
-
const pctStr = `${Math.round(pct * 100)}%`;
|
|
3074
|
-
invMoveToRow(invBaseRow);
|
|
3075
|
-
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`);
|
|
3076
|
-
};
|
|
3077
|
-
const invSpinnerInterval = setInterval(drawInvProgress, 80);
|
|
3078
|
-
|
|
3079
|
-
await Promise.all(activeWorkers.map(async (w, i) => {
|
|
3080
|
-
const num = `${c.dim}${(i + 1).toString().padStart(iColNum - 1)}${c.reset}`;
|
|
3081
|
-
const name = (w.username || w.account.label || '?').substring(0, iColName).padEnd(iColName);
|
|
3082
|
-
let invRes;
|
|
3083
|
-
try { invRes = await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3, silent: true }); }
|
|
3084
|
-
catch { invRes = { ok: false }; }
|
|
3085
|
-
invPending--;
|
|
3086
|
-
const items = invRes?.ok ? (invRes.result?.items?.length || 0) : 0;
|
|
3087
|
-
const val = invRes?.ok ? (invRes.result?.totalValue || 0) : 0;
|
|
3088
|
-
const sts = invRes?.ok ? `${rgb(52, 211, 153)}✓${c.reset}` : `${rgb(239, 68, 68)}✗${c.reset}`;
|
|
3089
|
-
const itemStr = `${items}`.padEnd(iColItems);
|
|
3090
|
-
const valStr = invRes?.ok ? `${c.green}⏣${val.toLocaleString()}${c.reset}` : `${c.dim}···${c.reset}`;
|
|
3091
|
-
const row = invBaseRow + 1 + i;
|
|
3092
|
-
invMoveToRow(row);
|
|
3093
|
-
process.stdout.write(` ${num} ${sts} ${name} ${itemStr} ${valStr.padEnd(iColVal + 5)}\x1b[K`);
|
|
3094
|
-
if (invRes?.ok) invDone++; else invFailed++;
|
|
2913
|
+
// ── Phase 2: Inventory check ─────────────────────────────────────
|
|
2914
|
+
console.log(` ${rgb(34, 211, 238)}·${c.reset} Checking inventory (${activeWorkers.length} accounts)...`);
|
|
2915
|
+
let invDone = 0, invFailed = 0;
|
|
2916
|
+
await Promise.all(activeWorkers.map(async (w) => {
|
|
2917
|
+
try { await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3, silent: true }); }
|
|
2918
|
+
catch { invFailed++; return; }
|
|
2919
|
+
invDone++;
|
|
3095
2920
|
}));
|
|
3096
2921
|
|
|
3097
|
-
clearInterval(invSpinnerInterval);
|
|
3098
|
-
process.stdout.write(`\r\x1b[2K`);
|
|
3099
|
-
|
|
3100
2922
|
if (invFailed > 0) {
|
|
3101
|
-
console.log(` ${rgb(239, 68, 68)}✗${c.reset}
|
|
3102
|
-
log('error', `${c.red}Not starting grind loops — ${invFailed} accounts failed inventory.${c.reset}`);
|
|
2923
|
+
console.log(` ${rgb(239, 68, 68)}✗${c.reset} Inventory: ${invFailed} failed — not starting grind loops`);
|
|
3103
2924
|
return;
|
|
3104
2925
|
}
|
|
3105
|
-
console.log(` ${rgb(52, 211, 153)}✓${c.reset}
|
|
3106
|
-
console.log('');
|
|
2926
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} Inventory: ${invDone}/${activeWorkers.length} clear`);
|
|
3107
2927
|
|
|
3108
|
-
// ── Phase 2.5: Balance check
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
const bColWallet = 12;
|
|
3112
|
-
const bColBank = 12;
|
|
3113
|
-
const bColTotal = 14;
|
|
3114
|
-
const bColLs = 4;
|
|
3115
|
-
const balVis = 7 + bColNum + bColName + bColWallet + bColBank + bColTotal + bColLs + 14;
|
|
3116
|
-
|
|
3117
|
-
// Capture starting row for balance phase
|
|
3118
|
-
// Set up stdin handler BEFORE writing MARKER (same fix — avoids race + PTY echo)
|
|
3119
|
-
let balBaseRow = 1;
|
|
3120
|
-
const balCaptureRow = () => new Promise(resolve => {
|
|
3121
|
-
const chunks = [];
|
|
3122
|
-
const handler = (chunk) => {
|
|
3123
|
-
chunks.push(chunk);
|
|
3124
|
-
const raw = chunks.join('');
|
|
3125
|
-
const m = raw.match(/\x1b\[(\d+);\d+R/);
|
|
3126
|
-
if (m) {
|
|
3127
|
-
process.stdin.removeListener('data', handler);
|
|
3128
|
-
balBaseRow = parseInt(m[1], 10) + 1;
|
|
3129
|
-
resolve();
|
|
3130
|
-
}
|
|
3131
|
-
};
|
|
3132
|
-
process.stdin.on('data', handler);
|
|
3133
|
-
// Write to stderr so PTY doesn't echo the visible MARKER text to stdout
|
|
3134
|
-
process.stderr.write(MARKER);
|
|
3135
|
-
setTimeout(resolve, 50);
|
|
3136
|
-
});
|
|
3137
|
-
await balCaptureRow();
|
|
3138
|
-
|
|
3139
|
-
const balMoveToRow = (row) => process.stdout.write(`\x1b[${row};1H`);
|
|
3140
|
-
console.log(` ${'─'.repeat(balVis)}`);
|
|
3141
|
-
for (let i = 0; i < activeWorkers.length; i++) {
|
|
3142
|
-
const w = activeWorkers[i];
|
|
3143
|
-
const num = `${c.dim}${(i + 1).toString().padStart(bColNum - 1)}${c.reset}`;
|
|
3144
|
-
const name = (w.username || w.account.label || '?').substring(0, bColName).padEnd(bColName);
|
|
3145
|
-
console.log(` ${num} ${c.dim}··${c.reset} ${name} ${c.dim}${'checking'.padEnd(bColWallet)}${c.reset} ${c.dim}${'···'.padEnd(bColBank)}${c.reset} ${c.dim}${'···'.padEnd(bColTotal)}${c.reset} ${c.dim}♥?${c.reset}`);
|
|
3146
|
-
}
|
|
3147
|
-
console.log(` ${'─'.repeat(balVis)}`);
|
|
3148
|
-
|
|
3149
|
-
let balDone = 0, balPending = activeWorkers.length;
|
|
3150
|
-
const drawBalProgress = () => {
|
|
3151
|
-
if (balPending === 0) return;
|
|
3152
|
-
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3153
|
-
const pct = activeWorkers.length > 0 ? ((activeWorkers.length - balPending) / activeWorkers.length) : 0;
|
|
3154
|
-
const barW = Math.min(20, startupTw - 40);
|
|
3155
|
-
const filled = Math.round(pct * barW);
|
|
3156
|
-
const bar = rgb(251, 191, 36) + '█'.repeat(filled) + rgb(50, 50, 70) + '░'.repeat(barW - filled) + c.reset;
|
|
3157
|
-
balMoveToRow(balBaseRow);
|
|
3158
|
-
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`);
|
|
3159
|
-
};
|
|
3160
|
-
const balSpinnerInterval = setInterval(drawBalProgress, 80);
|
|
3161
|
-
|
|
3162
|
-
await Promise.all(activeWorkers.map(async (w, i) => {
|
|
2928
|
+
// ── Phase 2.5: Balance check ────────────────────────────────────
|
|
2929
|
+
console.log(` ${rgb(251, 191, 36)}·${c.reset} Checking balance (${activeWorkers.length} accounts)...`);
|
|
2930
|
+
await Promise.all(activeWorkers.map(async (w) => {
|
|
3163
2931
|
try { await w.checkBalance(true); } catch {}
|
|
3164
|
-
balPending--;
|
|
3165
|
-
const num = `${c.dim}${(i + 1).toString().padStart(bColNum - 1)}${c.reset}`;
|
|
3166
|
-
const name = (w.username || w.account.label || '?').substring(0, bColName).padEnd(bColName);
|
|
3167
|
-
const wallet = w.stats?.balance || 0;
|
|
3168
|
-
const bank = w.stats?.bankBalance || 0;
|
|
3169
|
-
const ls = w._lifesavers ?? '?';
|
|
3170
|
-
const lsColor = ls === 0 ? rgb(239, 68, 68) : ls <= 2 ? rgb(251, 191, 36) : rgb(52, 211, 153);
|
|
3171
|
-
const walletStr = `${c.green}⏣${wallet.toLocaleString()}${c.reset}`;
|
|
3172
|
-
const bankStr = `${c.cyan}⏣${bank.toLocaleString()}${c.reset}`;
|
|
3173
|
-
const totalStr = `${c.bold}⏣${(wallet + bank).toLocaleString()}${c.reset}`;
|
|
3174
|
-
const row = balBaseRow + 1 + i;
|
|
3175
|
-
balMoveToRow(row);
|
|
3176
|
-
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`);
|
|
3177
|
-
balDone++;
|
|
3178
2932
|
}));
|
|
3179
2933
|
|
|
3180
|
-
clearInterval(balSpinnerInterval);
|
|
3181
|
-
process.stdout.write(`\r\x1b[2K`);
|
|
3182
|
-
|
|
3183
2934
|
let totalWallet = 0, totalBank = 0, noLifesaverAccounts = [];
|
|
3184
2935
|
for (const w of activeWorkers) {
|
|
3185
2936
|
totalWallet += w.stats?.balance || 0;
|
|
3186
2937
|
totalBank += w.stats?.bankBalance || 0;
|
|
3187
2938
|
if (w._lifesavers === 0) noLifesaverAccounts.push(w.username || w.account.label);
|
|
3188
2939
|
}
|
|
3189
|
-
console.log(` ${rgb(52, 211, 153)}✓${c.reset}
|
|
2940
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} Balance: ${c.green}⏣ ${(totalWallet + totalBank).toLocaleString()}${c.reset} ${c.dim}(wallet: ⏣ ${totalWallet.toLocaleString()} + bank: ⏣ ${totalBank.toLocaleString()})${c.reset}`);
|
|
3190
2941
|
if (noLifesaverAccounts.length > 0) {
|
|
3191
|
-
console.log(` ${rgb(239, 68, 68)}⚠${c.reset} ${
|
|
2942
|
+
console.log(` ${rgb(239, 68, 68)}⚠${c.reset} ${noLifesaverAccounts.length} account(s) have 0 LIFESAVERS — crime/search disabled`);
|
|
3192
2943
|
}
|
|
3193
|
-
console.log('');
|
|
3194
2944
|
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
const dmCheckPulse = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3198
|
-
console.log(` ${rgb(139, 92, 246)}${dmCheckPulse}${c.reset} ${c.dim}Checking DM history...${c.reset}`);
|
|
2945
|
+
// ── Phase 2.75: DM history check ────────────────────────────────
|
|
2946
|
+
console.log(` ${rgb(139, 92, 246)}·${c.reset} Checking DM history...`);
|
|
3199
2947
|
let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [], dmUnknown = [];
|
|
3200
2948
|
for (const w of activeWorkers) {
|
|
3201
2949
|
try {
|
|
@@ -3204,25 +2952,12 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3204
2952
|
if (dm.levelUps > 0) dmLevelUps += dm.levelUps;
|
|
3205
2953
|
if (dm.lifesavers === 0) dmNoLs.push(w.username);
|
|
3206
2954
|
if (dm.lifesavers === -1) dmUnknown.push(w.username);
|
|
3207
|
-
// Store level and lifesaver for dashboard
|
|
3208
2955
|
if (dm.currentLevel > 0) w._level = dm.currentLevel;
|
|
3209
2956
|
if (dm.lifesavers >= 0) w._lifesavers = dm.lifesavers;
|
|
3210
|
-
const parts = [];
|
|
3211
|
-
if (dm.currentLevel > 0) parts.push(`Lv${dm.currentLevel}`);
|
|
3212
|
-
if (dm.deaths > 0) parts.push(`${rgb(239, 68, 68)}${dm.deaths} deaths${c.reset}`);
|
|
3213
|
-
if (dm.lifesavers >= 0) {
|
|
3214
|
-
const lc = dm.lifesavers === 0 ? rgb(239, 68, 68) : dm.lifesavers <= 2 ? rgb(251, 191, 36) : rgb(52, 211, 153);
|
|
3215
|
-
parts.push(`${lc}♥${dm.lifesavers}${c.reset}`);
|
|
3216
|
-
} else {
|
|
3217
|
-
// Unknown lifesavers — pulse to show pending
|
|
3218
|
-
const pulse = PULSE_CHARS[Math.floor(Date.now() / 400) % PULSE_CHARS.length];
|
|
3219
|
-
parts.push(`${D}${pulse}♥?${c.reset}`);
|
|
3220
|
-
}
|
|
3221
2957
|
} catch {}
|
|
3222
2958
|
}
|
|
3223
2959
|
if (dmNoLs.length > 0) {
|
|
3224
2960
|
log('warn', `⚠ No lifesavers: ${dmNoLs.join(', ')}`);
|
|
3225
|
-
// Set Redis keys to block crime/search
|
|
3226
2961
|
for (const w of activeWorkers) {
|
|
3227
2962
|
if (dmNoLs.includes(w.username) && redis) {
|
|
3228
2963
|
try {
|
|
@@ -3234,18 +2969,15 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3234
2969
|
}
|
|
3235
2970
|
if (dmUnknown.length > 0) {
|
|
3236
2971
|
log('warn', `⚠ Lifesavers unknown — live monitor: ${dmUnknown.join(', ')}`);
|
|
3237
|
-
// Crime/search on these accounts will be skipped via safety hold until the live
|
|
3238
|
-
// DM gateway listener detects a death (→ sets count) or confirms clean.
|
|
3239
2972
|
}
|
|
3240
2973
|
const dmSummaryParts = [];
|
|
3241
2974
|
if (dmDeaths > 0) dmSummaryParts.push(`${dmDeaths} deaths`);
|
|
3242
2975
|
if (dmLevelUps > 0) dmSummaryParts.push(`${dmLevelUps} level-ups`);
|
|
3243
2976
|
if (dmUnknown.length > 0) dmSummaryParts.push(`${dmUnknown.length} pending`);
|
|
3244
|
-
console.log(` ${rgb(52, 211, 153)}✓${c.reset} DM check: ${dmSummaryParts.length > 0 ? dmSummaryParts.join(', ') : 'clean
|
|
3245
|
-
console.log('');
|
|
3246
|
-
|
|
3247
|
-
console.log(` ${rgb(139, 92, 246)}${c.bold}>>>${c.reset} ${gradientText('Starting grind loops...', [139, 92, 246], [52, 211, 153])}`);
|
|
2977
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} DM check: ${dmSummaryParts.length > 0 ? dmSummaryParts.join(', ') : 'clean'}`);
|
|
3248
2978
|
|
|
2979
|
+
// ── Phase 3: Start grind loops ───────────────────────────────────
|
|
2980
|
+
console.log(` ${rgb(139, 92, 246)}>>>${c.reset} Starting grind loops...`);
|
|
3249
2981
|
// Phase 3: Start all grind loops (only for valid workers)
|
|
3250
2982
|
for (const w of activeWorkers) {
|
|
3251
2983
|
if (!shutdownCalled) w.grindLoop();
|