dankgrinder 8.16.0 → 8.17.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 +52 -394
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -94,9 +94,6 @@ const c = {
|
|
|
94
94
|
restoreCursor: '\x1b8',
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
-
const WORKER_COLORS = [c.cyan, c.magenta, c.yellow, c.green, c.blue, c.red];
|
|
98
|
-
// Unique marker written to stdout so we can query cursor position via DSR response
|
|
99
|
-
const MARKER = '\x1b[6n\x1b[@@MARKER@@';
|
|
100
97
|
const DANK_MEMER_ID = '270904126974590976';
|
|
101
98
|
|
|
102
99
|
// ── Safe options for search/crime ──────────────────────────
|
|
@@ -231,54 +228,11 @@ function gradientLine(text, from, to) {
|
|
|
231
228
|
}
|
|
232
229
|
|
|
233
230
|
function gradientText(text, from, to) {
|
|
234
|
-
return
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// ── Sparkline graph for earnings trend ───────────────────────
|
|
238
|
-
const SPARK_CHARS = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
|
|
239
|
-
function drawSparkline(data, width = 12) {
|
|
240
|
-
if (!data || data.length === 0) return c.dim + '·····' + c.reset;
|
|
241
|
-
const recent = data.slice(-width);
|
|
242
|
-
const min = Math.min(...recent);
|
|
243
|
-
const max = Math.max(...recent);
|
|
244
|
-
const range = max - min || 1;
|
|
245
|
-
return recent.map(v => {
|
|
246
|
-
const idx = Math.min(7, Math.floor(((v - min) / range) * 8));
|
|
247
|
-
const ch = SPARK_CHARS[idx] || '▁';
|
|
248
|
-
const t = (v - min) / range;
|
|
249
|
-
// Gradient from red->yellow->green based on relative value
|
|
250
|
-
const r = t < 0.5 ? 239 : lerp(251, 52, (t - 0.5) * 2);
|
|
251
|
-
const g = t < 0.5 ? lerp(68, 191, t * 2) : lerp(191, 211, (t - 0.5) * 2);
|
|
252
|
-
const b = t < 0.5 ? lerp(68, 36, t * 2) : lerp(36, 153, (t - 0.5) * 2);
|
|
253
|
-
return rgb(r, g, b) + ch + c.reset;
|
|
254
|
-
}).join('');
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ── Advanced progress bar ────────────────────────────────────
|
|
258
|
-
function progressBar(value, max, width, filledColor, emptyColor) {
|
|
259
|
-
const pct = max > 0 ? Math.min(1, value / max) : 0;
|
|
260
|
-
const filled = Math.round(pct * width);
|
|
261
|
-
const empty = width - filled;
|
|
262
|
-
const fc = filledColor || [52, 211, 153];
|
|
263
|
-
const ec = emptyColor || [50, 50, 70];
|
|
264
|
-
return rgb(fc[0], fc[1], fc[2]) + '█'.repeat(filled) + rgb(ec[0], ec[1], ec[2]) + '░'.repeat(empty) + c.reset;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ── Animated braille spinner frames ──────────────────────────
|
|
268
|
-
const BRAILLE_SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
269
|
-
const BLOCK_SPIN = ['▉', '▊', '▋', '▌', '▍', '▎', '▏', '▎', '▍', '▌', '▋', '▊'];
|
|
270
|
-
const PULSE_CHARS = ['○', '◎', '●', '◉', '●', '◎'];
|
|
271
|
-
function getSpinner(type = 'braille') {
|
|
272
|
-
const now = Math.floor(Date.now() / 80);
|
|
273
|
-
if (type === 'block') return BLOCK_SPIN[now % BLOCK_SPIN.length];
|
|
274
|
-
if (type === 'pulse') return PULSE_CHARS[now % PULSE_CHARS.length];
|
|
275
|
-
return BRAILLE_SPIN[now % BRAILLE_SPIN.length];
|
|
231
|
+
return text;
|
|
276
232
|
}
|
|
277
233
|
|
|
278
234
|
function colorBanner() {
|
|
279
|
-
return `
|
|
280
|
-
DANKGRINDER v${PKG_VERSION}
|
|
281
|
-
`;
|
|
235
|
+
return `DANKGRINDER v${PKG_VERSION}`;
|
|
282
236
|
}
|
|
283
237
|
|
|
284
238
|
// ── Simple Logging ─────────────────────────────────────────────
|
|
@@ -2692,24 +2646,10 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2692
2646
|
REDIS_URL = process.env.REDIS_URL || '';
|
|
2693
2647
|
WEBHOOK_URL = process.env.WEBHOOK_URL || '';
|
|
2694
2648
|
|
|
2695
|
-
const tw =
|
|
2696
|
-
const bar = '─'.repeat(tw);
|
|
2649
|
+
const tw = 80;
|
|
2697
2650
|
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
try { require('zlib-sync'); hasZlib = true; } catch {}
|
|
2701
|
-
|
|
2702
|
-
console.log(colorBanner());
|
|
2703
|
-
console.log(
|
|
2704
|
-
` ${rgb(139, 92, 246)}v${PKG_VERSION}${c.reset}` +
|
|
2705
|
-
` ${c.dim}·${c.reset} ${c.white}${AccountWorker.COMMAND_MAP.length} Commands${c.reset}` +
|
|
2706
|
-
` ${c.dim}·${c.reset} ${rgb(34, 211, 238)}${CLOUD_MODE ? 'Cloud Mode' : (CLUSTER_ENABLED ? 'Cluster Mode' : 'Standalone')}${c.reset}` +
|
|
2707
|
-
` ${c.dim}·${c.reset} ${rgb(52, 211, 153)}Auto-Recovery${c.reset}` +
|
|
2708
|
-
` ${c.dim}·${c.reset} ${rgb(251, 191, 36)}Loss Limiter${c.reset}`
|
|
2709
|
-
);
|
|
2710
|
-
console.log(bar);
|
|
2711
|
-
|
|
2712
|
-
log('info', `${c.dim}Fetching accounts...${c.reset}`);
|
|
2651
|
+
console.log(`DANKGRINDER v${PKG_VERSION} - ${AccountWorker.COMMAND_MAP.length} commands - Standalone`);
|
|
2652
|
+
console.log('Fetching accounts...');
|
|
2713
2653
|
|
|
2714
2654
|
const fetchOpts = CLOUD_MODE ? { cloud: true } : {};
|
|
2715
2655
|
let data = await fetchConfig(4, 2000, fetchOpts);
|
|
@@ -2822,116 +2762,14 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2822
2762
|
console.log(` ${checks.join(' ')}`);
|
|
2823
2763
|
console.log('');
|
|
2824
2764
|
|
|
2825
|
-
// ── Phase 1: Login
|
|
2826
|
-
const startupTw = process.stdout.columns || 90;
|
|
2827
|
-
const colNum = 4; // " #"
|
|
2828
|
-
const colSts = 3; // "ST"
|
|
2829
|
-
const colName = Math.min(24, Math.max(12, Math.floor(startupTw * 0.25)));
|
|
2830
|
-
const colGuild = Math.min(18, Math.max(8, Math.floor(startupTw * 0.2)));
|
|
2831
|
-
const colCmds = 8;
|
|
2832
|
-
const loginVis = colNum + colSts + colName + colGuild + colCmds + 10;
|
|
2833
|
-
|
|
2834
|
-
const loginStates = accounts.map((acc, i) => ({
|
|
2835
|
-
name: acc.label || acc.id || '?',
|
|
2836
|
-
done: false,
|
|
2837
|
-
failed: false,
|
|
2838
|
-
worker: null,
|
|
2839
|
-
}));
|
|
2840
|
-
|
|
2841
|
-
let loginLines = [];
|
|
2842
|
-
loginLines.push(` ${'─'.repeat(loginVis)}`);
|
|
2843
|
-
for (let i = 0; i < loginStates.length; i++) {
|
|
2844
|
-
const s = loginStates[i];
|
|
2845
|
-
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
2846
|
-
const name = s.name.substring(0, colName).padEnd(colName);
|
|
2847
|
-
const guild = c.dim + '···'.padEnd(colGuild) + c.reset;
|
|
2848
|
-
const cmds = c.dim + '···'.padEnd(colCmds) + c.reset;
|
|
2849
|
-
loginLines.push(` ${num} ${c.dim}··${c.reset} ${name} ${guild} ${cmds}`);
|
|
2850
|
-
}
|
|
2851
|
-
loginLines.push(` ${'─'.repeat(loginVis)}`);
|
|
2852
|
-
for (const l of loginLines) console.log(l);
|
|
2853
|
-
|
|
2854
|
-
// Dynamically capture the starting row of the login table via DSR.
|
|
2855
|
-
// Write MARKER to stderr (not stdout) to avoid PTY cooked-mode echoing
|
|
2856
|
-
// of the visible "@MARKER@@" text portion, which was causing the DSR
|
|
2857
|
-
// response to be swallowed or delayed.
|
|
2858
|
-
let loginBaseRow = 1;
|
|
2859
|
-
const captureLoginRow = () => new Promise(resolve => {
|
|
2860
|
-
const chunks = [];
|
|
2861
|
-
const handler = (chunk) => {
|
|
2862
|
-
chunks.push(chunk);
|
|
2863
|
-
const raw = chunks.join('');
|
|
2864
|
-
const m = raw.match(/\x1b\[(\d+);\d+R/);
|
|
2865
|
-
if (m) {
|
|
2866
|
-
process.stdin.removeListener('data', handler);
|
|
2867
|
-
loginBaseRow = parseInt(m[1], 10) + 1;
|
|
2868
|
-
resolve();
|
|
2869
|
-
}
|
|
2870
|
-
};
|
|
2871
|
-
process.stdin.on('data', handler);
|
|
2872
|
-
// Write to stderr so PTY doesn't echo the visible MARKER text to stdout
|
|
2873
|
-
process.stderr.write(MARKER);
|
|
2874
|
-
setTimeout(resolve, 50);
|
|
2875
|
-
});
|
|
2876
|
-
await captureLoginRow();
|
|
2877
|
-
|
|
2878
|
-
let loginPending = new Array(accounts.length).fill(true);
|
|
2879
|
-
const moveToRow = (row) => process.stdout.write(`\x1b[${row};1H`);
|
|
2880
|
-
|
|
2881
|
-
const drawLoginSpinners = () => {
|
|
2882
|
-
for (let i = 0; i < loginPending.length; i++) {
|
|
2883
|
-
if (!loginPending[i]) continue;
|
|
2884
|
-
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
2885
|
-
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
2886
|
-
const name = loginStates[i].name.substring(0, colName).padEnd(colName);
|
|
2887
|
-
const guild = c.dim + 'logging in...'.substring(0, colGuild) + c.reset;
|
|
2888
|
-
const cmds = c.dim + '···'.padEnd(colCmds) + c.reset;
|
|
2889
|
-
const row = loginBaseRow + 1 + i; // +1 skips the top border line
|
|
2890
|
-
moveToRow(row);
|
|
2891
|
-
process.stdout.write(` ${num} ${rgb(139, 92, 246)}${spin}${c.reset} ${name} ${guild} ${cmds}\x1b[K`);
|
|
2892
|
-
}
|
|
2893
|
-
// Move cursor back to bottom to avoid overwriting the bottom border
|
|
2894
|
-
const lastRow = loginBaseRow + 1 + accounts.length + 1;
|
|
2895
|
-
moveToRow(lastRow);
|
|
2896
|
-
};
|
|
2897
|
-
const loginSpinnerInterval = setInterval(drawLoginSpinners, 80);
|
|
2898
|
-
|
|
2899
|
-
const finalizeLoginLine = (idx, worker) => {
|
|
2900
|
-
if (!loginPending[idx]) return;
|
|
2901
|
-
loginPending[idx] = false;
|
|
2902
|
-
const s = loginStates[idx];
|
|
2903
|
-
s.done = true;
|
|
2904
|
-
s.worker = worker;
|
|
2905
|
-
|
|
2906
|
-
const num = `${c.dim}${(idx + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
2907
|
-
const name = (worker.username || s.name || '?').substring(0, colName).padEnd(colName);
|
|
2908
|
-
let sts, guild, cmds;
|
|
2909
|
-
if (worker._tokenInvalid) {
|
|
2910
|
-
sts = `${rgb(239, 68, 68)}✗${c.reset}`;
|
|
2911
|
-
guild = 'INVALID'.padEnd(colGuild);
|
|
2912
|
-
cmds = '···'.padEnd(colCmds);
|
|
2913
|
-
s.failed = true;
|
|
2914
|
-
} else if (worker.channel) {
|
|
2915
|
-
sts = `${rgb(52, 211, 153)}✓${c.reset}`;
|
|
2916
|
-
const gn = (worker.channel.guild?.name || worker.channel.guild?.id || 'DM').substring(0, colGuild);
|
|
2917
|
-
guild = gn.padEnd(colGuild);
|
|
2918
|
-
cmds = `${worker.stats?.commands || 0}`.padEnd(colCmds);
|
|
2919
|
-
} else {
|
|
2920
|
-
sts = `${rgb(251, 146, 60)}⏳${c.reset}`;
|
|
2921
|
-
guild = 'timeout'.padEnd(colGuild);
|
|
2922
|
-
cmds = '···'.padEnd(colCmds);
|
|
2923
|
-
}
|
|
2924
|
-
const row = loginBaseRow + 1 + idx; // +1 skips the top border line
|
|
2925
|
-
moveToRow(row);
|
|
2926
|
-
process.stdout.write(` ${num} ${sts} ${name} ${c.dim}${guild}${c.reset} ${c.dim}${cmds}${c.reset}\x1b[K`);
|
|
2927
|
-
};
|
|
2928
|
-
|
|
2765
|
+
// ── Phase 1: Login ─────────────────────────────────────────────
|
|
2929
2766
|
const parsedGapMin = Number.parseInt(String(process.env.LOGIN_GAP_MIN_MS || '50'), 10);
|
|
2930
2767
|
const parsedGapMax = Number.parseInt(String(process.env.LOGIN_GAP_MAX_MS || '150'), 10);
|
|
2931
2768
|
const LOGIN_GAP_MIN_MS = Number.isFinite(parsedGapMin) && parsedGapMin >= 0 ? parsedGapMin : 50;
|
|
2932
2769
|
const LOGIN_GAP_MAX_MS = Number.isFinite(parsedGapMax) && parsedGapMax >= LOGIN_GAP_MIN_MS ? parsedGapMax : Math.max(parsedGapMin, 150);
|
|
2933
2770
|
const randomLoginGap = () => LOGIN_GAP_MAX_MS <= LOGIN_GAP_MIN_MS ? LOGIN_GAP_MIN_MS : LOGIN_GAP_MIN_MS + Math.floor(Math.random() * (LOGIN_GAP_MAX_MS - LOGIN_GAP_MIN_MS + 1));
|
|
2934
2771
|
|
|
2772
|
+
console.log(`Logging in ${accounts.length} accounts...`);
|
|
2935
2773
|
const BATCH_SIZE = 10;
|
|
2936
2774
|
for (let i = 0; i < accounts.length; i += BATCH_SIZE) {
|
|
2937
2775
|
if (shutdownCalled) break;
|
|
@@ -2941,260 +2779,88 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2941
2779
|
const worker = new AccountWorker(acc, i + idx);
|
|
2942
2780
|
workers.push(worker);
|
|
2943
2781
|
workerMap.set(acc.id, worker);
|
|
2944
|
-
loginStates[i + idx].worker = worker;
|
|
2945
2782
|
await worker.start();
|
|
2946
|
-
|
|
2783
|
+
if (worker._tokenInvalid) {
|
|
2784
|
+
console.log(` [${i + idx + 1}] FAIL - invalid token: ${acc.label || acc.id}`);
|
|
2785
|
+
} else if (worker.channel) {
|
|
2786
|
+
console.log(` [${i + idx + 1}] OK - ${worker.username}`);
|
|
2787
|
+
} else {
|
|
2788
|
+
console.log(` [${i + idx + 1}] TIMEOUT`);
|
|
2789
|
+
}
|
|
2947
2790
|
}));
|
|
2948
2791
|
if (i + BATCH_SIZE < accounts.length) await new Promise(r => setTimeout(r, randomLoginGap()));
|
|
2949
2792
|
hintGC();
|
|
2950
2793
|
}
|
|
2951
2794
|
|
|
2952
|
-
clearInterval(loginSpinnerInterval);
|
|
2953
2795
|
const loginDone = workers.filter(w => !w._tokenInvalid && w.channel).length;
|
|
2954
2796
|
const invalidWorkers = workers.filter(w => w._tokenInvalid);
|
|
2955
2797
|
const timedOutWorkers = workers.filter(w => !w.channel && !w._tokenInvalid);
|
|
2956
|
-
console.log(
|
|
2957
|
-
console.log('');
|
|
2798
|
+
console.log(`Login complete: ${loginDone}/${accounts.length} connected`);
|
|
2958
2799
|
if (invalidWorkers.length > 0) {
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2800
|
+
for (const w of invalidWorkers) console.log(` FAIL - invalid token: ${w.account.label || w.account.id}`);
|
|
2801
|
+
}
|
|
2802
|
+
if (timedOutWorkers.length > 0) {
|
|
2803
|
+
console.log(` WARN - ${timedOutWorkers.length} timed out (will retry in background)`);
|
|
2962
2804
|
}
|
|
2963
|
-
if (timedOutWorkers.length > 0) log('warn', `${timedOutWorkers.length} account(s) timed out during login (will retry in background)`);
|
|
2964
2805
|
|
|
2965
2806
|
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
2966
2807
|
|
|
2967
|
-
// ── Phase 2: Inventory check
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
// Print a unique marker, query its position, then overwrite it with the table
|
|
2975
|
-
// Set up stdin handler BEFORE writing MARKER (same fix as Phase 1 — avoids race)
|
|
2976
|
-
let invBaseRow = 1;
|
|
2977
|
-
const captureRow = () => new Promise(resolve => {
|
|
2978
|
-
const chunks = [];
|
|
2979
|
-
const handler = (chunk) => {
|
|
2980
|
-
chunks.push(chunk);
|
|
2981
|
-
const raw = chunks.join('');
|
|
2982
|
-
const m = raw.match(/\x1b\[(\d+);\d+R/);
|
|
2983
|
-
if (m) {
|
|
2984
|
-
process.stdin.removeListener('data', handler);
|
|
2985
|
-
invBaseRow = parseInt(m[1], 10) + 1; // +1: first account row is after marker
|
|
2986
|
-
resolve();
|
|
2987
|
-
}
|
|
2988
|
-
};
|
|
2989
|
-
process.stdin.on('data', handler);
|
|
2990
|
-
// Write to stderr so PTY doesn't echo the visible MARKER text to stdout
|
|
2991
|
-
process.stderr.write(MARKER);
|
|
2992
|
-
setTimeout(resolve, 50);
|
|
2993
|
-
});
|
|
2994
|
-
await captureRow();
|
|
2995
|
-
|
|
2996
|
-
// Now print the inventory table starting at invBaseRow
|
|
2997
|
-
const invMoveToRow = (row) => process.stdout.write(`\x1b[${row};1H`);
|
|
2998
|
-
console.log(` ${'─'.repeat(invVis)}`);
|
|
2999
|
-
for (let i = 0; i < activeWorkers.length; i++) {
|
|
3000
|
-
const w = activeWorkers[i];
|
|
3001
|
-
const num = `${c.dim}${(i + 1).toString().padStart(iColNum - 1)}${c.reset}`;
|
|
3002
|
-
const name = (w.username || w.account.label || '?').substring(0, iColName).padEnd(iColName);
|
|
3003
|
-
console.log(` ${num} ${c.dim}··${c.reset} ${name} ${c.dim}${'checking...'.padEnd(iColItems)}${c.reset} ${c.dim}${'···'.padEnd(iColVal)}${c.reset}`);
|
|
3004
|
-
}
|
|
3005
|
-
console.log(` ${'─'.repeat(invVis)}`);
|
|
3006
|
-
|
|
3007
|
-
let invDone = 0, invFailed = 0, invPending = activeWorkers.length;
|
|
3008
|
-
const drawInvProgress = () => {
|
|
3009
|
-
if (invPending === 0) return;
|
|
3010
|
-
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3011
|
-
const pct = activeWorkers.length > 0 ? ((activeWorkers.length - invPending) / activeWorkers.length) : 0;
|
|
3012
|
-
const barW = Math.min(20, startupTw - 40);
|
|
3013
|
-
const filled = Math.round(pct * barW);
|
|
3014
|
-
const bar = rgb(34, 211, 238) + '█'.repeat(filled) + rgb(50, 50, 70) + '░'.repeat(barW - filled) + c.reset;
|
|
3015
|
-
const pctStr = `${Math.round(pct * 100)}%`;
|
|
3016
|
-
invMoveToRow(invBaseRow);
|
|
3017
|
-
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`);
|
|
3018
|
-
};
|
|
3019
|
-
const invSpinnerInterval = setInterval(drawInvProgress, 80);
|
|
3020
|
-
|
|
3021
|
-
await Promise.all(activeWorkers.map(async (w, i) => {
|
|
3022
|
-
const num = `${c.dim}${(i + 1).toString().padStart(iColNum - 1)}${c.reset}`;
|
|
3023
|
-
const name = (w.username || w.account.label || '?').substring(0, iColName).padEnd(iColName);
|
|
3024
|
-
let invRes;
|
|
3025
|
-
try { invRes = await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3, silent: true }); }
|
|
3026
|
-
catch { invRes = { ok: false }; }
|
|
3027
|
-
invPending--;
|
|
3028
|
-
const items = invRes?.ok ? (invRes.result?.items?.length || 0) : 0;
|
|
3029
|
-
const val = invRes?.ok ? (invRes.result?.totalValue || 0) : 0;
|
|
3030
|
-
const sts = invRes?.ok ? `${rgb(52, 211, 153)}✓${c.reset}` : `${rgb(239, 68, 68)}✗${c.reset}`;
|
|
3031
|
-
const itemStr = `${items}`.padEnd(iColItems);
|
|
3032
|
-
const valStr = invRes?.ok ? `${c.green}⏣${val.toLocaleString()}${c.reset}` : `${c.dim}···${c.reset}`;
|
|
3033
|
-
const row = invBaseRow + 1 + i;
|
|
3034
|
-
invMoveToRow(row);
|
|
3035
|
-
process.stdout.write(` ${num} ${sts} ${name} ${itemStr} ${valStr.padEnd(iColVal + 5)}\x1b[K`);
|
|
3036
|
-
if (invRes?.ok) invDone++; else invFailed++;
|
|
2808
|
+
// ── Phase 2: Inventory check ────────────────────────────────────
|
|
2809
|
+
console.log('Checking inventory...');
|
|
2810
|
+
let invFailed = 0;
|
|
2811
|
+
await Promise.all(activeWorkers.map(async (w) => {
|
|
2812
|
+
try { await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3, silent: true }); }
|
|
2813
|
+
catch { invFailed++; }
|
|
3037
2814
|
}));
|
|
3038
|
-
|
|
3039
|
-
clearInterval(invSpinnerInterval);
|
|
3040
|
-
process.stdout.write(`\r\x1b[2K`);
|
|
3041
|
-
|
|
3042
2815
|
if (invFailed > 0) {
|
|
3043
|
-
console.log(`
|
|
3044
|
-
log('error', `${c.red}Not starting grind loops — ${invFailed} accounts failed inventory.${c.reset}`);
|
|
2816
|
+
console.log(`Inventory failed for ${invFailed} accounts. Not starting grind loops.`);
|
|
3045
2817
|
return;
|
|
3046
2818
|
}
|
|
3047
|
-
console.log(
|
|
3048
|
-
console.log('');
|
|
3049
|
-
|
|
3050
|
-
// ── Phase 2.5: Balance check — inline table, single spinner for progress ─────────
|
|
3051
|
-
const bColNum = 4;
|
|
3052
|
-
const bColName = Math.min(22, Math.max(12, Math.floor(startupTw * 0.22)));
|
|
3053
|
-
const bColWallet = 12;
|
|
3054
|
-
const bColBank = 12;
|
|
3055
|
-
const bColTotal = 14;
|
|
3056
|
-
const bColLs = 4;
|
|
3057
|
-
const balVis = 7 + bColNum + bColName + bColWallet + bColBank + bColTotal + bColLs + 14;
|
|
3058
|
-
|
|
3059
|
-
// Capture starting row for balance phase
|
|
3060
|
-
// Set up stdin handler BEFORE writing MARKER (same fix — avoids race + PTY echo)
|
|
3061
|
-
let balBaseRow = 1;
|
|
3062
|
-
const balCaptureRow = () => new Promise(resolve => {
|
|
3063
|
-
const chunks = [];
|
|
3064
|
-
const handler = (chunk) => {
|
|
3065
|
-
chunks.push(chunk);
|
|
3066
|
-
const raw = chunks.join('');
|
|
3067
|
-
const m = raw.match(/\x1b\[(\d+);\d+R/);
|
|
3068
|
-
if (m) {
|
|
3069
|
-
process.stdin.removeListener('data', handler);
|
|
3070
|
-
balBaseRow = parseInt(m[1], 10) + 1;
|
|
3071
|
-
resolve();
|
|
3072
|
-
}
|
|
3073
|
-
};
|
|
3074
|
-
process.stdin.on('data', handler);
|
|
3075
|
-
// Write to stderr so PTY doesn't echo the visible MARKER text to stdout
|
|
3076
|
-
process.stderr.write(MARKER);
|
|
3077
|
-
setTimeout(resolve, 50);
|
|
3078
|
-
});
|
|
3079
|
-
await balCaptureRow();
|
|
3080
|
-
|
|
3081
|
-
const balMoveToRow = (row) => process.stdout.write(`\x1b[${row};1H`);
|
|
3082
|
-
console.log(` ${'─'.repeat(balVis)}`);
|
|
3083
|
-
for (let i = 0; i < activeWorkers.length; i++) {
|
|
3084
|
-
const w = activeWorkers[i];
|
|
3085
|
-
const num = `${c.dim}${(i + 1).toString().padStart(bColNum - 1)}${c.reset}`;
|
|
3086
|
-
const name = (w.username || w.account.label || '?').substring(0, bColName).padEnd(bColName);
|
|
3087
|
-
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}`);
|
|
3088
|
-
}
|
|
3089
|
-
console.log(` ${'─'.repeat(balVis)}`);
|
|
3090
|
-
|
|
3091
|
-
let balDone = 0, balPending = activeWorkers.length;
|
|
3092
|
-
const drawBalProgress = () => {
|
|
3093
|
-
if (balPending === 0) return;
|
|
3094
|
-
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3095
|
-
const pct = activeWorkers.length > 0 ? ((activeWorkers.length - balPending) / activeWorkers.length) : 0;
|
|
3096
|
-
const barW = Math.min(20, startupTw - 40);
|
|
3097
|
-
const filled = Math.round(pct * barW);
|
|
3098
|
-
const bar = rgb(251, 191, 36) + '█'.repeat(filled) + rgb(50, 50, 70) + '░'.repeat(barW - filled) + c.reset;
|
|
3099
|
-
balMoveToRow(balBaseRow);
|
|
3100
|
-
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`);
|
|
3101
|
-
};
|
|
3102
|
-
const balSpinnerInterval = setInterval(drawBalProgress, 80);
|
|
3103
|
-
|
|
3104
|
-
await Promise.all(activeWorkers.map(async (w, i) => {
|
|
3105
|
-
try { await w.checkBalance(true); } catch {}
|
|
3106
|
-
balPending--;
|
|
3107
|
-
const num = `${c.dim}${(i + 1).toString().padStart(bColNum - 1)}${c.reset}`;
|
|
3108
|
-
const name = (w.username || w.account.label || '?').substring(0, bColName).padEnd(bColName);
|
|
3109
|
-
const wallet = w.stats?.balance || 0;
|
|
3110
|
-
const bank = w.stats?.bankBalance || 0;
|
|
3111
|
-
const ls = w._lifesavers ?? '?';
|
|
3112
|
-
const lsColor = ls === 0 ? rgb(239, 68, 68) : ls <= 2 ? rgb(251, 191, 36) : rgb(52, 211, 153);
|
|
3113
|
-
const walletStr = `${c.green}⏣${wallet.toLocaleString()}${c.reset}`;
|
|
3114
|
-
const bankStr = `${c.cyan}⏣${bank.toLocaleString()}${c.reset}`;
|
|
3115
|
-
const totalStr = `${c.bold}⏣${(wallet + bank).toLocaleString()}${c.reset}`;
|
|
3116
|
-
const row = balBaseRow + 1 + i;
|
|
3117
|
-
balMoveToRow(row);
|
|
3118
|
-
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`);
|
|
3119
|
-
balDone++;
|
|
3120
|
-
}));
|
|
2819
|
+
console.log('Inventory check complete');
|
|
3121
2820
|
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
let totalWallet = 0, totalBank = 0, noLifesaverAccounts = [];
|
|
2821
|
+
// ── Phase 2.5: Balance check ───────────────────────────────────
|
|
2822
|
+
console.log('Checking balances...');
|
|
3126
2823
|
for (const w of activeWorkers) {
|
|
3127
|
-
|
|
3128
|
-
totalBank += w.stats?.bankBalance || 0;
|
|
3129
|
-
if (w._lifesavers === 0) noLifesaverAccounts.push(w.username || w.account.label);
|
|
2824
|
+
try { await w.checkBalance(true); } catch {}
|
|
3130
2825
|
}
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
2826
|
+
let totalCoins = 0;
|
|
2827
|
+
for (const w of activeWorkers) {
|
|
2828
|
+
totalCoins += w.stats?.balance || 0;
|
|
2829
|
+
totalCoins += w.stats?.bankBalance || 0;
|
|
3134
2830
|
}
|
|
3135
|
-
console.log(
|
|
3136
|
-
|
|
2831
|
+
console.log(`Balances: total ${totalCoins.toLocaleString()} coins across ${activeWorkers.length} accounts`);
|
|
3137
2832
|
|
|
3138
|
-
// Phase 2.75:
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [], dmUnknown = [];
|
|
2833
|
+
// ── Phase 2.75: DM history check ────────────────────────────────
|
|
2834
|
+
console.log('Checking DM history...');
|
|
2835
|
+
let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [];
|
|
3142
2836
|
for (const w of activeWorkers) {
|
|
3143
2837
|
try {
|
|
3144
2838
|
const dm = await w.checkDmHistory();
|
|
3145
2839
|
if (dm.deaths > 0) dmDeaths += dm.deaths;
|
|
3146
2840
|
if (dm.levelUps > 0) dmLevelUps += dm.levelUps;
|
|
3147
2841
|
if (dm.lifesavers === 0) dmNoLs.push(w.username);
|
|
3148
|
-
if (dm.lifesavers === -1) dmUnknown.push(w.username);
|
|
3149
|
-
// Store level and lifesaver for dashboard
|
|
3150
2842
|
if (dm.currentLevel > 0) w._level = dm.currentLevel;
|
|
3151
2843
|
if (dm.lifesavers >= 0) w._lifesavers = dm.lifesavers;
|
|
3152
|
-
|
|
3153
|
-
if (dm.
|
|
3154
|
-
if (dm.deaths > 0) parts.push(`${rgb(239, 68, 68)}${dm.deaths} deaths${c.reset}`);
|
|
3155
|
-
if (dm.lifesavers >= 0) {
|
|
3156
|
-
const lc = dm.lifesavers === 0 ? rgb(239, 68, 68) : dm.lifesavers <= 2 ? rgb(251, 191, 36) : rgb(52, 211, 153);
|
|
3157
|
-
parts.push(`${lc}♥${dm.lifesavers}${c.reset}`);
|
|
3158
|
-
} else {
|
|
3159
|
-
// Unknown lifesavers — pulse to show pending
|
|
3160
|
-
const pulse = PULSE_CHARS[Math.floor(Date.now() / 400) % PULSE_CHARS.length];
|
|
3161
|
-
parts.push(`${D}${pulse}♥?${c.reset}`);
|
|
3162
|
-
}
|
|
3163
|
-
} catch {}
|
|
3164
|
-
}
|
|
3165
|
-
if (dmNoLs.length > 0) {
|
|
3166
|
-
log('warn', `⚠ No lifesavers: ${dmNoLs.join(', ')}`);
|
|
3167
|
-
// Set Redis keys to block crime/search
|
|
3168
|
-
for (const w of activeWorkers) {
|
|
3169
|
-
if (dmNoLs.includes(w.username) && redis) {
|
|
2844
|
+
// Block crime/search for accounts with 0 lifesavers
|
|
2845
|
+
if (dm.lifesavers === 0 && redis) {
|
|
3170
2846
|
try {
|
|
3171
2847
|
await redis.set(`dkg:lifesavers:${w.account.id}`, '0', 'EX', 86400);
|
|
3172
|
-
await redis.set(`raw:alert:no-lifesaver:${w.channel?.id}`, '1', 'EX', 86400);
|
|
3173
2848
|
} catch {}
|
|
3174
2849
|
}
|
|
3175
|
-
}
|
|
3176
|
-
}
|
|
3177
|
-
if (dmUnknown.length > 0) {
|
|
3178
|
-
log('warn', `⚠ Lifesavers unknown — live monitor: ${dmUnknown.join(', ')}`);
|
|
3179
|
-
// Crime/search on these accounts will be skipped via safety hold until the live
|
|
3180
|
-
// DM gateway listener detects a death (→ sets count) or confirms clean.
|
|
2850
|
+
} catch {}
|
|
3181
2851
|
}
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
if (
|
|
3185
|
-
if (
|
|
3186
|
-
console.log(`
|
|
3187
|
-
console.log('');
|
|
2852
|
+
if (dmNoLs.length > 0) console.log(` WARN - No lifesavers: ${dmNoLs.join(', ')}`);
|
|
2853
|
+
const parts = [];
|
|
2854
|
+
if (dmDeaths > 0) parts.push(`${dmDeaths} deaths`);
|
|
2855
|
+
if (dmLevelUps > 0) parts.push(`${dmLevelUps} level-ups`);
|
|
2856
|
+
console.log(`DM check: ${parts.length > 0 ? parts.join(', ') : 'clean'}`);
|
|
3188
2857
|
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
// Phase 3: Start all grind loops (only for valid workers)
|
|
2858
|
+
// ── Phase 3: Start grind loops ───────────────────────────────────
|
|
2859
|
+
console.log(`Starting ${activeWorkers.length} grind loops...`);
|
|
3192
2860
|
for (const w of activeWorkers) {
|
|
3193
2861
|
if (!shutdownCalled) w.grindLoop();
|
|
3194
2862
|
}
|
|
3195
|
-
|
|
3196
|
-
console.log(` ${rgb(52, 211, 153)}✓${c.reset} All grind loops started — ${activeWorkers.length} accounts active`);
|
|
3197
|
-
console.log(` v${PKG_VERSION} | press Ctrl+C to stop`);
|
|
2863
|
+
console.log(`All grind loops started. v${PKG_VERSION} | Ctrl+C to stop`);
|
|
3198
2864
|
|
|
3199
2865
|
// Cluster heartbeat — lets other nodes see this node is alive
|
|
3200
2866
|
if (CLUSTER_ENABLED) {
|
|
@@ -3263,31 +2929,23 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3263
2929
|
setDashboardActive(false);
|
|
3264
2930
|
process.stdout.write(c.show);
|
|
3265
2931
|
|
|
3266
|
-
const sepBar = rgb(139, 92, 246) + c.bold + '═'.repeat(tw) + c.reset;
|
|
3267
2932
|
console.log('');
|
|
3268
|
-
console.log(
|
|
3269
|
-
console.log(sepBar);
|
|
2933
|
+
console.log('Session Summary');
|
|
3270
2934
|
|
|
3271
2935
|
// Collect stats from all workers (including rotated-out ones)
|
|
3272
2936
|
let finalCoins = 0;
|
|
3273
2937
|
let finalCmds = 0;
|
|
3274
2938
|
for (const wk of workers) {
|
|
3275
2939
|
const rate = wk.stats.commands > 0 ? ((wk.stats.successes / wk.stats.commands) * 100).toFixed(0) : 0;
|
|
3276
|
-
console.log(
|
|
3277
|
-
` ${wk.color}${c.bold}${(wk.username || '?').padEnd(18)}${c.reset}` +
|
|
3278
|
-
` ${rgb(52, 211, 153)}+⏣ ${wk.stats.coins.toLocaleString().padStart(8)}${c.reset}` +
|
|
3279
|
-
` ${c.dim}${wk.stats.commands.toString().padStart(4)} cmds${c.reset}` +
|
|
3280
|
-
` ${c.dim}${rate}% success${c.reset}`
|
|
3281
|
-
);
|
|
2940
|
+
console.log(` ${(wk.username || '?').padEnd(18)} +${wk.stats.coins.toLocaleString().padStart(8)} coins ${wk.stats.commands} cmds ${rate}% ok`);
|
|
3282
2941
|
finalCoins += wk.stats.coins || 0;
|
|
3283
2942
|
finalCmds += wk.stats.commands || 0;
|
|
3284
2943
|
}
|
|
3285
|
-
console.log(sepBar);
|
|
3286
2944
|
|
|
3287
2945
|
const memFinal = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
3288
2946
|
const avgEarn = globalEarningsEMA.get();
|
|
3289
2947
|
const cpm = globalCmdRate.getRate().toFixed(1);
|
|
3290
|
-
console.log(`
|
|
2948
|
+
console.log(`Total: +${finalCoins.toLocaleString()} coins in ${formatUptime()} | ${finalCmds} cmds | ~${cpm} cmd/m | ${memFinal}MB`);
|
|
3291
2949
|
console.log('');
|
|
3292
2950
|
|
|
3293
2951
|
// Release all cluster claims before stopping workers
|