dankgrinder 7.73.0 → 7.75.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 +38 -308
- package/lib/rawLogger.js +8 -1
- 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: '╯',
|
|
@@ -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 {}
|
|
@@ -2765,8 +2760,6 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2765
2760
|
` ${c.dim}·${c.reset} ${rgb(52, 211, 153)}Auto-Recovery${c.reset}` +
|
|
2766
2761
|
` ${c.dim}·${c.reset} ${rgb(251, 191, 36)}Loss Limiter${c.reset}`
|
|
2767
2762
|
);
|
|
2768
|
-
console.log(bar);
|
|
2769
|
-
|
|
2770
2763
|
log('info', `${c.dim}Fetching accounts...${c.reset}`);
|
|
2771
2764
|
|
|
2772
2765
|
const fetchOpts = CLOUD_MODE ? { cloud: true } : {};
|
|
@@ -2831,7 +2824,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2831
2824
|
|
|
2832
2825
|
// Init rawLogger Redis (uses same URL — logs all raw gateway data)
|
|
2833
2826
|
if (REDIS_URL) {
|
|
2834
|
-
rawLogger.init(
|
|
2827
|
+
rawLogger.init(redis);
|
|
2835
2828
|
// Listen for DM events across all accounts — update worker state + dashboard LIVE
|
|
2836
2829
|
rawLogger.onDmEvent((event, raw) => {
|
|
2837
2830
|
const channelId = raw.channel_id;
|
|
@@ -2880,110 +2873,8 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2880
2873
|
console.log(` ${checks.join(' ')}`);
|
|
2881
2874
|
console.log('');
|
|
2882
2875
|
|
|
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
|
-
|
|
2876
|
+
// ── Phase 1: Login accounts ─────────────────────────────────────────
|
|
2877
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} Logging in ${accounts.length} account(s)...`);
|
|
2987
2878
|
const parsedGapMin = Number.parseInt(String(process.env.LOGIN_GAP_MIN_MS || '50'), 10);
|
|
2988
2879
|
const parsedGapMax = Number.parseInt(String(process.env.LOGIN_GAP_MAX_MS || '150'), 10);
|
|
2989
2880
|
const LOGIN_GAP_MIN_MS = Number.isFinite(parsedGapMin) && parsedGapMin >= 0 ? parsedGapMin : 50;
|
|
@@ -2999,203 +2890,58 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2999
2890
|
const worker = new AccountWorker(acc, i + idx);
|
|
3000
2891
|
workers.push(worker);
|
|
3001
2892
|
workerMap.set(acc.id, worker);
|
|
3002
|
-
loginStates[i + idx].worker = worker;
|
|
3003
2893
|
await worker.start();
|
|
3004
|
-
finalizeLoginLine(i + idx, worker);
|
|
3005
2894
|
}));
|
|
3006
2895
|
if (i + BATCH_SIZE < accounts.length) await new Promise(r => setTimeout(r, randomLoginGap()));
|
|
3007
2896
|
hintGC();
|
|
3008
2897
|
}
|
|
3009
2898
|
|
|
3010
|
-
clearInterval(loginSpinnerInterval);
|
|
3011
2899
|
const loginDone = workers.filter(w => !w._tokenInvalid && w.channel).length;
|
|
3012
2900
|
const invalidWorkers = workers.filter(w => w._tokenInvalid);
|
|
3013
2901
|
const timedOutWorkers = workers.filter(w => !w.channel && !w._tokenInvalid);
|
|
3014
|
-
console.log(
|
|
3015
|
-
console.log('');
|
|
2902
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${loginDone}/${accounts.length} accounts connected`);
|
|
3016
2903
|
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('');
|
|
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`);
|
|
3020
2906
|
}
|
|
3021
|
-
if (timedOutWorkers.length > 0) log('warn', `${timedOutWorkers.length} account(s) timed out during login
|
|
2907
|
+
if (timedOutWorkers.length > 0) log('warn', `${timedOutWorkers.length} account(s) timed out during login`);
|
|
3022
2908
|
|
|
3023
2909
|
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
3024
2910
|
|
|
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++;
|
|
2911
|
+
// ── Phase 2: Inventory check ─────────────────────────────────────
|
|
2912
|
+
console.log(` ${rgb(34, 211, 238)}·${c.reset} Checking inventory (${activeWorkers.length} accounts)...`);
|
|
2913
|
+
let invDone = 0, invFailed = 0;
|
|
2914
|
+
await Promise.all(activeWorkers.map(async (w) => {
|
|
2915
|
+
try { await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3, silent: true }); }
|
|
2916
|
+
catch { invFailed++; return; }
|
|
2917
|
+
invDone++;
|
|
3095
2918
|
}));
|
|
3096
2919
|
|
|
3097
|
-
clearInterval(invSpinnerInterval);
|
|
3098
|
-
process.stdout.write(`\r\x1b[2K`);
|
|
3099
|
-
|
|
3100
2920
|
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}`);
|
|
2921
|
+
console.log(` ${rgb(239, 68, 68)}✗${c.reset} Inventory: ${invFailed} failed — not starting grind loops`);
|
|
3103
2922
|
return;
|
|
3104
2923
|
}
|
|
3105
|
-
console.log(` ${rgb(52, 211, 153)}✓${c.reset}
|
|
3106
|
-
console.log('');
|
|
3107
|
-
|
|
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);
|
|
2924
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} Inventory: ${invDone}/${activeWorkers.length} clear`);
|
|
3161
2925
|
|
|
3162
|
-
|
|
2926
|
+
// ── Phase 2.5: Balance check ────────────────────────────────────
|
|
2927
|
+
console.log(` ${rgb(251, 191, 36)}·${c.reset} Checking balance (${activeWorkers.length} accounts)...`);
|
|
2928
|
+
await Promise.all(activeWorkers.map(async (w) => {
|
|
3163
2929
|
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
2930
|
}));
|
|
3179
2931
|
|
|
3180
|
-
clearInterval(balSpinnerInterval);
|
|
3181
|
-
process.stdout.write(`\r\x1b[2K`);
|
|
3182
|
-
|
|
3183
2932
|
let totalWallet = 0, totalBank = 0, noLifesaverAccounts = [];
|
|
3184
2933
|
for (const w of activeWorkers) {
|
|
3185
2934
|
totalWallet += w.stats?.balance || 0;
|
|
3186
2935
|
totalBank += w.stats?.bankBalance || 0;
|
|
3187
2936
|
if (w._lifesavers === 0) noLifesaverAccounts.push(w.username || w.account.label);
|
|
3188
2937
|
}
|
|
3189
|
-
console.log(` ${rgb(52, 211, 153)}✓${c.reset}
|
|
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}`);
|
|
3190
2939
|
if (noLifesaverAccounts.length > 0) {
|
|
3191
|
-
console.log(` ${rgb(239, 68, 68)}⚠${c.reset} ${
|
|
2940
|
+
console.log(` ${rgb(239, 68, 68)}⚠${c.reset} ${noLifesaverAccounts.length} account(s) have 0 LIFESAVERS — crime/search disabled`);
|
|
3192
2941
|
}
|
|
3193
|
-
console.log('');
|
|
3194
|
-
|
|
3195
2942
|
|
|
3196
|
-
// Phase 2.75:
|
|
3197
|
-
|
|
3198
|
-
console.log(` ${rgb(139, 92, 246)}${dmCheckPulse}${c.reset} ${c.dim}Checking DM history...${c.reset}`);
|
|
2943
|
+
// ── Phase 2.75: DM history check ────────────────────────────────
|
|
2944
|
+
console.log(` ${rgb(139, 92, 246)}·${c.reset} Checking DM history...`);
|
|
3199
2945
|
let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [], dmUnknown = [];
|
|
3200
2946
|
for (const w of activeWorkers) {
|
|
3201
2947
|
try {
|
|
@@ -3204,25 +2950,12 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3204
2950
|
if (dm.levelUps > 0) dmLevelUps += dm.levelUps;
|
|
3205
2951
|
if (dm.lifesavers === 0) dmNoLs.push(w.username);
|
|
3206
2952
|
if (dm.lifesavers === -1) dmUnknown.push(w.username);
|
|
3207
|
-
// Store level and lifesaver for dashboard
|
|
3208
2953
|
if (dm.currentLevel > 0) w._level = dm.currentLevel;
|
|
3209
2954
|
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
2955
|
} catch {}
|
|
3222
2956
|
}
|
|
3223
2957
|
if (dmNoLs.length > 0) {
|
|
3224
2958
|
log('warn', `⚠ No lifesavers: ${dmNoLs.join(', ')}`);
|
|
3225
|
-
// Set Redis keys to block crime/search
|
|
3226
2959
|
for (const w of activeWorkers) {
|
|
3227
2960
|
if (dmNoLs.includes(w.username) && redis) {
|
|
3228
2961
|
try {
|
|
@@ -3234,18 +2967,15 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3234
2967
|
}
|
|
3235
2968
|
if (dmUnknown.length > 0) {
|
|
3236
2969
|
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
2970
|
}
|
|
3240
2971
|
const dmSummaryParts = [];
|
|
3241
2972
|
if (dmDeaths > 0) dmSummaryParts.push(`${dmDeaths} deaths`);
|
|
3242
2973
|
if (dmLevelUps > 0) dmSummaryParts.push(`${dmLevelUps} level-ups`);
|
|
3243
2974
|
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])}`);
|
|
2975
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} DM check: ${dmSummaryParts.length > 0 ? dmSummaryParts.join(', ') : 'clean'}`);
|
|
3248
2976
|
|
|
2977
|
+
// ── Phase 3: Start grind loops ───────────────────────────────────
|
|
2978
|
+
console.log(` ${rgb(139, 92, 246)}>>>${c.reset} Starting grind loops...`);
|
|
3249
2979
|
// Phase 3: Start all grind loops (only for valid workers)
|
|
3250
2980
|
for (const w of activeWorkers) {
|
|
3251
2981
|
if (!shutdownCalled) w.grindLoop();
|
package/lib/rawLogger.js
CHANGED
|
@@ -30,7 +30,14 @@ const memRing = [];
|
|
|
30
30
|
let memIdx = 0;
|
|
31
31
|
|
|
32
32
|
// ── Redis init ──
|
|
33
|
-
async function init(
|
|
33
|
+
async function init(redisUrlOrInstance) {
|
|
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;
|
|
34
41
|
if (!redisUrl) {
|
|
35
42
|
console.log('[rawLogger] No Redis URL — raw logging disabled');
|
|
36
43
|
return;
|