dankgrinder 4.0.0 → 4.1.1
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/README.md +25 -16
- package/bin/dankgrinder.js +69 -32
- package/lib/commands/adventure.js +502 -0
- package/lib/commands/beg.js +45 -0
- package/lib/commands/blackjack.js +85 -0
- package/lib/commands/crime.js +94 -0
- package/lib/commands/deposit.js +46 -0
- package/lib/commands/dig.js +82 -0
- package/lib/commands/fish.js +615 -0
- package/lib/commands/fishVision.js +141 -0
- package/lib/commands/gamble.js +96 -0
- package/lib/commands/generic.js +181 -0
- package/lib/commands/highlow.js +112 -0
- package/lib/commands/hunt.js +85 -0
- package/lib/commands/index.js +59 -0
- package/lib/commands/postmemes.js +148 -0
- package/lib/commands/profile.js +99 -0
- package/lib/commands/scratch.js +83 -0
- package/lib/commands/search.js +102 -0
- package/lib/commands/shop.js +262 -0
- package/lib/commands/trivia.js +146 -0
- package/lib/commands/utils.js +287 -0
- package/lib/commands/work.js +400 -0
- package/lib/grinder.js +560 -656
- package/package.json +4 -3
package/lib/grinder.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { Client } = require('discord.js-selfbot-v13');
|
|
2
2
|
const Redis = require('ioredis');
|
|
3
|
+
const commands = require('./commands');
|
|
3
4
|
|
|
4
5
|
// ── Terminal Colors & ANSI ───────────────────────────────────
|
|
5
6
|
const c = {
|
|
@@ -36,12 +37,12 @@ const SAFE_CRIME_OPTIONS = [
|
|
|
36
37
|
|
|
37
38
|
let API_KEY = '';
|
|
38
39
|
let API_URL = '';
|
|
39
|
-
let REDIS_URL = process.env.REDIS_URL || '
|
|
40
|
+
let REDIS_URL = process.env.REDIS_URL || '';
|
|
40
41
|
let redis = null;
|
|
41
42
|
const workers = [];
|
|
42
43
|
|
|
43
44
|
function initRedis() {
|
|
44
|
-
if (!redis) {
|
|
45
|
+
if (!redis && REDIS_URL) {
|
|
45
46
|
try {
|
|
46
47
|
redis = new Redis(REDIS_URL, { maxRetriesPerRequest: null, lazyConnect: true });
|
|
47
48
|
redis.connect().catch(() => {});
|
|
@@ -51,37 +52,78 @@ function initRedis() {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
// ── Truecolor gradient helpers ───────────────────────────────
|
|
56
|
+
function rgb(r, g, b) { return `\x1b[38;2;${r};${g};${b}m`; }
|
|
57
|
+
function bgRgb(r, g, b) { return `\x1b[48;2;${r};${g};${b}m`; }
|
|
58
|
+
function lerp(a, b, t) { return Math.round(a + (b - a) * t); }
|
|
59
|
+
|
|
60
|
+
function gradientLine(text, from, to) {
|
|
61
|
+
const chars = [...text];
|
|
62
|
+
const visible = chars.filter(ch => ch !== ' ').length;
|
|
63
|
+
let ci = 0, out = '';
|
|
64
|
+
for (const ch of chars) {
|
|
65
|
+
if (ch === ' ') { out += ' '; continue; }
|
|
66
|
+
const t = visible > 1 ? ci / (visible - 1) : 0;
|
|
67
|
+
out += rgb(lerp(from[0], to[0], t), lerp(from[1], to[1], t), lerp(from[2], to[2], t)) + ch;
|
|
68
|
+
ci++;
|
|
69
|
+
}
|
|
70
|
+
return out + c.reset;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const BANNER_RAW = [
|
|
74
|
+
' ██████╗ █████╗ ███╗ ██╗██╗ ██╗',
|
|
75
|
+
' ██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝',
|
|
76
|
+
' ██║ ██║███████║██╔██╗ ██║█████╔╝ ',
|
|
77
|
+
' ██║ ██║██╔══██║██║╚██╗██║██╔═██╗ ',
|
|
78
|
+
' ██████╔╝██║ ██║██║ ╚████║██║ ██╗',
|
|
79
|
+
' ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝',
|
|
80
|
+
' ██████╗ ██████╗ ██╗███╗ ██╗██████╗ ███████╗██████╗ ',
|
|
81
|
+
' ██╔════╝ ██╔══██╗██║████╗ ██║██╔══██╗██╔════╝██╔══██╗',
|
|
82
|
+
' ██║ ███╗██████╔╝██║██╔██╗ ██║██║ ██║█████╗ ██████╔╝',
|
|
83
|
+
' ██║ ██║██╔══██╗██║██║╚██╗██║██║ ██║██╔══╝ ██╔══██╗',
|
|
84
|
+
' ╚██████╔╝██║ ██║██║██║ ╚████║██████╔╝███████╗██║ ██║',
|
|
85
|
+
' ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝',
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
function colorBanner() {
|
|
89
|
+
const topColor = [192, 132, 252];
|
|
90
|
+
const midColor = [139, 92, 246];
|
|
91
|
+
const botColor = [34, 211, 238];
|
|
92
|
+
const n = BANNER_RAW.length;
|
|
93
|
+
let out = '\n';
|
|
94
|
+
for (let i = 0; i < n; i++) {
|
|
95
|
+
const t = i / (n - 1);
|
|
96
|
+
const from = t < 0.5
|
|
97
|
+
? [lerp(topColor[0], midColor[0], t * 2), lerp(topColor[1], midColor[1], t * 2), lerp(topColor[2], midColor[2], t * 2)]
|
|
98
|
+
: [lerp(midColor[0], botColor[0], (t - 0.5) * 2), lerp(midColor[1], botColor[1], (t - 0.5) * 2), lerp(midColor[2], botColor[2], (t - 0.5) * 2)];
|
|
99
|
+
const to = t < 0.5
|
|
100
|
+
? [lerp(236, 168, t * 2), lerp(72, 85, t * 2), lerp(153, 247, t * 2)]
|
|
101
|
+
: [lerp(168, 6, (t - 0.5) * 2), lerp(85, 182, (t - 0.5) * 2), lerp(247, 212, (t - 0.5) * 2)];
|
|
102
|
+
out += c.bold + gradientLine(BANNER_RAW[i], from, to) + '\n';
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
54
107
|
// ── Live Dashboard State ─────────────────────────────────────
|
|
55
108
|
let dashboardLines = 0;
|
|
56
109
|
let dashboardStarted = false;
|
|
110
|
+
let dashboardRendering = false;
|
|
57
111
|
let totalBalance = 0;
|
|
58
112
|
let totalCoins = 0;
|
|
59
113
|
let totalCommands = 0;
|
|
60
114
|
let startTime = Date.now();
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
const BANNER = `
|
|
65
|
-
${c.magenta}${c.bold} ██████╗ █████╗ ███╗ ██╗██╗ ██╗${c.reset}
|
|
66
|
-
${c.magenta}${c.bold} ██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝${c.reset}
|
|
67
|
-
${c.magenta}${c.bold} ██║ ██║███████║██╔██╗ ██║█████╔╝ ${c.reset}
|
|
68
|
-
${c.magenta}${c.bold} ██║ ██║██╔══██║██║╚██╗██║██╔═██╗ ${c.reset}
|
|
69
|
-
${c.magenta}${c.bold} ██████╔╝██║ ██║██║ ╚████║██║ ██╗${c.reset}
|
|
70
|
-
${c.magenta}${c.bold} ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝${c.reset}
|
|
71
|
-
${c.cyan}${c.bold} ██████╗ ██████╗ ██╗███╗ ██╗██████╗ ███████╗██████╗${c.reset}
|
|
72
|
-
${c.cyan}${c.bold} ██╔════╝ ██╔══██╗██║████╗ ██║██╔══██╗██╔════╝██╔══██╗${c.reset}
|
|
73
|
-
${c.cyan}${c.bold} ██║ ███╗██████╔╝██║██╔██╗ ██║██║ ██║█████╗ ██████╔╝${c.reset}
|
|
74
|
-
${c.cyan}${c.bold} ██║ ██║██╔══██╗██║██║╚██╗██║██║ ██║██╔══╝ ██╔══██╗${c.reset}
|
|
75
|
-
${c.cyan}${c.bold} ╚██████╔╝██║ ██║██║██║ ╚████║██████╔╝███████╗██║ ██║${c.reset}
|
|
76
|
-
${c.cyan}${c.bold} ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝${c.reset}
|
|
77
|
-
`;
|
|
115
|
+
let shutdownCalled = false;
|
|
116
|
+
const recentLogs = [];
|
|
117
|
+
const MAX_LOGS = 4;
|
|
78
118
|
|
|
79
119
|
function formatUptime() {
|
|
80
120
|
const s = Math.floor((Date.now() - startTime) / 1000);
|
|
81
121
|
const h = Math.floor(s / 3600);
|
|
82
122
|
const m = Math.floor((s % 3600) / 60);
|
|
83
123
|
const sec = s % 60;
|
|
84
|
-
return `${h}h ${m}m
|
|
124
|
+
if (h > 0) return `${h}h ${m}m`;
|
|
125
|
+
if (m > 0) return `${m}m ${sec}s`;
|
|
126
|
+
return `${sec}s`;
|
|
85
127
|
}
|
|
86
128
|
|
|
87
129
|
function formatCoins(n) {
|
|
@@ -91,9 +133,9 @@ function formatCoins(n) {
|
|
|
91
133
|
}
|
|
92
134
|
|
|
93
135
|
function renderDashboard() {
|
|
94
|
-
if (!dashboardStarted || workers.length === 0) return;
|
|
136
|
+
if (!dashboardStarted || workers.length === 0 || dashboardRendering) return;
|
|
137
|
+
dashboardRendering = true;
|
|
95
138
|
|
|
96
|
-
// Compute totals
|
|
97
139
|
totalBalance = 0; totalCoins = 0; totalCommands = 0;
|
|
98
140
|
for (const w of workers) {
|
|
99
141
|
totalBalance += w.stats.balance || 0;
|
|
@@ -102,42 +144,49 @@ function renderDashboard() {
|
|
|
102
144
|
}
|
|
103
145
|
|
|
104
146
|
const lines = [];
|
|
105
|
-
const
|
|
106
|
-
const
|
|
147
|
+
const tw = Math.min(process.stdout.columns || 80, 78);
|
|
148
|
+
const thinBar = c.dim + '─'.repeat(tw) + c.reset;
|
|
149
|
+
const thickTop = rgb(139, 92, 246) + c.bold + '═'.repeat(tw) + c.reset;
|
|
150
|
+
const thickBot = rgb(34, 211, 238) + c.bold + '═'.repeat(tw) + c.reset;
|
|
107
151
|
|
|
108
|
-
|
|
109
|
-
lines.push(sep);
|
|
152
|
+
lines.push(thickTop);
|
|
110
153
|
lines.push(
|
|
111
|
-
` ${
|
|
112
|
-
` ${c.dim}│${c.reset}
|
|
113
|
-
` ${c.
|
|
114
|
-
` ${c.dim}│${c.reset}
|
|
154
|
+
` ${rgb(192, 132, 252)}${c.bold}⏣ ${formatCoins(totalBalance)}${c.reset}` +
|
|
155
|
+
` ${c.dim}│${c.reset}` +
|
|
156
|
+
` ${rgb(52, 211, 153)}${c.bold}+${formatCoins(totalCoins)}${c.reset} ${c.dim}earned${c.reset}` +
|
|
157
|
+
` ${c.dim}│${c.reset}` +
|
|
158
|
+
` ${c.white}${c.bold}${totalCommands}${c.reset} ${c.dim}cmds${c.reset}` +
|
|
159
|
+
` ${c.dim}│${c.reset}` +
|
|
160
|
+
` ${rgb(251, 191, 36)}⏱ ${formatUptime()}${c.reset}` +
|
|
161
|
+
` ${c.dim}│${c.reset}` +
|
|
162
|
+
` ${rgb(52, 211, 153)}●${c.reset} ${c.dim}LIVE${c.reset}`
|
|
115
163
|
);
|
|
116
|
-
lines.push(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
);
|
|
164
|
+
lines.push(thinBar);
|
|
165
|
+
|
|
166
|
+
for (const wk of workers) {
|
|
167
|
+
const last = (wk.lastStatus || 'idle').substring(0, 32);
|
|
168
|
+
const dot = wk.running
|
|
169
|
+
? (wk.paused ? `${rgb(251, 191, 36)}⏸${c.reset}` : wk.busy ? `${rgb(251, 191, 36)}◉${c.reset}` : `${rgb(52, 211, 153)}●${c.reset}`)
|
|
170
|
+
: `${rgb(239, 68, 68)}○${c.reset}`;
|
|
171
|
+
const name = `${wk.color}${c.bold}${(wk.username || '?').substring(0, 16).padEnd(16)}${c.reset}`;
|
|
172
|
+
const bal = wk.stats.balance > 0
|
|
173
|
+
? `${rgb(251, 191, 36)}⏣${c.reset}${c.white}${c.bold}${formatCoins(wk.stats.balance).padStart(7)}${c.reset}`
|
|
174
|
+
: `${c.dim}⏣ -${c.reset}`;
|
|
175
|
+
const earned = wk.stats.coins > 0
|
|
176
|
+
? `${rgb(52, 211, 153)}+${formatCoins(wk.stats.coins).padStart(6)}${c.reset}`
|
|
177
|
+
: `${c.dim} +0${c.reset}`;
|
|
178
|
+
lines.push(` ${dot} ${name} ${bal} ${earned} ${c.dim}${last}${c.reset}`);
|
|
131
179
|
}
|
|
132
|
-
lines.push(sep);
|
|
133
180
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
181
|
+
if (recentLogs.length > 0) {
|
|
182
|
+
lines.push(thinBar);
|
|
183
|
+
for (const entry of recentLogs) {
|
|
184
|
+
lines.push(` ${c.dim}${entry}${c.reset}`);
|
|
185
|
+
}
|
|
137
186
|
}
|
|
138
|
-
if (recentLogs.length > 0) lines.push(sep);
|
|
139
187
|
|
|
140
|
-
|
|
188
|
+
lines.push(thickBot);
|
|
189
|
+
|
|
141
190
|
if (dashboardLines > 0) {
|
|
142
191
|
process.stdout.write(c.cursorUp(dashboardLines));
|
|
143
192
|
}
|
|
@@ -145,30 +194,31 @@ function renderDashboard() {
|
|
|
145
194
|
process.stdout.write(c.clearLine + '\r' + line + '\n');
|
|
146
195
|
}
|
|
147
196
|
dashboardLines = lines.length;
|
|
197
|
+
dashboardRendering = false;
|
|
148
198
|
}
|
|
149
199
|
|
|
150
200
|
function log(type, msg, label) {
|
|
151
|
-
const time = new Date().toLocaleTimeString('en-US', { hour12:
|
|
201
|
+
const time = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
152
202
|
const icons = {
|
|
153
|
-
info:
|
|
154
|
-
cmd:
|
|
203
|
+
info: '·', success: '✓', error: '✗', warn: '!',
|
|
204
|
+
cmd: '▸', coin: '$', buy: '♦', bal: '◈', debug: '·',
|
|
155
205
|
};
|
|
156
|
-
const tag = label ?
|
|
157
|
-
const stripped = msg.replace(/\x1b\[[0-9;]*m/g, '');
|
|
206
|
+
const tag = label ? (label.replace(/\x1b\[[0-9;]*m/g, '') + ' ') : '';
|
|
207
|
+
const stripped = msg.replace(/\x1b\[[0-9;]*m/g, '');
|
|
158
208
|
if (dashboardStarted) {
|
|
159
|
-
|
|
160
|
-
recentLogs.push(`${time} ${icons[type] || '│'} ${tag}${stripped}`.substring(0, 80));
|
|
209
|
+
recentLogs.push(`${time} ${icons[type] || '·'} ${tag}${stripped}`.substring(0, 72));
|
|
161
210
|
while (recentLogs.length > MAX_LOGS) recentLogs.shift();
|
|
162
211
|
renderDashboard();
|
|
163
212
|
} else {
|
|
164
213
|
const colorIcons = {
|
|
165
|
-
info: `${c.
|
|
166
|
-
error: `${
|
|
167
|
-
cmd: `${
|
|
168
|
-
buy: `${
|
|
169
|
-
debug: `${c.dim}
|
|
214
|
+
info: `${c.dim}·${c.reset}`, success: `${rgb(52, 211, 153)}✓${c.reset}`,
|
|
215
|
+
error: `${rgb(239, 68, 68)}✗${c.reset}`, warn: `${rgb(251, 191, 36)}!${c.reset}`,
|
|
216
|
+
cmd: `${rgb(168, 85, 247)}▸${c.reset}`, coin: `${rgb(251, 191, 36)}$${c.reset}`,
|
|
217
|
+
buy: `${rgb(59, 130, 246)}♦${c.reset}`, bal: `${rgb(52, 211, 153)}◈${c.reset}`,
|
|
218
|
+
debug: `${c.dim}·${c.reset}`,
|
|
170
219
|
};
|
|
171
|
-
|
|
220
|
+
const tagCol = label ? `${label} ` : '';
|
|
221
|
+
console.log(` ${colorIcons[type] || colorIcons.info} ${tagCol}${msg}`);
|
|
172
222
|
}
|
|
173
223
|
}
|
|
174
224
|
|
|
@@ -193,6 +243,17 @@ async function sendLog(accountName, command, response, status) {
|
|
|
193
243
|
} catch { /* silent */ }
|
|
194
244
|
}
|
|
195
245
|
|
|
246
|
+
async function reportEarnings(accountId, accountName, earned, spent, command) {
|
|
247
|
+
if (earned <= 0 && spent <= 0) return;
|
|
248
|
+
try {
|
|
249
|
+
await fetch(`${API_URL}/api/grinder/earnings`, {
|
|
250
|
+
method: 'POST',
|
|
251
|
+
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
252
|
+
body: JSON.stringify({ account_id: accountId, account_name: accountName, earned, spent, command }),
|
|
253
|
+
});
|
|
254
|
+
} catch { /* silent */ }
|
|
255
|
+
}
|
|
256
|
+
|
|
196
257
|
function randomDelay(min, max) {
|
|
197
258
|
return new Promise((r) => setTimeout(r, (Math.random() * (max - min) + min) * 1000));
|
|
198
259
|
}
|
|
@@ -203,31 +264,21 @@ function safeParseJSON(str, fallback = []) {
|
|
|
203
264
|
try { return JSON.parse(str || '[]'); } catch { return fallback; }
|
|
204
265
|
}
|
|
205
266
|
|
|
206
|
-
// ── Coin Parser
|
|
267
|
+
// ── Coin Parser (conservative — only ⏣ amounts) ─────────────
|
|
207
268
|
function parseCoins(text) {
|
|
208
269
|
if (!text) return 0;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
/
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
/gained\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
219
|
-
];
|
|
220
|
-
let total = 0;
|
|
221
|
-
for (const pat of patterns) {
|
|
222
|
-
const matches = text.match(pat);
|
|
223
|
-
if (matches) {
|
|
224
|
-
for (const m of matches) {
|
|
225
|
-
const numStr = m.replace(/[^\d]/g, '');
|
|
226
|
-
if (numStr) total = Math.max(total, parseInt(numStr));
|
|
227
|
-
}
|
|
270
|
+
// Only match coins that are clearly ⏣ prefixed
|
|
271
|
+
const matches = text.match(/⏣\s*([\d,]+)/g);
|
|
272
|
+
if (!matches) return 0;
|
|
273
|
+
let best = 0;
|
|
274
|
+
for (const m of matches) {
|
|
275
|
+
const numStr = m.replace(/[^\d]/g, '');
|
|
276
|
+
if (numStr) {
|
|
277
|
+
const val = parseInt(numStr);
|
|
278
|
+
if (val > 0 && val < 10000000) best = Math.max(best, val); // Cap at 10M sanity
|
|
228
279
|
}
|
|
229
280
|
}
|
|
230
|
-
return
|
|
281
|
+
return best;
|
|
231
282
|
}
|
|
232
283
|
|
|
233
284
|
function getFullText(msg) {
|
|
@@ -253,7 +304,7 @@ function getAllButtons(msg) {
|
|
|
253
304
|
for (const row of msg.components) {
|
|
254
305
|
if (row.components) {
|
|
255
306
|
for (const comp of row.components) {
|
|
256
|
-
if (comp.type === 2) buttons.push(comp);
|
|
307
|
+
if (comp.type === 2 || comp.type === 'BUTTON') buttons.push(comp);
|
|
257
308
|
}
|
|
258
309
|
}
|
|
259
310
|
}
|
|
@@ -267,7 +318,7 @@ function getAllSelectMenus(msg) {
|
|
|
267
318
|
for (const row of msg.components) {
|
|
268
319
|
if (row.components) {
|
|
269
320
|
for (const comp of row.components) {
|
|
270
|
-
if (comp.type === 3) menus.push(comp);
|
|
321
|
+
if (comp.type === 3 || comp.type === 'SELECT_MENU' || comp.type === 'STRING_SELECT') menus.push(comp);
|
|
271
322
|
}
|
|
272
323
|
}
|
|
273
324
|
}
|
|
@@ -275,6 +326,18 @@ function getAllSelectMenus(msg) {
|
|
|
275
326
|
return menus;
|
|
276
327
|
}
|
|
277
328
|
|
|
329
|
+
// Safe button click — tries .click() first, falls back to msg.clickButton()
|
|
330
|
+
async function safeClickButton(msg, button) {
|
|
331
|
+
if (typeof button.click === 'function') {
|
|
332
|
+
return button.click();
|
|
333
|
+
}
|
|
334
|
+
// Fallback: use message.clickButton with customId
|
|
335
|
+
if (button.customId && typeof msg.clickButton === 'function') {
|
|
336
|
+
return msg.clickButton(button.customId);
|
|
337
|
+
}
|
|
338
|
+
throw new Error('No click method available on button');
|
|
339
|
+
}
|
|
340
|
+
|
|
278
341
|
function pickSafeButton(buttons, safeList) {
|
|
279
342
|
if (!buttons || buttons.length === 0) return null;
|
|
280
343
|
if (safeList && safeList.length > 0) {
|
|
@@ -309,6 +372,68 @@ function debugMessage(msg, label) {
|
|
|
309
372
|
}
|
|
310
373
|
}
|
|
311
374
|
|
|
375
|
+
// ══════════════════════════════════════════════════════════════
|
|
376
|
+
// ═ Min-Heap Priority Queue for Command Scheduling
|
|
377
|
+
// ══════════════════════════════════════════════════════════════
|
|
378
|
+
|
|
379
|
+
class MinHeap {
|
|
380
|
+
constructor() { this.heap = []; }
|
|
381
|
+
|
|
382
|
+
push(item) {
|
|
383
|
+
this.heap.push(item);
|
|
384
|
+
this._bubbleUp(this.heap.length - 1);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
pop() {
|
|
388
|
+
if (this.heap.length === 0) return null;
|
|
389
|
+
const top = this.heap[0];
|
|
390
|
+
const last = this.heap.pop();
|
|
391
|
+
if (this.heap.length > 0) {
|
|
392
|
+
this.heap[0] = last;
|
|
393
|
+
this._sinkDown(0);
|
|
394
|
+
}
|
|
395
|
+
return top;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
peek() {
|
|
399
|
+
return this.heap.length > 0 ? this.heap[0] : null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
get size() { return this.heap.length; }
|
|
403
|
+
|
|
404
|
+
_bubbleUp(i) {
|
|
405
|
+
while (i > 0) {
|
|
406
|
+
const parent = (i - 1) >> 1;
|
|
407
|
+
if (this.heap[i].nextRunAt < this.heap[parent].nextRunAt ||
|
|
408
|
+
(this.heap[i].nextRunAt === this.heap[parent].nextRunAt && this.heap[i].priority > this.heap[parent].priority)) {
|
|
409
|
+
[this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
|
|
410
|
+
i = parent;
|
|
411
|
+
} else break;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
_sinkDown(i) {
|
|
416
|
+
const n = this.heap.length;
|
|
417
|
+
while (true) {
|
|
418
|
+
let smallest = i;
|
|
419
|
+
const left = 2 * i + 1;
|
|
420
|
+
const right = 2 * i + 2;
|
|
421
|
+
if (left < n && (this.heap[left].nextRunAt < this.heap[smallest].nextRunAt ||
|
|
422
|
+
(this.heap[left].nextRunAt === this.heap[smallest].nextRunAt && this.heap[left].priority > this.heap[smallest].priority))) {
|
|
423
|
+
smallest = left;
|
|
424
|
+
}
|
|
425
|
+
if (right < n && (this.heap[right].nextRunAt < this.heap[smallest].nextRunAt ||
|
|
426
|
+
(this.heap[right].nextRunAt === this.heap[smallest].nextRunAt && this.heap[right].priority > this.heap[smallest].priority))) {
|
|
427
|
+
smallest = right;
|
|
428
|
+
}
|
|
429
|
+
if (smallest !== i) {
|
|
430
|
+
[this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];
|
|
431
|
+
i = smallest;
|
|
432
|
+
} else break;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
312
437
|
// ══════════════════════════════════════════════════════════════
|
|
313
438
|
// ═ Worker: one per Discord account
|
|
314
439
|
// ══════════════════════════════════════════════════════════════
|
|
@@ -328,15 +453,23 @@ class AccountWorker {
|
|
|
328
453
|
this.lastRunTime = {};
|
|
329
454
|
this.cycleCount = 0;
|
|
330
455
|
this.lastCommandRun = 0;
|
|
456
|
+
this.paused = false;
|
|
457
|
+
this.globalCooldownUntil = 0;
|
|
458
|
+
this.commandQueue = null;
|
|
459
|
+
this.lastHealthCheck = Date.now();
|
|
331
460
|
}
|
|
332
461
|
|
|
333
462
|
get tag() { return `${this.color}${c.bold}${this.username}${c.reset}`; }
|
|
334
|
-
log(type, msg) { log(type, msg, this.tag); }
|
|
335
463
|
|
|
336
|
-
|
|
464
|
+
log(type, msg) {
|
|
465
|
+
const stripped = msg.replace(/\x1b\[[0-9;]*m/g, '');
|
|
466
|
+
this.lastStatus = stripped.substring(0, 40);
|
|
467
|
+
log(type, msg, this.tag);
|
|
468
|
+
}
|
|
469
|
+
|
|
337
470
|
setStatus(text) {
|
|
338
471
|
this.lastStatus = text;
|
|
339
|
-
renderDashboard();
|
|
472
|
+
if (dashboardStarted) renderDashboard();
|
|
340
473
|
}
|
|
341
474
|
|
|
342
475
|
waitForDankMemer(timeout = 15000) {
|
|
@@ -347,19 +480,39 @@ class AccountWorker {
|
|
|
347
480
|
resolve(null);
|
|
348
481
|
}, timeout);
|
|
349
482
|
const self = this;
|
|
483
|
+
function cleanup() {
|
|
484
|
+
clearTimeout(timer);
|
|
485
|
+
self.client.removeListener('messageCreate', handler);
|
|
486
|
+
self.client.removeListener('messageUpdate', updateHandler);
|
|
487
|
+
}
|
|
350
488
|
function handler(msg) {
|
|
351
489
|
if (msg.author.id === DANK_MEMER_ID && msg.channel.id === self.channel.id) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
490
|
+
// If message has no content and no embeds, Dank Memer may populate via edit
|
|
491
|
+
const hasContent = (msg.content && msg.content.length > 0) || (msg.embeds && msg.embeds.length > 0) || (msg.components && msg.components.length > 0);
|
|
492
|
+
if (!hasContent) {
|
|
493
|
+
// Wait for the edit with actual content (up to 3s)
|
|
494
|
+
const editTimer = setTimeout(() => { cleanup(); resolve(msg); }, 3000);
|
|
495
|
+
function editHandler(oldMsg, newMsg) {
|
|
496
|
+
if (newMsg.id === msg.id) {
|
|
497
|
+
clearTimeout(editTimer);
|
|
498
|
+
self.client.removeListener('messageUpdate', editHandler);
|
|
499
|
+
cleanup();
|
|
500
|
+
resolve(newMsg);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
self.client.on('messageUpdate', editHandler);
|
|
504
|
+
// Remove original handlers since we're now specifically looking for this msg's edit
|
|
505
|
+
self.client.removeListener('messageCreate', handler);
|
|
506
|
+
self.client.removeListener('messageUpdate', updateHandler);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
cleanup();
|
|
355
510
|
resolve(msg);
|
|
356
511
|
}
|
|
357
512
|
}
|
|
358
513
|
function updateHandler(oldMsg, newMsg) {
|
|
359
514
|
if (newMsg.author?.id === DANK_MEMER_ID && newMsg.channel?.id === self.channel.id) {
|
|
360
|
-
|
|
361
|
-
self.client.removeListener('messageCreate', handler);
|
|
362
|
-
self.client.removeListener('messageUpdate', updateHandler);
|
|
515
|
+
cleanup();
|
|
363
516
|
resolve(newMsg);
|
|
364
517
|
}
|
|
365
518
|
}
|
|
@@ -473,7 +626,7 @@ class AccountWorker {
|
|
|
473
626
|
}
|
|
474
627
|
|
|
475
628
|
this.log('buy', `Clicking Buy ${itemName}...`);
|
|
476
|
-
try { await buyBtn
|
|
629
|
+
try { await safeClickButton(response, buyBtn); } catch (e) {
|
|
477
630
|
this.log('error', `Buy click failed: ${e.message}`);
|
|
478
631
|
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
479
632
|
return false;
|
|
@@ -560,383 +713,13 @@ class AccountWorker {
|
|
|
560
713
|
}
|
|
561
714
|
}
|
|
562
715
|
|
|
563
|
-
// ══════════════════════════════════════════════════════════════
|
|
564
|
-
// ═ COMMAND HANDLERS
|
|
565
|
-
// ══════════════════════════════════════════════════════════════
|
|
566
|
-
|
|
567
|
-
async handleSearch(response) {
|
|
568
|
-
if (!response) return null;
|
|
569
|
-
debugMessage(response, 'SEARCH');
|
|
570
|
-
const buttons = getAllButtons(response);
|
|
571
|
-
if (buttons.length === 0) return getFullText(response).substring(0, 80);
|
|
572
|
-
const userSafe = safeParseJSON(this.account.search_answers, []);
|
|
573
|
-
const btn = pickSafeButton(buttons, userSafe);
|
|
574
|
-
if (btn) {
|
|
575
|
-
await humanDelay();
|
|
576
|
-
try {
|
|
577
|
-
await btn.click();
|
|
578
|
-
const followUp = await this.waitForDankMemer(10000);
|
|
579
|
-
if (followUp) {
|
|
580
|
-
const text = getFullText(followUp);
|
|
581
|
-
const coins = parseCoins(text);
|
|
582
|
-
if (coins > 0) { this.stats.coins += coins; return `${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
583
|
-
if (text.toLowerCase().includes('nothing')) return `${btn.label} → found nothing`;
|
|
584
|
-
return `${btn.label} → done`;
|
|
585
|
-
}
|
|
586
|
-
return `Clicked: ${btn.label}`;
|
|
587
|
-
} catch { return null; }
|
|
588
|
-
}
|
|
589
|
-
return 'No safe option found';
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
async handleCrime(response) {
|
|
593
|
-
if (!response) return null;
|
|
594
|
-
debugMessage(response, 'CRIME');
|
|
595
|
-
const buttons = getAllButtons(response);
|
|
596
|
-
if (buttons.length === 0) return getFullText(response).substring(0, 80);
|
|
597
|
-
const userSafe = safeParseJSON(this.account.crime_answers, []);
|
|
598
|
-
const btn = pickSafeButton(buttons, userSafe);
|
|
599
|
-
if (btn) {
|
|
600
|
-
await humanDelay();
|
|
601
|
-
try {
|
|
602
|
-
await btn.click();
|
|
603
|
-
const followUp = await this.waitForDankMemer(10000);
|
|
604
|
-
if (followUp) {
|
|
605
|
-
const text = getFullText(followUp);
|
|
606
|
-
const coins = parseCoins(text);
|
|
607
|
-
if (coins > 0) { this.stats.coins += coins; return `${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
608
|
-
return `${btn.label} → done`;
|
|
609
|
-
}
|
|
610
|
-
return `Clicked: ${btn.label}`;
|
|
611
|
-
} catch { return null; }
|
|
612
|
-
}
|
|
613
|
-
return 'No safe option found';
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
async handleHighLow(response) {
|
|
617
|
-
if (!response) return null;
|
|
618
|
-
debugMessage(response, 'HIGHLOW');
|
|
619
|
-
const text = getFullText(response);
|
|
620
|
-
const match = text.match(/number.*?(\d+)/i) || text.match(/(\d+)/);
|
|
621
|
-
const buttons = getAllButtons(response);
|
|
622
|
-
if (match && buttons.length >= 2) {
|
|
623
|
-
const num = parseInt(match[1]);
|
|
624
|
-
await humanDelay();
|
|
625
|
-
let targetBtn;
|
|
626
|
-
if (num > 50) targetBtn = buttons.find((b) => (b.label || '').toLowerCase().includes('lower')) || buttons[1];
|
|
627
|
-
else if (num < 50) targetBtn = buttons.find((b) => (b.label || '').toLowerCase().includes('higher')) || buttons[0];
|
|
628
|
-
else {
|
|
629
|
-
const jackpot = buttons.find((b) => (b.label || '').toLowerCase().includes('jackpot'));
|
|
630
|
-
targetBtn = jackpot || buttons[Math.floor(Math.random() * 2)];
|
|
631
|
-
}
|
|
632
|
-
try {
|
|
633
|
-
await targetBtn.click();
|
|
634
|
-
const followUp = await this.waitForDankMemer(10000);
|
|
635
|
-
if (followUp) {
|
|
636
|
-
const ftText = getFullText(followUp);
|
|
637
|
-
const coins = parseCoins(ftText);
|
|
638
|
-
if (coins > 0) { this.stats.coins += coins; return `${num} → ${targetBtn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
639
|
-
const moreButtons = getAllButtons(followUp);
|
|
640
|
-
if (moreButtons.length >= 2) return await this.handleHighLow(followUp);
|
|
641
|
-
return `${num} → ${targetBtn.label}`;
|
|
642
|
-
}
|
|
643
|
-
return `${num} → ${targetBtn.label}`;
|
|
644
|
-
} catch { return null; }
|
|
645
|
-
}
|
|
646
|
-
if (buttons.length > 0) {
|
|
647
|
-
const btn = buttons[Math.floor(Math.random() * buttons.length)];
|
|
648
|
-
await humanDelay();
|
|
649
|
-
try { await btn.click(); } catch {}
|
|
650
|
-
return `Clicked: ${btn.label || 'button'}`;
|
|
651
|
-
}
|
|
652
|
-
return null;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
async handleScratch(response) {
|
|
656
|
-
if (!response) return null;
|
|
657
|
-
debugMessage(response, 'SCRATCH');
|
|
658
|
-
const buttons = getAllButtons(response);
|
|
659
|
-
if (buttons.length === 0) {
|
|
660
|
-
const text = getFullText(response);
|
|
661
|
-
const coins = parseCoins(text);
|
|
662
|
-
if (coins > 0) { this.stats.coins += coins; return `${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
663
|
-
return text.substring(0, 60) || 'done';
|
|
664
|
-
}
|
|
665
|
-
let lastResponse = response;
|
|
666
|
-
for (let i = 0; i < Math.min(buttons.length, 9); i++) {
|
|
667
|
-
const btn = buttons[i];
|
|
668
|
-
if (btn && !btn.disabled) {
|
|
669
|
-
await humanDelay(300, 700);
|
|
670
|
-
try {
|
|
671
|
-
await btn.click();
|
|
672
|
-
const followUp = await this.waitForDankMemer(5000);
|
|
673
|
-
if (followUp) lastResponse = followUp;
|
|
674
|
-
} catch { break; }
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
const finalText = getFullText(lastResponse);
|
|
678
|
-
const coins = parseCoins(finalText);
|
|
679
|
-
if (coins > 0) { this.stats.coins += coins; return `scratch → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
680
|
-
return 'scratch done';
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
async handleAdventure(response) {
|
|
684
|
-
if (!response) return null;
|
|
685
|
-
debugMessage(response, 'ADVENTURE');
|
|
686
|
-
let current = response;
|
|
687
|
-
let rounds = 0;
|
|
688
|
-
const MAX_ROUNDS = 15;
|
|
689
|
-
while (current && rounds < MAX_ROUNDS) {
|
|
690
|
-
const buttons = getAllButtons(current);
|
|
691
|
-
if (buttons.length === 0) break;
|
|
692
|
-
const safeButtons = buttons.filter((b) => !b.disabled && b.style !== 4);
|
|
693
|
-
const btn = safeButtons.length > 0 ? safeButtons[Math.floor(Math.random() * safeButtons.length)] : buttons.find((b) => !b.disabled);
|
|
694
|
-
if (!btn) break;
|
|
695
|
-
await humanDelay(500, 1500);
|
|
696
|
-
try {
|
|
697
|
-
await btn.click();
|
|
698
|
-
const followUp = await this.waitForDankMemer(10000);
|
|
699
|
-
if (followUp) { current = followUp; rounds++; } else break;
|
|
700
|
-
} catch { break; }
|
|
701
|
-
}
|
|
702
|
-
const finalText = getFullText(current);
|
|
703
|
-
// Parse next adventure time from footer
|
|
704
|
-
const nextMatch = finalText.match(/next adventure.*?(\d+)/i);
|
|
705
|
-
if (nextMatch) {
|
|
706
|
-
const nextSec = parseInt(nextMatch[1]) * 60;
|
|
707
|
-
this.log('info', `Next adventure in ${c.yellow}${nextMatch[1]}m${c.reset}`);
|
|
708
|
-
await this.setCooldown('adventure', nextSec);
|
|
709
|
-
}
|
|
710
|
-
const coins = parseCoins(finalText);
|
|
711
|
-
if (coins > 0) { this.stats.coins += coins; return `adventure (${rounds} rounds) → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
712
|
-
return `adventure done (${rounds} rounds)`;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// ── BlackJack Handler ─────────────────────────────────────
|
|
716
|
-
async handleBlackjack(response) {
|
|
717
|
-
if (!response) return null;
|
|
718
|
-
debugMessage(response, 'BLACKJACK');
|
|
719
|
-
let current = response;
|
|
720
|
-
const MAX_ROUNDS = 10;
|
|
721
|
-
for (let i = 0; i < MAX_ROUNDS; i++) {
|
|
722
|
-
const text = getFullText(current);
|
|
723
|
-
const buttons = getAllButtons(current);
|
|
724
|
-
if (buttons.length === 0) break;
|
|
725
|
-
// Parse player total from embed - look for patterns like "Total: 15" or "Value: **15**"
|
|
726
|
-
const totalMatch = text.match(/total[:\s]*\**(\d+)\**/i) || text.match(/value[:\s]*\**(\d+)\**/i);
|
|
727
|
-
const playerTotal = totalMatch ? parseInt(totalMatch[1]) : 0;
|
|
728
|
-
let targetBtn;
|
|
729
|
-
if (playerTotal >= 17 || playerTotal === 0) {
|
|
730
|
-
targetBtn = buttons.find(b => (b.label || '').toLowerCase().includes('stand')) || buttons[1];
|
|
731
|
-
} else {
|
|
732
|
-
targetBtn = buttons.find(b => (b.label || '').toLowerCase().includes('hit')) || buttons[0];
|
|
733
|
-
}
|
|
734
|
-
if (!targetBtn || targetBtn.disabled) break;
|
|
735
|
-
await humanDelay(500, 1200);
|
|
736
|
-
try {
|
|
737
|
-
await targetBtn.click();
|
|
738
|
-
const followUp = await this.waitForDankMemer(8000);
|
|
739
|
-
if (followUp) {
|
|
740
|
-
current = followUp;
|
|
741
|
-
const fButtons = getAllButtons(current);
|
|
742
|
-
if (fButtons.length === 0 || fButtons.every(b => b.disabled)) break;
|
|
743
|
-
} else break;
|
|
744
|
-
} catch { break; }
|
|
745
|
-
}
|
|
746
|
-
const finalText = getFullText(current);
|
|
747
|
-
const coins = parseCoins(finalText);
|
|
748
|
-
if (coins > 0) { this.stats.coins += coins; return `blackjack → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
749
|
-
if (finalText.toLowerCase().includes('won')) return 'blackjack → won';
|
|
750
|
-
if (finalText.toLowerCase().includes('lost') || finalText.toLowerCase().includes('bust')) return 'blackjack → lost';
|
|
751
|
-
return 'blackjack done';
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// ── Trivia Handler ────────────────────────────────────────
|
|
755
|
-
async handleTrivia(response) {
|
|
756
|
-
if (!response) return null;
|
|
757
|
-
debugMessage(response, 'TRIVIA');
|
|
758
|
-
const buttons = getAllButtons(response);
|
|
759
|
-
if (buttons.length === 0) return getFullText(response).substring(0, 80);
|
|
760
|
-
// Pick a random answer (we don't have a trivia DB - random has ~25% chance)
|
|
761
|
-
const clickable = buttons.filter(b => !b.disabled);
|
|
762
|
-
const btn = clickable[Math.floor(Math.random() * clickable.length)];
|
|
763
|
-
if (btn) {
|
|
764
|
-
await humanDelay(1000, 3000);
|
|
765
|
-
try {
|
|
766
|
-
await btn.click();
|
|
767
|
-
const followUp = await this.waitForDankMemer(8000);
|
|
768
|
-
if (followUp) {
|
|
769
|
-
const text = getFullText(followUp);
|
|
770
|
-
const coins = parseCoins(text);
|
|
771
|
-
if (coins > 0) { this.stats.coins += coins; return `trivia → ${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
772
|
-
if (text.toLowerCase().includes('correct')) return `trivia → ${btn.label} → correct`;
|
|
773
|
-
return `trivia → ${btn.label} → wrong`;
|
|
774
|
-
}
|
|
775
|
-
} catch {}
|
|
776
|
-
}
|
|
777
|
-
return 'trivia done';
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
// ── Work Shift Handler ────────────────────────────────────
|
|
781
|
-
async handleWorkShift(response) {
|
|
782
|
-
if (!response) return null;
|
|
783
|
-
debugMessage(response, 'WORK');
|
|
784
|
-
let current = response;
|
|
785
|
-
const MAX_ROUNDS = 5;
|
|
786
|
-
for (let i = 0; i < MAX_ROUNDS; i++) {
|
|
787
|
-
const buttons = getAllButtons(current);
|
|
788
|
-
const text = getFullText(current);
|
|
789
|
-
// Work shift mini-games: word scramble, memory, typing, etc.
|
|
790
|
-
// Try to click any available non-disabled button
|
|
791
|
-
if (buttons.length === 0) break;
|
|
792
|
-
const clickable = buttons.filter(b => !b.disabled);
|
|
793
|
-
if (clickable.length === 0) break;
|
|
794
|
-
// For word-type games, look for the correct button
|
|
795
|
-
const btn = clickable[0]; // Just pick first available
|
|
796
|
-
await humanDelay(500, 1500);
|
|
797
|
-
try {
|
|
798
|
-
await btn.click();
|
|
799
|
-
const followUp = await this.waitForDankMemer(8000);
|
|
800
|
-
if (followUp) { current = followUp; } else break;
|
|
801
|
-
} catch { break; }
|
|
802
|
-
}
|
|
803
|
-
const finalText = getFullText(current);
|
|
804
|
-
const coins = parseCoins(finalText);
|
|
805
|
-
if (coins > 0) { this.stats.coins += coins; return `work → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
806
|
-
return 'work done';
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// ── Generic Command Handler (beg, hunt, dig, farm, tidy, etc.) ──
|
|
810
|
-
async handleGenericCommand(response) {
|
|
811
|
-
if (!response) return null;
|
|
812
|
-
debugMessage(response, 'GENERIC');
|
|
813
|
-
const text = getFullText(response);
|
|
814
|
-
const coins = parseCoins(text);
|
|
815
|
-
const neededItem = this.needsItem(text);
|
|
816
|
-
if (neededItem) {
|
|
817
|
-
this.log('warn', `Missing ${c.bold}${neededItem}${c.reset} — auto-buying...`);
|
|
818
|
-
const bought = await this.buyItem(neededItem);
|
|
819
|
-
if (bought) return `auto-bought ${neededItem}`;
|
|
820
|
-
return `need ${neededItem} (couldn't buy)`;
|
|
821
|
-
}
|
|
822
|
-
// Handle buttons if present (e.g., postmemes, farm choices)
|
|
823
|
-
const buttons = getAllButtons(response);
|
|
824
|
-
if (buttons.length > 0) {
|
|
825
|
-
const btn = buttons.find((b) => !b.disabled) || buttons[0];
|
|
826
|
-
if (btn) {
|
|
827
|
-
await humanDelay();
|
|
828
|
-
try {
|
|
829
|
-
await btn.click();
|
|
830
|
-
const followUp = await this.waitForDankMemer(8000);
|
|
831
|
-
if (followUp) {
|
|
832
|
-
const fText = getFullText(followUp);
|
|
833
|
-
const fCoins = parseCoins(fText);
|
|
834
|
-
if (fCoins > 0) { this.stats.coins += fCoins; return `${c.green}+⏣ ${fCoins.toLocaleString()}${c.reset}`; }
|
|
835
|
-
// Check if followUp also has buttons (multi-step)
|
|
836
|
-
const nextButtons = getAllButtons(followUp);
|
|
837
|
-
if (nextButtons.length > 0) {
|
|
838
|
-
const nextBtn = nextButtons.find(b => !b.disabled);
|
|
839
|
-
if (nextBtn) {
|
|
840
|
-
await humanDelay();
|
|
841
|
-
try { await nextBtn.click(); } catch {}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
} catch {}
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
// Handle select menus (some commands use dropdowns)
|
|
849
|
-
const menus = getAllSelectMenus(response);
|
|
850
|
-
if (menus.length > 0) {
|
|
851
|
-
const menu = menus[0];
|
|
852
|
-
const options = menu.options || [];
|
|
853
|
-
if (options.length > 0) {
|
|
854
|
-
const opt = options[Math.floor(Math.random() * options.length)];
|
|
855
|
-
try {
|
|
856
|
-
await response.selectMenu(menu.customId, [opt.value]);
|
|
857
|
-
const followUp = await this.waitForDankMemer(8000);
|
|
858
|
-
if (followUp) {
|
|
859
|
-
const fText = getFullText(followUp);
|
|
860
|
-
const fCoins = parseCoins(fText);
|
|
861
|
-
if (fCoins > 0) { this.stats.coins += fCoins; return `${opt.label} → ${c.green}+⏣ ${fCoins.toLocaleString()}${c.reset}`; }
|
|
862
|
-
}
|
|
863
|
-
} catch {}
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
if (coins > 0) { this.stats.coins += coins; return `${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
867
|
-
return text.substring(0, 60) || 'done';
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// ── Simple Gambling (cointoss, roulette, slots, snakeeyes) ──
|
|
871
|
-
async handleSimpleGamble(response) {
|
|
872
|
-
if (!response) return null;
|
|
873
|
-
debugMessage(response, 'GAMBLE');
|
|
874
|
-
const text = getFullText(response);
|
|
875
|
-
const coins = parseCoins(text);
|
|
876
|
-
// Check buttons (some gambling has buttons)
|
|
877
|
-
const buttons = getAllButtons(response);
|
|
878
|
-
if (buttons.length > 0) {
|
|
879
|
-
const btn = buttons.find(b => !b.disabled);
|
|
880
|
-
if (btn) {
|
|
881
|
-
await humanDelay();
|
|
882
|
-
try {
|
|
883
|
-
await btn.click();
|
|
884
|
-
const followUp = await this.waitForDankMemer(8000);
|
|
885
|
-
if (followUp) {
|
|
886
|
-
const fText = getFullText(followUp);
|
|
887
|
-
const fCoins = parseCoins(fText);
|
|
888
|
-
if (fCoins > 0) { this.stats.coins += fCoins; return `${c.green}+⏣ ${fCoins.toLocaleString()}${c.reset}`; }
|
|
889
|
-
if (fText.toLowerCase().includes('won')) return `${c.green}won${c.reset}`;
|
|
890
|
-
if (fText.toLowerCase().includes('lost')) return `${c.red}lost${c.reset}`;
|
|
891
|
-
}
|
|
892
|
-
} catch {}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
if (coins > 0) { this.stats.coins += coins; return `${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
896
|
-
if (text.toLowerCase().includes('won')) return `${c.green}won${c.reset}`;
|
|
897
|
-
if (text.toLowerCase().includes('lost')) return `${c.red}lost${c.reset}`;
|
|
898
|
-
return text.substring(0, 60) || 'done';
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// ── Deposit Handler ────────────────────────────────────────
|
|
902
|
-
async handleDeposit(response) {
|
|
903
|
-
if (!response) return null;
|
|
904
|
-
const text = getFullText(response);
|
|
905
|
-
const coins = parseCoins(text);
|
|
906
|
-
if (text.toLowerCase().includes('deposited')) {
|
|
907
|
-
return `deposited ${c.green}⏣ ${coins.toLocaleString()}${c.reset}`;
|
|
908
|
-
}
|
|
909
|
-
return text.substring(0, 60) || 'deposit done';
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
// ── Alert Handler ─────────────────────────────────────────
|
|
913
|
-
async handleAlert(response) {
|
|
914
|
-
if (!response) return null;
|
|
915
|
-
debugMessage(response, 'ALERT');
|
|
916
|
-
const buttons = getAllButtons(response);
|
|
917
|
-
// Click any dismiss/accept/ok button
|
|
918
|
-
const btn = buttons.find(b => !b.disabled && ['ok', 'dismiss', 'accept', 'got it', 'continue'].some(s => (b.label || '').toLowerCase().includes(s)));
|
|
919
|
-
if (btn) {
|
|
920
|
-
await humanDelay();
|
|
921
|
-
try { await btn.click(); } catch {}
|
|
922
|
-
return `alert dismissed`;
|
|
923
|
-
}
|
|
924
|
-
if (buttons.length > 0) {
|
|
925
|
-
const first = buttons.find(b => !b.disabled);
|
|
926
|
-
if (first) {
|
|
927
|
-
await humanDelay();
|
|
928
|
-
try { await first.click(); } catch {}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
return 'alert handled';
|
|
932
|
-
}
|
|
933
|
-
|
|
934
716
|
// ── Run Single Command ──────────────────────────────────────
|
|
717
|
+
// Each modular command handler sends the command, waits for response,
|
|
718
|
+
// handles Hold Tight / cooldowns / item-buying internally.
|
|
935
719
|
async runCommand(cmdName, prefix) {
|
|
936
720
|
let cmdString;
|
|
937
721
|
const betAmount = this.account.bet_amount || 1000;
|
|
938
722
|
|
|
939
|
-
// Build the command string
|
|
940
723
|
switch (cmdName) {
|
|
941
724
|
case 'dep max': cmdString = `${prefix} dep max`; break;
|
|
942
725
|
case 'blackjack': cmdString = `${prefix} bj ${betAmount}`; break;
|
|
@@ -948,65 +731,85 @@ class AccountWorker {
|
|
|
948
731
|
default: cmdString = `${prefix} ${cmdName}`;
|
|
949
732
|
}
|
|
950
733
|
|
|
951
|
-
|
|
734
|
+
if (shutdownCalled || !this.running) return;
|
|
952
735
|
this.stats.commands++;
|
|
953
736
|
|
|
737
|
+
const cmdOpts = {
|
|
738
|
+
channel: this.channel,
|
|
739
|
+
waitForDankMemer: (timeout) => this.waitForDankMemer(timeout),
|
|
740
|
+
client: this.client,
|
|
741
|
+
safeAnswers: cmdName === 'search' ? safeParseJSON(this.account.search_answers, []) :
|
|
742
|
+
cmdName === 'crime' ? safeParseJSON(this.account.crime_answers, []) : [],
|
|
743
|
+
betAmount,
|
|
744
|
+
accountId: this.account.id,
|
|
745
|
+
redis,
|
|
746
|
+
};
|
|
747
|
+
|
|
954
748
|
try {
|
|
955
|
-
|
|
956
|
-
|
|
749
|
+
let cmdResult;
|
|
750
|
+
switch (cmdName) {
|
|
751
|
+
case 'beg': cmdResult = await commands.runBeg(cmdOpts); break;
|
|
752
|
+
case 'search': cmdResult = await commands.runSearch(cmdOpts); break;
|
|
753
|
+
case 'crime': cmdResult = await commands.runCrime(cmdOpts); break;
|
|
754
|
+
case 'hl': cmdResult = await commands.runHighLow(cmdOpts); break;
|
|
755
|
+
case 'pm': cmdResult = await commands.runPostMemes(cmdOpts); break;
|
|
756
|
+
case 'hunt': cmdResult = await commands.runHunt(cmdOpts); break;
|
|
757
|
+
case 'dig': cmdResult = await commands.runDig(cmdOpts); break;
|
|
758
|
+
case 'fish': cmdResult = await commands.runFish(cmdOpts); break;
|
|
759
|
+
case 'scratch': cmdResult = await commands.runScratch(cmdOpts); break;
|
|
760
|
+
case 'adventure': cmdResult = await commands.runAdventure(cmdOpts); break;
|
|
761
|
+
case 'blackjack': cmdResult = await commands.runBlackjack(cmdOpts); break;
|
|
762
|
+
case 'trivia': cmdResult = await commands.runTrivia(cmdOpts); break;
|
|
763
|
+
case 'work shift': cmdResult = await commands.runWorkShift(cmdOpts); break;
|
|
764
|
+
case 'coinflip': cmdResult = await commands.runCoinflip(cmdOpts); break;
|
|
765
|
+
case 'roulette': cmdResult = await commands.runRoulette(cmdOpts); break;
|
|
766
|
+
case 'slots': cmdResult = await commands.runSlots(cmdOpts); break;
|
|
767
|
+
case 'snakeeyes': cmdResult = await commands.runSnakeeyes(cmdOpts); break;
|
|
768
|
+
case 'dep max': cmdResult = await commands.runDeposit(cmdOpts); break;
|
|
769
|
+
case 'alert': cmdResult = await commands.runAlert(cmdOpts); break;
|
|
770
|
+
default: cmdResult = await commands.runGeneric({ ...cmdOpts, cmdString, cmdName }); break;
|
|
771
|
+
}
|
|
957
772
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
773
|
+
const result = cmdResult.result || 'done';
|
|
774
|
+
const resultLower = result.toLowerCase();
|
|
775
|
+
|
|
776
|
+
// Rate limit detection
|
|
777
|
+
if (resultLower.includes('slow down') || resultLower.includes('rate limit') || resultLower.includes('too fast')) {
|
|
778
|
+
this.log('warn', `${c.yellow}⚠ Rate limited!${c.reset} Setting 60s global cooldown for ${this.username}`);
|
|
779
|
+
this.globalCooldownUntil = Date.now() + 60000;
|
|
780
|
+
await this.setCooldown(cmdName, 60);
|
|
962
781
|
return;
|
|
963
782
|
}
|
|
964
783
|
|
|
965
|
-
//
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
this.log('warn', `${cmdName} on cooldown`);
|
|
973
|
-
// Try to parse remaining cooldown from text
|
|
974
|
-
const timeMatch = responseText.match(/(\d+)\s*(second|minute|hour|day)/i);
|
|
975
|
-
if (timeMatch) {
|
|
976
|
-
let secs = parseInt(timeMatch[1]);
|
|
977
|
-
const unit = timeMatch[2].toLowerCase();
|
|
978
|
-
if (unit.startsWith('minute')) secs *= 60;
|
|
979
|
-
if (unit.startsWith('hour')) secs *= 3600;
|
|
980
|
-
if (unit.startsWith('day')) secs *= 86400;
|
|
981
|
-
await this.setCooldown(cmdName, secs + 5);
|
|
982
|
-
this.log('info', `Set cooldown for ${cmdName}: ${secs}s`);
|
|
983
|
-
}
|
|
984
|
-
await sendLog(this.username, cmdString, 'on cooldown', 'cooldown');
|
|
784
|
+
// Captcha/verification detection — pause immediately
|
|
785
|
+
if (resultLower.includes('captcha') || resultLower.includes('verification') ||
|
|
786
|
+
resultLower.includes('are you human') || resultLower.includes("prove you're not a bot")) {
|
|
787
|
+
this.log('error', `${c.red}${c.bold}🚨 CAPTCHA/VERIFICATION DETECTED for ${this.username}!${c.reset}`);
|
|
788
|
+
this.log('error', `${c.red}URGENT: Worker PAUSED. Re-enable manually from the dashboard.${c.reset}`);
|
|
789
|
+
this.paused = true;
|
|
790
|
+
await sendLog(this.username, cmdString, 'CAPTCHA DETECTED — worker paused', 'error');
|
|
985
791
|
return;
|
|
986
792
|
}
|
|
987
793
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
case 'slots':
|
|
1001
|
-
case 'snakeeyes': result = await this.handleSimpleGamble(response); break;
|
|
1002
|
-
case 'dep max': result = await this.handleDeposit(response); break;
|
|
1003
|
-
case 'alert': result = await this.handleAlert(response); break;
|
|
1004
|
-
default: result = await this.handleGenericCommand(response); break;
|
|
794
|
+
const earned = Math.max(0, cmdResult.coins || 0);
|
|
795
|
+
const spent = Math.max(0, cmdResult.lost || 0);
|
|
796
|
+
if (earned > 0) this.stats.coins += earned;
|
|
797
|
+
if (cmdResult.nextCooldownSec) await this.setCooldown(cmdName, cmdResult.nextCooldownSec);
|
|
798
|
+
|
|
799
|
+
if (cmdResult.holdTightReason) {
|
|
800
|
+
const reason = cmdResult.holdTightReason;
|
|
801
|
+
this.log('warn', `Hold Tight caused by /${reason} — setting 35s cooldown on "${reason}"`);
|
|
802
|
+
const reasonMap = { postmemes: 'pm', highlow: 'hl', blackjack: 'bj', 'work shift': 'work shift' };
|
|
803
|
+
const mappedCmd = reasonMap[reason] || reason;
|
|
804
|
+
await this.setCooldown(mappedCmd, 35);
|
|
805
|
+
await this.setCooldown(cmdName, 35);
|
|
1005
806
|
}
|
|
1006
807
|
|
|
1007
808
|
this.stats.successes++;
|
|
1008
|
-
|
|
1009
|
-
|
|
809
|
+
const shortResult = result.substring(0, 50).replace(/\n/g, ' ');
|
|
810
|
+
this.setStatus(`${cmdName} → ${shortResult}`);
|
|
811
|
+
await sendLog(this.username, cmdString, result, 'success');
|
|
812
|
+
reportEarnings(this.account.id, this.username, earned, spent, cmdName);
|
|
1010
813
|
} catch (err) {
|
|
1011
814
|
this.stats.errors++;
|
|
1012
815
|
this.log('error', `${cmdString} failed: ${err.message}`);
|
|
@@ -1032,130 +835,215 @@ class AccountWorker {
|
|
|
1032
835
|
// Stats are shown in the live dashboard, no-op here
|
|
1033
836
|
}
|
|
1034
837
|
|
|
838
|
+
// ── Command Map (shared across ticks, used to build the heap) ──
|
|
839
|
+
static COMMAND_MAP = [
|
|
840
|
+
{ key: 'cmd_beg', cmd: 'beg', cdKey: 'cd_beg', defaultCd: 40, priority: 5 },
|
|
841
|
+
{ key: 'cmd_search', cmd: 'search', cdKey: 'cd_search', defaultCd: 25, priority: 4 },
|
|
842
|
+
{ key: 'cmd_hl', cmd: 'hl', cdKey: 'cd_hl', defaultCd: 10, priority: 3 },
|
|
843
|
+
{ key: 'cmd_pm', cmd: 'pm', cdKey: 'cd_pm', defaultCd: 20, priority: 3 },
|
|
844
|
+
{ key: 'cmd_crime', cmd: 'crime', cdKey: 'cd_crime', defaultCd: 40, priority: 2 },
|
|
845
|
+
{ key: 'cmd_hunt', cmd: 'hunt', cdKey: 'cd_hunt', defaultCd: 20, priority: 1 },
|
|
846
|
+
{ key: 'cmd_dig', cmd: 'dig', cdKey: 'cd_dig', defaultCd: 20, priority: 1 },
|
|
847
|
+
{ key: 'cmd_fish', cmd: 'fish', cdKey: 'cd_fish', defaultCd: 20, priority: 1 },
|
|
848
|
+
{ key: 'cmd_farm', cmd: 'farm', cdKey: 'cd_farm', defaultCd: 10, priority: 2 },
|
|
849
|
+
{ key: 'cmd_tidy', cmd: 'tidy', cdKey: 'cd_tidy', defaultCd: 40, priority: 1 },
|
|
850
|
+
{ key: 'cmd_blackjack', cmd: 'blackjack', cdKey: 'cd_blackjack', defaultCd: 3, priority: 3 },
|
|
851
|
+
{ key: 'cmd_cointoss', cmd: 'coinflip', cdKey: 'cd_cointoss', defaultCd: 2, priority: 3 },
|
|
852
|
+
{ key: 'cmd_roulette', cmd: 'roulette', cdKey: 'cd_roulette', defaultCd: 3, priority: 3 },
|
|
853
|
+
{ key: 'cmd_slots', cmd: 'slots', cdKey: 'cd_slots', defaultCd: 3, priority: 3 },
|
|
854
|
+
{ key: 'cmd_snakeeyes', cmd: 'snakeeyes', cdKey: 'cd_snakeeyes', defaultCd: 3, priority: 3 },
|
|
855
|
+
{ key: 'cmd_trivia', cmd: 'trivia', cdKey: 'cd_trivia', defaultCd: 10, priority: 2 },
|
|
856
|
+
{ key: 'cmd_use', cmd: 'use', cdKey: 'cd_use', defaultCd: 10, priority: 1 },
|
|
857
|
+
{ key: 'cmd_drops', cmd: 'drops', cdKey: 'cd_drops', defaultCd: 60, priority: 2 },
|
|
858
|
+
{ key: 'cmd_stream', cmd: 'stream', cdKey: 'cd_stream', defaultCd: 600, priority: 8 },
|
|
859
|
+
{ key: 'cmd_deposit', cmd: 'dep max', cdKey: 'cd_deposit', defaultCd: 60, priority: 7 },
|
|
860
|
+
{ key: 'cmd_daily', cmd: 'daily', cdKey: 'cd_daily', defaultCd: 86400, priority: 10 },
|
|
861
|
+
{ key: 'cmd_weekly', cmd: 'weekly', cdKey: 'cd_weekly', defaultCd: 604800, priority: 10 },
|
|
862
|
+
{ key: 'cmd_monthly', cmd: 'monthly', cdKey: 'cd_monthly', defaultCd: 2592000, priority: 10 },
|
|
863
|
+
{ key: 'cmd_work', cmd: 'work shift', cdKey: 'cd_work', defaultCd: 3600, priority: 8 },
|
|
864
|
+
{ key: 'cmd_scratch', cmd: 'scratch', cdKey: 'cd_scratch', defaultCd: 3600, priority: 6 },
|
|
865
|
+
{ key: 'cmd_adventure', cmd: 'adventure', cdKey: 'cd_adventure', defaultCd: 300, priority: 6 },
|
|
866
|
+
{ key: 'cmd_alert', cmd: 'alert', cdKey: 'cd_alert', defaultCd: 300, priority: 9 },
|
|
867
|
+
];
|
|
868
|
+
|
|
869
|
+
buildCommandQueue() {
|
|
870
|
+
const heap = new MinHeap();
|
|
871
|
+
const now = Date.now();
|
|
872
|
+
const enabled = AccountWorker.COMMAND_MAP.filter(
|
|
873
|
+
ci => this.account[ci.key] === true || this.account[ci.key] === 1
|
|
874
|
+
);
|
|
875
|
+
for (const info of enabled) {
|
|
876
|
+
heap.push({ cmd: info.cmd, nextRunAt: now, priority: info.priority, info });
|
|
877
|
+
}
|
|
878
|
+
return heap;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// ── Health Check: verify Discord client is still connected ──
|
|
882
|
+
async healthCheck() {
|
|
883
|
+
if (!this.running || shutdownCalled) return;
|
|
884
|
+
const now = Date.now();
|
|
885
|
+
if (now - this.lastHealthCheck < 60000) return;
|
|
886
|
+
this.lastHealthCheck = now;
|
|
887
|
+
|
|
888
|
+
if (!this.client.ws || this.client.ws.status !== 0) {
|
|
889
|
+
this.log('warn', `${c.yellow}⚠ Discord client disconnected. Attempting reconnect...${c.reset}`);
|
|
890
|
+
try {
|
|
891
|
+
this.client.destroy();
|
|
892
|
+
await this.client.login(this.account.discord_token);
|
|
893
|
+
this.channel = await this.client.channels.fetch(this.account.channel_id).catch(() => null);
|
|
894
|
+
if (this.channel) {
|
|
895
|
+
this.log('success', 'Reconnected successfully.');
|
|
896
|
+
} else {
|
|
897
|
+
this.log('error', 'Reconnected but channel not found.');
|
|
898
|
+
}
|
|
899
|
+
} catch (e) {
|
|
900
|
+
this.log('error', `Reconnect failed: ${e.message}`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
1035
905
|
// ── Main Non-Blocking Grind Scheduler ───────────────────────
|
|
1036
906
|
async tick() {
|
|
1037
|
-
if (!this.running) return;
|
|
907
|
+
if (!this.running || shutdownCalled) return;
|
|
908
|
+
if (this.paused) {
|
|
909
|
+
this.setStatus(`${c.red}${c.bold}PAUSED (captcha/verification)${c.reset}`);
|
|
910
|
+
this.tickTimeout = setTimeout(() => this.tick(), 5000);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
1038
913
|
if (this.busy) {
|
|
1039
|
-
this.tickTimeout = setTimeout(() => this.tick(),
|
|
914
|
+
this.tickTimeout = setTimeout(() => this.tick(), 2000);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Global rate-limit cooldown
|
|
919
|
+
const now = Date.now();
|
|
920
|
+
if (now < this.globalCooldownUntil) {
|
|
921
|
+
const waitSec = Math.ceil((this.globalCooldownUntil - now) / 1000);
|
|
922
|
+
this.setStatus(`${c.yellow}rate limited (${waitSec}s)${c.reset}`);
|
|
923
|
+
this.tickTimeout = setTimeout(() => this.tick(), 2000);
|
|
1040
924
|
return;
|
|
1041
925
|
}
|
|
1042
926
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
{ key: 'cmd_tidy', cmd: 'tidy', cdKey: 'cd_tidy', defaultCd: 40, priority: 1 },
|
|
1055
|
-
// Gambling
|
|
1056
|
-
{ key: 'cmd_blackjack', cmd: 'blackjack', cdKey: 'cd_blackjack', defaultCd: 3, priority: 3 },
|
|
1057
|
-
{ key: 'cmd_cointoss', cmd: 'coinflip', cdKey: 'cd_cointoss', defaultCd: 2, priority: 3 },
|
|
1058
|
-
{ key: 'cmd_roulette', cmd: 'roulette', cdKey: 'cd_roulette', defaultCd: 3, priority: 3 },
|
|
1059
|
-
{ key: 'cmd_slots', cmd: 'slots', cdKey: 'cd_slots', defaultCd: 3, priority: 3 },
|
|
1060
|
-
{ key: 'cmd_snakeeyes', cmd: 'snakeeyes', cdKey: 'cd_snakeeyes', defaultCd: 3, priority: 3 },
|
|
1061
|
-
// Medium cooldown
|
|
1062
|
-
{ key: 'cmd_trivia', cmd: 'trivia', cdKey: 'cd_trivia', defaultCd: 10, priority: 2 },
|
|
1063
|
-
{ key: 'cmd_use', cmd: 'use', cdKey: 'cd_use', defaultCd: 10, priority: 1 },
|
|
1064
|
-
{ key: 'cmd_drops', cmd: 'drops', cdKey: 'cd_drops', defaultCd: 60, priority: 2 },
|
|
1065
|
-
{ key: 'cmd_stream', cmd: 'stream', cdKey: 'cd_stream', defaultCd: 600, priority: 8 },
|
|
1066
|
-
// Economy
|
|
1067
|
-
{ key: 'cmd_deposit', cmd: 'dep max', cdKey: 'cd_deposit', defaultCd: 60, priority: 7 },
|
|
1068
|
-
// Long cooldown / special
|
|
1069
|
-
{ key: 'cmd_daily', cmd: 'daily', cdKey: 'cd_daily', defaultCd: 86400, priority: 10 },
|
|
1070
|
-
{ key: 'cmd_weekly', cmd: 'weekly', cdKey: 'cd_weekly', defaultCd: 604800, priority: 10 },
|
|
1071
|
-
{ key: 'cmd_monthly', cmd: 'monthly', cdKey: 'cd_monthly', defaultCd: 2592000, priority: 10 },
|
|
1072
|
-
{ key: 'cmd_work', cmd: 'work shift', cdKey: 'cd_work', defaultCd: 3600, priority: 8 },
|
|
1073
|
-
{ key: 'cmd_scratch', cmd: 'scratch', cdKey: 'cd_scratch', defaultCd: 3600, priority: 6 },
|
|
1074
|
-
{ key: 'cmd_adventure', cmd: 'adventure', cdKey: 'cd_adventure', defaultCd: 300, priority: 6 },
|
|
1075
|
-
// System
|
|
1076
|
-
{ key: 'cmd_alert', cmd: 'alert', cdKey: 'cd_alert', defaultCd: 300, priority: 9 },
|
|
1077
|
-
];
|
|
1078
|
-
|
|
1079
|
-
const enabledCommands = commandMap.filter((ci) => this.account[ci.key] === true || this.account[ci.key] === 1);
|
|
1080
|
-
|
|
1081
|
-
if (enabledCommands.length === 0) {
|
|
927
|
+
// Periodic health check
|
|
928
|
+
try { await this.healthCheck(); } catch (e) {
|
|
929
|
+
this.log('error', `Health check error: ${e.message}`);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Rebuild queue if it doesn't exist or is empty (e.g. after config refresh)
|
|
933
|
+
if (!this.commandQueue || this.commandQueue.size === 0) {
|
|
934
|
+
this.commandQueue = this.buildCommandQueue();
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (this.commandQueue.size === 0) {
|
|
1082
938
|
this.tickTimeout = setTimeout(() => this.tick(), 15000);
|
|
1083
939
|
return;
|
|
1084
940
|
}
|
|
1085
941
|
|
|
1086
|
-
|
|
1087
|
-
|
|
942
|
+
// Peek the top item — is it ready?
|
|
943
|
+
const top = this.commandQueue.peek();
|
|
944
|
+
if (top.nextRunAt > now) {
|
|
945
|
+
const waitMs = Math.min(top.nextRunAt - now, 2000);
|
|
946
|
+
this.setStatus(`${c.dim}waiting for cooldowns...${c.reset}`);
|
|
947
|
+
this.tickTimeout = setTimeout(() => this.tick(), waitMs);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
1088
950
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
951
|
+
// Pop the command, check Redis cooldown as a secondary gate
|
|
952
|
+
const item = this.commandQueue.pop();
|
|
953
|
+
const ready = await this.isCooldownReady(item.cmd);
|
|
954
|
+
if (!ready) {
|
|
955
|
+
const cd = (this.account[item.info.cdKey] || item.info.defaultCd);
|
|
956
|
+
item.nextRunAt = now + cd * 1000;
|
|
957
|
+
this.commandQueue.push(item);
|
|
958
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
959
|
+
return;
|
|
1095
960
|
}
|
|
1096
961
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const totalWait = cd + jitter;
|
|
1102
|
-
|
|
1103
|
-
// Optimistic lock via Redis
|
|
1104
|
-
await this.setCooldown(nextCmdToRun.cmd, totalWait);
|
|
1105
|
-
|
|
1106
|
-
// Global Jitter between commands
|
|
1107
|
-
const now = Date.now();
|
|
1108
|
-
const timeSinceLastCmd = now - (this.lastCommandRun || 0);
|
|
1109
|
-
const globalJitter = 1500 + Math.random() * 1500;
|
|
1110
|
-
if (timeSinceLastCmd < globalJitter) {
|
|
1111
|
-
await new Promise(r => setTimeout(r, globalJitter - timeSinceLastCmd));
|
|
1112
|
-
}
|
|
962
|
+
this.busy = true;
|
|
963
|
+
const cd = (this.account[item.info.cdKey] || item.info.defaultCd);
|
|
964
|
+
const jitter = 1 + Math.random() * 3;
|
|
965
|
+
const totalWait = cd + jitter;
|
|
1113
966
|
|
|
1114
|
-
|
|
1115
|
-
this.setStatus(`${c.white}pls ${nextCmdToRun.cmd}${c.reset}`);
|
|
1116
|
-
await this.runCommand(nextCmdToRun.cmd, prefix);
|
|
1117
|
-
|
|
1118
|
-
this.lastCommandRun = Date.now();
|
|
1119
|
-
// Re-apply cooldown based on actual finish time
|
|
1120
|
-
await this.setCooldown(nextCmdToRun.cmd, totalWait);
|
|
1121
|
-
|
|
1122
|
-
this.busy = false;
|
|
1123
|
-
this.cycleCount++;
|
|
967
|
+
await this.setCooldown(item.cmd, totalWait);
|
|
1124
968
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
969
|
+
// Global jitter between commands
|
|
970
|
+
const timeSinceLastCmd = now - (this.lastCommandRun || 0);
|
|
971
|
+
const globalJitter = 1500 + Math.random() * 1500;
|
|
972
|
+
if (timeSinceLastCmd < globalJitter) {
|
|
973
|
+
await new Promise(r => setTimeout(r, globalJitter - timeSinceLastCmd));
|
|
974
|
+
}
|
|
1131
975
|
|
|
1132
|
-
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
|
|
976
|
+
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
977
|
+
this.setStatus(`${c.white}pls ${item.cmd}${c.reset}`);
|
|
978
|
+
await this.runCommand(item.cmd, prefix);
|
|
979
|
+
|
|
980
|
+
this.lastCommandRun = Date.now();
|
|
981
|
+
await this.setCooldown(item.cmd, totalWait);
|
|
982
|
+
|
|
983
|
+
// Push the command back into the heap with its next available time
|
|
984
|
+
item.nextRunAt = Date.now() + totalWait * 1000;
|
|
985
|
+
this.commandQueue.push(item);
|
|
986
|
+
|
|
987
|
+
this.busy = false;
|
|
988
|
+
this.cycleCount++;
|
|
989
|
+
|
|
990
|
+
if (this.cycleCount > 0 && this.cycleCount % 10 === 0) this.printStats();
|
|
991
|
+
if (this.cycleCount > 0 && this.cycleCount % 20 === 0) {
|
|
992
|
+
this.busy = true;
|
|
993
|
+
await this.checkBalance();
|
|
994
|
+
this.busy = false;
|
|
1136
995
|
}
|
|
996
|
+
|
|
997
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
1137
998
|
}
|
|
1138
999
|
|
|
1139
1000
|
async grindLoop() {
|
|
1140
1001
|
if (this.running) return;
|
|
1141
1002
|
this.running = true;
|
|
1142
1003
|
this.busy = false;
|
|
1004
|
+
this.paused = false;
|
|
1143
1005
|
this.cycleCount = 0;
|
|
1144
1006
|
this.lastCommandRun = 0;
|
|
1007
|
+
this.commandQueue = this.buildCommandQueue();
|
|
1008
|
+
this.lastHealthCheck = Date.now();
|
|
1145
1009
|
|
|
1146
1010
|
this.configInterval = setInterval(async () => {
|
|
1147
1011
|
if (!this.running) return;
|
|
1148
1012
|
await this.refreshConfig();
|
|
1013
|
+
|
|
1014
|
+
// Dashboard can un-pause a captcha-paused worker by re-activating it
|
|
1015
|
+
if (this.account.active && this.paused) {
|
|
1016
|
+
this.log('success', 'Account re-activated from dashboard! Resuming from captcha pause...');
|
|
1017
|
+
this.paused = false;
|
|
1018
|
+
this.busy = false;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1149
1021
|
if (!this.account.active && !this.busy) {
|
|
1150
1022
|
this.log('warn', 'Account deactivated from dashboard. Pausing...');
|
|
1151
1023
|
this.busy = true;
|
|
1152
|
-
} else if (this.account.active && this.busy) {
|
|
1024
|
+
} else if (this.account.active && this.busy && !this.paused) {
|
|
1153
1025
|
this.log('success', 'Account re-activated! Resuming...');
|
|
1154
1026
|
this.busy = false;
|
|
1155
1027
|
}
|
|
1028
|
+
|
|
1029
|
+
// Rebuild the command queue on config refresh so newly enabled/disabled commands take effect
|
|
1030
|
+
this.commandQueue = this.buildCommandQueue();
|
|
1156
1031
|
}, 15000);
|
|
1157
1032
|
|
|
1158
|
-
|
|
1033
|
+
const safeTickLoop = async () => {
|
|
1034
|
+
try {
|
|
1035
|
+
await this.tick();
|
|
1036
|
+
} catch (err) {
|
|
1037
|
+
this.log('error', `Unhandled tick error: ${err.message}`);
|
|
1038
|
+
this.busy = false;
|
|
1039
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
1040
|
+
if (this.running && !shutdownCalled) {
|
|
1041
|
+
this.tickTimeout = setTimeout(() => safeTickLoop(), 100);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
safeTickLoop();
|
|
1159
1047
|
}
|
|
1160
1048
|
|
|
1161
1049
|
async refreshConfig() {
|
|
@@ -1172,10 +1060,8 @@ class AccountWorker {
|
|
|
1172
1060
|
}
|
|
1173
1061
|
|
|
1174
1062
|
async start() {
|
|
1175
|
-
if (!this.account.discord_token) { this.log('error', 'No
|
|
1176
|
-
if (!this.account.channel_id) { this.log('error', 'No channel
|
|
1177
|
-
|
|
1178
|
-
this.log('info', 'Connecting...');
|
|
1063
|
+
if (!this.account.discord_token) { this.log('error', 'No token'); return; }
|
|
1064
|
+
if (!this.account.channel_id) { this.log('error', 'No channel'); return; }
|
|
1179
1065
|
|
|
1180
1066
|
return new Promise((resolve) => {
|
|
1181
1067
|
this.client.on('ready', async () => {
|
|
@@ -1188,41 +1074,33 @@ class AccountWorker {
|
|
|
1188
1074
|
});
|
|
1189
1075
|
} catch { /* silent */ }
|
|
1190
1076
|
|
|
1191
|
-
this.log('success', `Logged in! ${c.dim}(${this.client.guilds.cache.size} servers)${c.reset}`);
|
|
1192
1077
|
this.channel = await this.client.channels.fetch(this.account.channel_id).catch(() => null);
|
|
1193
|
-
|
|
1194
1078
|
if (!this.channel) {
|
|
1195
|
-
this.log('error', `Channel
|
|
1079
|
+
this.log('error', `Channel not found`);
|
|
1196
1080
|
resolve(); return;
|
|
1197
1081
|
}
|
|
1198
1082
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
{ key: '
|
|
1203
|
-
{ key: '
|
|
1204
|
-
{ key: '
|
|
1205
|
-
{ key: '
|
|
1206
|
-
{ key: '
|
|
1207
|
-
{ key: '
|
|
1208
|
-
{ key: '
|
|
1209
|
-
{ key: '
|
|
1210
|
-
{ key: '
|
|
1211
|
-
{ key: '
|
|
1212
|
-
{ key: '
|
|
1213
|
-
{ key: '
|
|
1214
|
-
{ key: 'cmd_deposit', label: 'deposit' }, { key: 'cmd_drops', label: 'drops' },
|
|
1215
|
-
{ key: 'cmd_alert', label: 'alert' },
|
|
1083
|
+
const enabledCmds = [
|
|
1084
|
+
{ key: 'cmd_hunt', l: 'hunt' }, { key: 'cmd_dig', l: 'dig' },
|
|
1085
|
+
{ key: 'cmd_fish', l: 'fish' }, { key: 'cmd_beg', l: 'beg' },
|
|
1086
|
+
{ key: 'cmd_search', l: 'search' }, { key: 'cmd_hl', l: 'hl' },
|
|
1087
|
+
{ key: 'cmd_crime', l: 'crime' }, { key: 'cmd_pm', l: 'pm' },
|
|
1088
|
+
{ key: 'cmd_daily', l: 'daily' }, { key: 'cmd_weekly', l: 'weekly' },
|
|
1089
|
+
{ key: 'cmd_monthly', l: 'monthly' }, { key: 'cmd_work', l: 'work' },
|
|
1090
|
+
{ key: 'cmd_stream', l: 'stream' }, { key: 'cmd_scratch', l: 'scratch' },
|
|
1091
|
+
{ key: 'cmd_adventure', l: 'adv' }, { key: 'cmd_farm', l: 'farm' },
|
|
1092
|
+
{ key: 'cmd_tidy', l: 'tidy' }, { key: 'cmd_blackjack', l: 'bj' },
|
|
1093
|
+
{ key: 'cmd_cointoss', l: 'flip' }, { key: 'cmd_roulette', l: 'roul' },
|
|
1094
|
+
{ key: 'cmd_slots', l: 'slots' }, { key: 'cmd_snakeeyes', l: 'snake' },
|
|
1095
|
+
{ key: 'cmd_trivia', l: 'trivia' }, { key: 'cmd_use', l: 'use' },
|
|
1096
|
+
{ key: 'cmd_deposit', l: 'dep' }, { key: 'cmd_drops', l: 'drops' },
|
|
1097
|
+
{ key: 'cmd_alert', l: 'alert' },
|
|
1216
1098
|
].filter((ci) => this.account[ci.key] === true || this.account[ci.key] === 1);
|
|
1217
1099
|
|
|
1218
|
-
const
|
|
1219
|
-
this.log('
|
|
1220
|
-
if (this.account.bet_amount) {
|
|
1221
|
-
this.log('info', `Bet amount: ${c.yellow}⏣ ${this.account.bet_amount.toLocaleString()}${c.reset}`);
|
|
1222
|
-
}
|
|
1100
|
+
const cmdList = enabledCmds.map(ci => ci.l).join(' ');
|
|
1101
|
+
this.log('success', `${this.tag} ${c.dim}#${(this.channel.name || '?').substring(0, 12)}${c.reset} ${c.dim}[${enabledCmds.length} cmds: ${cmdList}]${c.reset}`);
|
|
1223
1102
|
|
|
1224
1103
|
await this.checkBalance();
|
|
1225
|
-
console.log('');
|
|
1226
1104
|
this.grindLoop();
|
|
1227
1105
|
resolve();
|
|
1228
1106
|
});
|
|
@@ -1233,8 +1111,10 @@ class AccountWorker {
|
|
|
1233
1111
|
|
|
1234
1112
|
stop() {
|
|
1235
1113
|
this.running = false;
|
|
1114
|
+
this.paused = false;
|
|
1236
1115
|
if (this.tickTimeout) clearTimeout(this.tickTimeout);
|
|
1237
1116
|
if (this.configInterval) clearInterval(this.configInterval);
|
|
1117
|
+
this.commandQueue = null;
|
|
1238
1118
|
try { this.client.destroy(); } catch {}
|
|
1239
1119
|
}
|
|
1240
1120
|
}
|
|
@@ -1245,21 +1125,37 @@ class AccountWorker {
|
|
|
1245
1125
|
|
|
1246
1126
|
async function start(apiKey, apiUrl) {
|
|
1247
1127
|
API_KEY = apiKey;
|
|
1248
|
-
API_URL = apiUrl;
|
|
1128
|
+
API_URL = apiUrl || process.env.DANKGRINDER_URL || 'http://localhost:3000';
|
|
1129
|
+
REDIS_URL = process.env.REDIS_URL || '';
|
|
1249
1130
|
initRedis();
|
|
1250
1131
|
|
|
1251
|
-
// Clear screen & show big banner
|
|
1252
1132
|
process.stdout.write('\x1b[2J\x1b[H');
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1133
|
+
const tw = Math.min(process.stdout.columns || 80, 78);
|
|
1134
|
+
const bar = c.dim + '─'.repeat(tw) + c.reset;
|
|
1135
|
+
|
|
1136
|
+
console.log(colorBanner());
|
|
1137
|
+
console.log(
|
|
1138
|
+
` ${rgb(139, 92, 246)}v4.1${c.reset}` +
|
|
1139
|
+
` ${c.dim}·${c.reset} ${c.white}30 Commands${c.reset}` +
|
|
1140
|
+
` ${c.dim}·${c.reset} ${rgb(34, 211, 238)}Priority Queue${c.reset}` +
|
|
1141
|
+
` ${c.dim}·${c.reset} ${rgb(52, 211, 153)}Redis Cooldowns${c.reset}` +
|
|
1142
|
+
` ${c.dim}·${c.reset} ${rgb(251, 191, 36)}Smart AI${c.reset}`
|
|
1143
|
+
);
|
|
1144
|
+
console.log(bar);
|
|
1145
|
+
|
|
1146
|
+
const checks = [];
|
|
1147
|
+
checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}API${c.reset}`);
|
|
1148
|
+
checks.push(REDIS_URL ? `${rgb(52, 211, 153)}✓${c.reset} ${c.white}Redis${c.reset}` : `${c.dim}○ Redis${c.reset}`);
|
|
1149
|
+
console.log(` ${checks.join(' ')}`);
|
|
1256
1150
|
|
|
1257
|
-
log('info',
|
|
1258
|
-
log('info', `Redis: ${c.dim}${REDIS_URL.replace(/:[^:]+@/, ':***@')}${c.reset}`);
|
|
1259
|
-
log('info', 'Fetching accounts...');
|
|
1151
|
+
log('info', `${c.dim}Fetching accounts...${c.reset}`);
|
|
1260
1152
|
|
|
1261
1153
|
const data = await fetchConfig();
|
|
1262
|
-
if (!data) {
|
|
1154
|
+
if (!data) {
|
|
1155
|
+
log('error', `Cannot connect to API`);
|
|
1156
|
+
log('error', `Pass ${c.white}--url${c.reset} with your Railway URL or set ${c.white}DANKGRINDER_URL${c.reset}`);
|
|
1157
|
+
process.exit(1);
|
|
1158
|
+
}
|
|
1263
1159
|
|
|
1264
1160
|
const { accounts } = data;
|
|
1265
1161
|
if (!accounts || accounts.length === 0) {
|
|
@@ -1267,7 +1163,10 @@ async function start(apiKey, apiUrl) {
|
|
|
1267
1163
|
process.exit(1);
|
|
1268
1164
|
}
|
|
1269
1165
|
|
|
1270
|
-
|
|
1166
|
+
checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}${accounts.length} Account${accounts.length > 1 ? 's' : ''}${c.reset}`);
|
|
1167
|
+
process.stdout.write(c.cursorUp(1));
|
|
1168
|
+
process.stdout.write(c.clearLine + '\r');
|
|
1169
|
+
console.log(` ${checks.join(' ')}`);
|
|
1271
1170
|
console.log('');
|
|
1272
1171
|
|
|
1273
1172
|
for (let i = 0; i < accounts.length; i++) {
|
|
@@ -1279,26 +1178,31 @@ async function start(apiKey, apiUrl) {
|
|
|
1279
1178
|
console.log('');
|
|
1280
1179
|
startTime = Date.now();
|
|
1281
1180
|
dashboardStarted = true;
|
|
1282
|
-
process.stdout.write(c.hide);
|
|
1181
|
+
process.stdout.write(c.hide);
|
|
1283
1182
|
|
|
1284
|
-
// Start the live dashboard refresh loop
|
|
1285
1183
|
setInterval(() => renderDashboard(), 1000);
|
|
1286
|
-
renderDashboard();
|
|
1184
|
+
renderDashboard();
|
|
1287
1185
|
|
|
1288
1186
|
process.on('SIGINT', () => {
|
|
1289
|
-
|
|
1187
|
+
shutdownCalled = true;
|
|
1188
|
+
process.stdout.write(c.show);
|
|
1290
1189
|
dashboardStarted = false;
|
|
1291
|
-
|
|
1292
|
-
console.log('');
|
|
1293
|
-
log(
|
|
1294
|
-
console.log(
|
|
1295
|
-
for (const
|
|
1296
|
-
const rate =
|
|
1297
|
-
console.log(
|
|
1298
|
-
|
|
1190
|
+
const sepBar = rgb(139, 92, 246) + c.bold + '═'.repeat(tw) + c.reset;
|
|
1191
|
+
console.log('\n');
|
|
1192
|
+
console.log(` ${rgb(251, 191, 36)}${c.bold}Session Summary${c.reset}`);
|
|
1193
|
+
console.log(sepBar);
|
|
1194
|
+
for (const wk of workers) {
|
|
1195
|
+
const rate = wk.stats.commands > 0 ? ((wk.stats.successes / wk.stats.commands) * 100).toFixed(0) : 0;
|
|
1196
|
+
console.log(
|
|
1197
|
+
` ${wk.color}${c.bold}${(wk.username || '?').padEnd(18)}${c.reset}` +
|
|
1198
|
+
` ${rgb(52, 211, 153)}+⏣ ${wk.stats.coins.toLocaleString().padStart(8)}${c.reset}` +
|
|
1199
|
+
` ${c.dim}${wk.stats.commands.toString().padStart(4)} cmds${c.reset}` +
|
|
1200
|
+
` ${c.dim}${rate}% success${c.reset}`
|
|
1201
|
+
);
|
|
1202
|
+
wk.stop();
|
|
1299
1203
|
}
|
|
1300
|
-
console.log(
|
|
1301
|
-
console.log(` ${
|
|
1204
|
+
console.log(sepBar);
|
|
1205
|
+
console.log(` ${rgb(251, 191, 36)}${c.bold}Total: +⏣ ${totalCoins.toLocaleString()}${c.reset} ${c.dim}in ${formatUptime()}${c.reset}`);
|
|
1302
1206
|
console.log('');
|
|
1303
1207
|
setTimeout(() => process.exit(0), 2000);
|
|
1304
1208
|
});
|