dankgrinder 8.2.0 → 8.10.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 +319 -49
- package/lib/rawLogger.js +1 -8
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -94,20 +94,10 @@ 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@@';
|
|
97
99
|
const DANK_MEMER_ID = '270904126974590976';
|
|
98
100
|
|
|
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
|
-
}
|
|
111
101
|
// ── Safe options for search/crime ──────────────────────────
|
|
112
102
|
// Object.freeze → V8 marks these as immutable, enabling inline caching
|
|
113
103
|
// and preventing accidental mutation across 10K worker instances.
|
|
@@ -282,6 +272,17 @@ function progressBar(value, max, width, filledColor, emptyColor) {
|
|
|
282
272
|
return rgb(fc[0], fc[1], fc[2]) + '█'.repeat(filled) + rgb(ec[0], ec[1], ec[2]) + '░'.repeat(empty) + c.reset;
|
|
283
273
|
}
|
|
284
274
|
|
|
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
|
+
|
|
285
286
|
// ── Box drawing helpers ──────────────────────────────────────
|
|
286
287
|
const BOX = {
|
|
287
288
|
tl: '╭', tr: '╮', bl: '╰', br: '╯',
|
|
@@ -1388,7 +1389,9 @@ class AccountWorker {
|
|
|
1388
1389
|
|
|
1389
1390
|
// Update Redis with findings
|
|
1390
1391
|
if (redis) {
|
|
1391
|
-
|
|
1392
|
+
if (currentLevel > 0) {
|
|
1393
|
+
await redis.set(`dkg:level:${this.account.id}`, String(currentLevel), 'EX', 2592000);
|
|
1394
|
+
this._level = currentLevel;
|
|
1392
1395
|
if (currentLevel > 0) {
|
|
1393
1396
|
await redis.set(`dkg:level:${this.account.id}`, String(currentLevel), 'EX', 2592000);
|
|
1394
1397
|
this._level = currentLevel;
|
|
@@ -1401,19 +1404,17 @@ class AccountWorker {
|
|
|
1401
1404
|
await redis.set(`raw:alert:no-lifesaver:${this.channel?.id}`, '1', 'EX', 86400);
|
|
1402
1405
|
}
|
|
1403
1406
|
}
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
await new Promise(r => setTimeout(r, delays[attempt]));
|
|
1407
|
+
}
|
|
1408
|
+
return { deaths, levelUps, currentLevel, lifesavers: lastLifesaverCount, dmChannelId: dm.id };
|
|
1409
|
+
} catch (e) {
|
|
1410
|
+
lastError = e;
|
|
1411
|
+
if (attempt < maxRetries - 1) {
|
|
1412
|
+
await new Promise(r => setTimeout(r, delays[attempt]));
|
|
1413
|
+
}
|
|
1412
1414
|
}
|
|
1413
1415
|
}
|
|
1416
|
+
return { deaths: 0, levelUps: 0, currentLevel: 0, lifesavers: -1 };
|
|
1414
1417
|
}
|
|
1415
|
-
return { deaths: 0, levelUps: 0, currentLevel: 0, lifesavers: -1 };
|
|
1416
|
-
}
|
|
1417
1418
|
|
|
1418
1419
|
// ── Run Single Command ──────────────────────────────────────
|
|
1419
1420
|
// Each modular command handler sends the command, waits for response,
|
|
@@ -2748,6 +2749,10 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2748
2749
|
REDIS_URL = process.env.REDIS_URL || '';
|
|
2749
2750
|
WEBHOOK_URL = process.env.WEBHOOK_URL || '';
|
|
2750
2751
|
|
|
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
|
+
|
|
2751
2756
|
// Detect zlib-sync availability
|
|
2752
2757
|
let hasZlib = false;
|
|
2753
2758
|
try { require('zlib-sync'); hasZlib = true; } catch {}
|
|
@@ -2760,6 +2765,8 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2760
2765
|
` ${c.dim}·${c.reset} ${rgb(52, 211, 153)}Auto-Recovery${c.reset}` +
|
|
2761
2766
|
` ${c.dim}·${c.reset} ${rgb(251, 191, 36)}Loss Limiter${c.reset}`
|
|
2762
2767
|
);
|
|
2768
|
+
console.log(bar);
|
|
2769
|
+
|
|
2763
2770
|
log('info', `${c.dim}Fetching accounts...${c.reset}`);
|
|
2764
2771
|
|
|
2765
2772
|
const fetchOpts = CLOUD_MODE ? { cloud: true } : {};
|
|
@@ -2824,7 +2831,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2824
2831
|
|
|
2825
2832
|
// Init rawLogger Redis (uses same URL — logs all raw gateway data)
|
|
2826
2833
|
if (REDIS_URL) {
|
|
2827
|
-
rawLogger.init(
|
|
2834
|
+
rawLogger.init(REDIS_URL).catch(() => {});
|
|
2828
2835
|
// Listen for DM events across all accounts — update worker state + dashboard LIVE
|
|
2829
2836
|
rawLogger.onDmEvent((event, raw) => {
|
|
2830
2837
|
const channelId = raw.channel_id;
|
|
@@ -2873,8 +2880,110 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2873
2880
|
console.log(` ${checks.join(' ')}`);
|
|
2874
2881
|
console.log('');
|
|
2875
2882
|
|
|
2876
|
-
// ── Phase 1: Login
|
|
2877
|
-
|
|
2883
|
+
// ── Phase 1: Login with per-account inline rendering ─────────────────────────
|
|
2884
|
+
const startupTw = process.stdout.columns || 90;
|
|
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
2987
|
const parsedGapMin = Number.parseInt(String(process.env.LOGIN_GAP_MIN_MS || '50'), 10);
|
|
2879
2988
|
const parsedGapMax = Number.parseInt(String(process.env.LOGIN_GAP_MAX_MS || '150'), 10);
|
|
2880
2989
|
const LOGIN_GAP_MIN_MS = Number.isFinite(parsedGapMin) && parsedGapMin >= 0 ? parsedGapMin : 50;
|
|
@@ -2890,58 +2999,203 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2890
2999
|
const worker = new AccountWorker(acc, i + idx);
|
|
2891
3000
|
workers.push(worker);
|
|
2892
3001
|
workerMap.set(acc.id, worker);
|
|
3002
|
+
loginStates[i + idx].worker = worker;
|
|
2893
3003
|
await worker.start();
|
|
3004
|
+
finalizeLoginLine(i + idx, worker);
|
|
2894
3005
|
}));
|
|
2895
3006
|
if (i + BATCH_SIZE < accounts.length) await new Promise(r => setTimeout(r, randomLoginGap()));
|
|
2896
3007
|
hintGC();
|
|
2897
3008
|
}
|
|
2898
3009
|
|
|
3010
|
+
clearInterval(loginSpinnerInterval);
|
|
2899
3011
|
const loginDone = workers.filter(w => !w._tokenInvalid && w.channel).length;
|
|
2900
3012
|
const invalidWorkers = workers.filter(w => w._tokenInvalid);
|
|
2901
3013
|
const timedOutWorkers = workers.filter(w => !w.channel && !w._tokenInvalid);
|
|
2902
|
-
console.log(
|
|
3014
|
+
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}`);
|
|
3015
|
+
console.log('');
|
|
2903
3016
|
if (invalidWorkers.length > 0) {
|
|
2904
|
-
log('warn', `${rgb(239, 68, 68)}${invalidWorkers.length} account(s) have INVALID tokens`);
|
|
2905
|
-
for (const w of invalidWorkers) log('error', ` ${w.account.label || w.account.id} — token invalid or expired`);
|
|
3017
|
+
log('warn', `${rgb(239, 68, 68)}${invalidWorkers.length} account(s) have INVALID tokens:${c.reset}`);
|
|
3018
|
+
for (const w of invalidWorkers) log('error', ` ✗ ${w.account.label || w.account.id} — token is invalid or expired`);
|
|
3019
|
+
console.log('');
|
|
2906
3020
|
}
|
|
2907
|
-
if (timedOutWorkers.length > 0) log('warn', `${timedOutWorkers.length} account(s) timed out during login`);
|
|
3021
|
+
if (timedOutWorkers.length > 0) log('warn', `${timedOutWorkers.length} account(s) timed out during login (will retry in background)`);
|
|
2908
3022
|
|
|
2909
3023
|
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
2910
3024
|
|
|
2911
|
-
// ── Phase 2: Inventory check
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
3025
|
+
// ── Phase 2: Inventory check — spinner for pending count, results inline ─────────
|
|
3026
|
+
const iColNum = 4;
|
|
3027
|
+
const iColName = Math.min(22, Math.max(12, Math.floor(startupTw * 0.22)));
|
|
3028
|
+
const iColItems = 8;
|
|
3029
|
+
const iColVal = 16;
|
|
3030
|
+
const invVis = 7 + iColNum + iColName + iColItems + iColVal + 12;
|
|
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++;
|
|
2918
3095
|
}));
|
|
2919
3096
|
|
|
3097
|
+
clearInterval(invSpinnerInterval);
|
|
3098
|
+
process.stdout.write(`\r\x1b[2K`);
|
|
3099
|
+
|
|
2920
3100
|
if (invFailed > 0) {
|
|
2921
|
-
console.log(` ${rgb(239, 68, 68)}✗${c.reset} Inventory
|
|
3101
|
+
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}`);
|
|
3102
|
+
log('error', `${c.red}Not starting grind loops — ${invFailed} accounts failed inventory.${c.reset}`);
|
|
2922
3103
|
return;
|
|
2923
3104
|
}
|
|
2924
|
-
console.log(` ${rgb(52, 211, 153)}✓${c.reset} Inventory
|
|
3105
|
+
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}`);
|
|
3106
|
+
console.log('');
|
|
2925
3107
|
|
|
2926
|
-
// ── Phase 2.5: Balance check
|
|
2927
|
-
|
|
2928
|
-
|
|
3108
|
+
// ── Phase 2.5: Balance check — inline table, single spinner for progress ─────────
|
|
3109
|
+
const bColNum = 4;
|
|
3110
|
+
const bColName = Math.min(22, Math.max(12, Math.floor(startupTw * 0.22)));
|
|
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) => {
|
|
2929
3163
|
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++;
|
|
2930
3178
|
}));
|
|
2931
3179
|
|
|
3180
|
+
clearInterval(balSpinnerInterval);
|
|
3181
|
+
process.stdout.write(`\r\x1b[2K`);
|
|
3182
|
+
|
|
2932
3183
|
let totalWallet = 0, totalBank = 0, noLifesaverAccounts = [];
|
|
2933
3184
|
for (const w of activeWorkers) {
|
|
2934
3185
|
totalWallet += w.stats?.balance || 0;
|
|
2935
3186
|
totalBank += w.stats?.bankBalance || 0;
|
|
2936
3187
|
if (w._lifesavers === 0) noLifesaverAccounts.push(w.username || w.account.label);
|
|
2937
3188
|
}
|
|
2938
|
-
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}`);
|
|
3189
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}Balance${c.reset} Total: ${c.bold}${c.green}⏣ ${(totalWallet + totalBank).toLocaleString()}${c.reset} ${c.dim}(wallet: ⏣ ${totalWallet.toLocaleString()} + bank: ⏣ ${totalBank.toLocaleString()})${c.reset}`);
|
|
2939
3190
|
if (noLifesaverAccounts.length > 0) {
|
|
2940
|
-
console.log(` ${rgb(239, 68, 68)}⚠${c.reset} ${noLifesaverAccounts.length} account(s) have 0 LIFESAVERS
|
|
3191
|
+
console.log(` ${rgb(239, 68, 68)}⚠${c.reset} ${c.bold}${c.red}WARNING: ${noLifesaverAccounts.length} account(s) have 0 LIFESAVERS!${c.reset} Crime/Search disabled for: ${noLifesaverAccounts.join(', ')}`);
|
|
2941
3192
|
}
|
|
3193
|
+
console.log('');
|
|
2942
3194
|
|
|
2943
|
-
|
|
2944
|
-
|
|
3195
|
+
|
|
3196
|
+
// Phase 2.75: Check DM history for deaths/level-ups (sequential, fast)
|
|
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
3199
|
let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [], dmUnknown = [];
|
|
2946
3200
|
for (const w of activeWorkers) {
|
|
2947
3201
|
try {
|
|
@@ -2950,12 +3204,25 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2950
3204
|
if (dm.levelUps > 0) dmLevelUps += dm.levelUps;
|
|
2951
3205
|
if (dm.lifesavers === 0) dmNoLs.push(w.username);
|
|
2952
3206
|
if (dm.lifesavers === -1) dmUnknown.push(w.username);
|
|
3207
|
+
// Store level and lifesaver for dashboard
|
|
2953
3208
|
if (dm.currentLevel > 0) w._level = dm.currentLevel;
|
|
2954
3209
|
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
|
+
}
|
|
2955
3221
|
} catch {}
|
|
2956
3222
|
}
|
|
2957
3223
|
if (dmNoLs.length > 0) {
|
|
2958
3224
|
log('warn', `⚠ No lifesavers: ${dmNoLs.join(', ')}`);
|
|
3225
|
+
// Set Redis keys to block crime/search
|
|
2959
3226
|
for (const w of activeWorkers) {
|
|
2960
3227
|
if (dmNoLs.includes(w.username) && redis) {
|
|
2961
3228
|
try {
|
|
@@ -2967,15 +3234,18 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2967
3234
|
}
|
|
2968
3235
|
if (dmUnknown.length > 0) {
|
|
2969
3236
|
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.
|
|
2970
3239
|
}
|
|
2971
3240
|
const dmSummaryParts = [];
|
|
2972
3241
|
if (dmDeaths > 0) dmSummaryParts.push(`${dmDeaths} deaths`);
|
|
2973
3242
|
if (dmLevelUps > 0) dmSummaryParts.push(`${dmLevelUps} level-ups`);
|
|
2974
3243
|
if (dmUnknown.length > 0) dmSummaryParts.push(`${dmUnknown.length} pending`);
|
|
2975
|
-
console.log(` ${rgb(52, 211, 153)}✓${c.reset} DM check: ${dmSummaryParts.length > 0 ? dmSummaryParts.join(', ') : 'clean'}`);
|
|
3244
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} DM check: ${dmSummaryParts.length > 0 ? dmSummaryParts.join(', ') : 'clean — no deaths or level-ups'}`);
|
|
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])}`);
|
|
2976
3248
|
|
|
2977
|
-
// ── Phase 3: Start grind loops ───────────────────────────────────
|
|
2978
|
-
console.log(` ${rgb(139, 92, 246)}>>>${c.reset} Starting grind loops...`);
|
|
2979
3249
|
// Phase 3: Start all grind loops (only for valid workers)
|
|
2980
3250
|
for (const w of activeWorkers) {
|
|
2981
3251
|
if (!shutdownCalled) w.grindLoop();
|
package/lib/rawLogger.js
CHANGED
|
@@ -30,14 +30,7 @@ const memRing = [];
|
|
|
30
30
|
let memIdx = 0;
|
|
31
31
|
|
|
32
32
|
// ── Redis init ──
|
|
33
|
-
async function init(
|
|
34
|
-
// Support passing an existing Redis instance directly (preferred)
|
|
35
|
-
if (redisUrlOrInstance && typeof redisUrlOrInstance.status !== 'undefined') {
|
|
36
|
-
redis = redisUrlOrInstance;
|
|
37
|
-
redisReady = redis.status === 'ready';
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const redisUrl = redisUrlOrInstance;
|
|
33
|
+
async function init(redisUrl) {
|
|
41
34
|
if (!redisUrl) {
|
|
42
35
|
console.log('[rawLogger] No Redis URL — raw logging disabled');
|
|
43
36
|
return;
|