dankgrinder 3.0.0 → 4.0.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 +856 -202
- package/package.json +3 -2
package/lib/grinder.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
const { Client } = require('discord.js-selfbot-v13');
|
|
2
|
+
const Redis = require('ioredis');
|
|
2
3
|
|
|
3
|
-
// ── Terminal Colors
|
|
4
|
+
// ── Terminal Colors & ANSI ───────────────────────────────────
|
|
4
5
|
const c = {
|
|
5
6
|
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
|
|
6
7
|
green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
7
8
|
magenta: '\x1b[35m', white: '\x1b[37m', blue: '\x1b[34m',
|
|
8
9
|
bgGreen: '\x1b[42m', bgRed: '\x1b[41m', bgYellow: '\x1b[43m', bgCyan: '\x1b[46m',
|
|
9
|
-
bgMagenta: '\x1b[45m', bgBlue: '\x1b[44m',
|
|
10
|
+
bgMagenta: '\x1b[45m', bgBlue: '\x1b[44m', bgWhite: '\x1b[47m',
|
|
11
|
+
// Cursor control
|
|
12
|
+
clearLine: '\x1b[2K',
|
|
13
|
+
cursorUp: (n) => `\x1b[${n}A`,
|
|
14
|
+
cursorTo: (col) => `\x1b[${col}G`,
|
|
15
|
+
hide: '\x1b[?25l',
|
|
16
|
+
show: '\x1b[?25h',
|
|
17
|
+
saveCursor: '\x1b7',
|
|
18
|
+
restoreCursor: '\x1b8',
|
|
10
19
|
};
|
|
11
20
|
|
|
12
21
|
const WORKER_COLORS = [c.cyan, c.magenta, c.yellow, c.green, c.blue, c.red];
|
|
@@ -27,22 +36,140 @@ const SAFE_CRIME_OPTIONS = [
|
|
|
27
36
|
|
|
28
37
|
let API_KEY = '';
|
|
29
38
|
let API_URL = '';
|
|
39
|
+
let REDIS_URL = process.env.REDIS_URL || 'redis://default:qXcezFjDHlCDtakRUZJmvsEzHoBgdLHi@shortline.proxy.rlwy.net:32007';
|
|
40
|
+
let redis = null;
|
|
30
41
|
const workers = [];
|
|
31
42
|
|
|
43
|
+
function initRedis() {
|
|
44
|
+
if (!redis) {
|
|
45
|
+
try {
|
|
46
|
+
redis = new Redis(REDIS_URL, { maxRetriesPerRequest: null, lazyConnect: true });
|
|
47
|
+
redis.connect().catch(() => {});
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.error('Redis connection failed', e);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Live Dashboard State ─────────────────────────────────────
|
|
55
|
+
let dashboardLines = 0;
|
|
56
|
+
let dashboardStarted = false;
|
|
57
|
+
let totalBalance = 0;
|
|
58
|
+
let totalCoins = 0;
|
|
59
|
+
let totalCommands = 0;
|
|
60
|
+
let startTime = Date.now();
|
|
61
|
+
const recentLogs = []; // Ring buffer of last N log entries
|
|
62
|
+
const MAX_LOGS = 6;
|
|
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
|
+
`;
|
|
78
|
+
|
|
79
|
+
function formatUptime() {
|
|
80
|
+
const s = Math.floor((Date.now() - startTime) / 1000);
|
|
81
|
+
const h = Math.floor(s / 3600);
|
|
82
|
+
const m = Math.floor((s % 3600) / 60);
|
|
83
|
+
const sec = s % 60;
|
|
84
|
+
return `${h}h ${m}m ${sec}s`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatCoins(n) {
|
|
88
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
89
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
90
|
+
return n.toLocaleString();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function renderDashboard() {
|
|
94
|
+
if (!dashboardStarted || workers.length === 0) return;
|
|
95
|
+
|
|
96
|
+
// Compute totals
|
|
97
|
+
totalBalance = 0; totalCoins = 0; totalCommands = 0;
|
|
98
|
+
for (const w of workers) {
|
|
99
|
+
totalBalance += w.stats.balance || 0;
|
|
100
|
+
totalCoins += w.stats.coins || 0;
|
|
101
|
+
totalCommands += w.stats.commands || 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const lines = [];
|
|
105
|
+
const width = Math.min(process.stdout.columns || 80, 90);
|
|
106
|
+
const sep = c.dim + '─'.repeat(width) + c.reset;
|
|
107
|
+
|
|
108
|
+
// Header bar
|
|
109
|
+
lines.push(sep);
|
|
110
|
+
lines.push(
|
|
111
|
+
` ${c.yellow}${c.bold}⏣ ${formatCoins(totalBalance)}${c.reset}` +
|
|
112
|
+
` ${c.dim}│${c.reset} ${c.green}+⏣ ${formatCoins(totalCoins)}${c.reset} earned` +
|
|
113
|
+
` ${c.dim}│${c.reset} ${c.white}${totalCommands}${c.reset} cmds` +
|
|
114
|
+
` ${c.dim}│${c.reset} ${c.dim}⏱ ${formatUptime()}${c.reset}`
|
|
115
|
+
);
|
|
116
|
+
lines.push(sep);
|
|
117
|
+
|
|
118
|
+
// Per-account rows
|
|
119
|
+
for (const w of workers) {
|
|
120
|
+
const status = w.lastStatus || 'idle';
|
|
121
|
+
const statusColor = w.running ? (w.busy ? c.yellow : c.green) : c.red;
|
|
122
|
+
const dot = w.running ? (w.busy ? '◉' : '●') : '○';
|
|
123
|
+
const bal = w.stats.balance > 0 ? `${c.dim}⏣${c.reset} ${c.white}${formatCoins(w.stats.balance)}${c.reset}` : `${c.dim}⏣ ---${c.reset}`;
|
|
124
|
+
const earned = w.stats.coins > 0 ? `${c.green}+${formatCoins(w.stats.coins)}${c.reset}` : `${c.dim}+0${c.reset}`;
|
|
125
|
+
const cmds = `${c.dim}${w.stats.commands}cmd${c.reset}`;
|
|
126
|
+
const name = `${w.color}${c.bold}${(w.username || 'Account').substring(0, 14).padEnd(14)}${c.reset}`;
|
|
127
|
+
|
|
128
|
+
lines.push(
|
|
129
|
+
` ${statusColor}${dot}${c.reset} ${name} ${bal} ${earned} ${cmds} ${c.dim}→${c.reset} ${status.substring(0, 35)}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
lines.push(sep);
|
|
133
|
+
|
|
134
|
+
// Scrolling log section (last 6 events)
|
|
135
|
+
for (const entry of recentLogs) {
|
|
136
|
+
lines.push(` ${c.dim}${entry}${c.reset}`);
|
|
137
|
+
}
|
|
138
|
+
if (recentLogs.length > 0) lines.push(sep);
|
|
139
|
+
|
|
140
|
+
// Move cursor up to overwrite previous dashboard
|
|
141
|
+
if (dashboardLines > 0) {
|
|
142
|
+
process.stdout.write(c.cursorUp(dashboardLines));
|
|
143
|
+
}
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
process.stdout.write(c.clearLine + '\r' + line + '\n');
|
|
146
|
+
}
|
|
147
|
+
dashboardLines = lines.length;
|
|
148
|
+
}
|
|
149
|
+
|
|
32
150
|
function log(type, msg, label) {
|
|
33
151
|
const time = new Date().toLocaleTimeString('en-US', { hour12: true, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
34
152
|
const icons = {
|
|
35
|
-
info:
|
|
36
|
-
|
|
37
|
-
error: `${c.red}✗${c.reset}`,
|
|
38
|
-
warn: `${c.yellow}!${c.reset}`,
|
|
39
|
-
cmd: `${c.magenta}▸${c.reset}`,
|
|
40
|
-
coin: `${c.yellow}$${c.reset}`,
|
|
41
|
-
buy: `${c.blue}♦${c.reset}`,
|
|
42
|
-
bal: `${c.green}◈${c.reset}`,
|
|
153
|
+
info: `│`, success: `✓`, error: `✗`, warn: `!`,
|
|
154
|
+
cmd: `▸`, coin: `$`, buy: `♦`, bal: `◈`, debug: `⊙`,
|
|
43
155
|
};
|
|
44
156
|
const tag = label ? `${label} ` : '';
|
|
45
|
-
|
|
157
|
+
const stripped = msg.replace(/\x1b\[[0-9;]*m/g, ''); // Strip ANSI for log buffer
|
|
158
|
+
if (dashboardStarted) {
|
|
159
|
+
// Add to scrolling log buffer
|
|
160
|
+
recentLogs.push(`${time} ${icons[type] || '│'} ${tag}${stripped}`.substring(0, 80));
|
|
161
|
+
while (recentLogs.length > MAX_LOGS) recentLogs.shift();
|
|
162
|
+
renderDashboard();
|
|
163
|
+
} else {
|
|
164
|
+
const colorIcons = {
|
|
165
|
+
info: `${c.cyan}│${c.reset}`, success: `${c.green}✓${c.reset}`,
|
|
166
|
+
error: `${c.red}✗${c.reset}`, warn: `${c.yellow}!${c.reset}`,
|
|
167
|
+
cmd: `${c.magenta}▸${c.reset}`, coin: `${c.yellow}$${c.reset}`,
|
|
168
|
+
buy: `${c.blue}♦${c.reset}`, bal: `${c.green}◈${c.reset}`,
|
|
169
|
+
debug: `${c.dim}⊙${c.reset}`,
|
|
170
|
+
};
|
|
171
|
+
console.log(` ${c.dim}${time}${c.reset} ${colorIcons[type] || colorIcons.info} ${tag}${msg}`);
|
|
172
|
+
}
|
|
46
173
|
}
|
|
47
174
|
|
|
48
175
|
async function fetchConfig() {
|
|
@@ -56,12 +183,12 @@ async function fetchConfig() {
|
|
|
56
183
|
} catch (err) { log('error', `Cannot reach API: ${err.message}`); return null; }
|
|
57
184
|
}
|
|
58
185
|
|
|
59
|
-
async function sendLog(command, response, status) {
|
|
186
|
+
async function sendLog(accountName, command, response, status) {
|
|
60
187
|
try {
|
|
61
188
|
await fetch(`${API_URL}/api/grinder/log`, {
|
|
62
189
|
method: 'POST',
|
|
63
190
|
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
64
|
-
body: JSON.stringify({ command, response, status }),
|
|
191
|
+
body: JSON.stringify({ account_name: accountName, command, response, status }),
|
|
65
192
|
});
|
|
66
193
|
} catch { /* silent */ }
|
|
67
194
|
}
|
|
@@ -79,7 +206,6 @@ function safeParseJSON(str, fallback = []) {
|
|
|
79
206
|
// ── Coin Parser ──────────────────────────────────────────────
|
|
80
207
|
function parseCoins(text) {
|
|
81
208
|
if (!text) return 0;
|
|
82
|
-
// Match patterns like "⏣ 1,250" or "+1,250 coins" or "**1,250**"
|
|
83
209
|
const patterns = [
|
|
84
210
|
/[⏣💰]\s*[\d,]+/g,
|
|
85
211
|
/\+\s*[\d,]+\s*coins?/gi,
|
|
@@ -88,6 +214,8 @@ function parseCoins(text) {
|
|
|
88
214
|
/earned\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
89
215
|
/found\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
90
216
|
/got\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
217
|
+
/won\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
218
|
+
/gained\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
91
219
|
];
|
|
92
220
|
let total = 0;
|
|
93
221
|
for (const pat of patterns) {
|
|
@@ -119,42 +247,66 @@ function getFullText(msg) {
|
|
|
119
247
|
return text;
|
|
120
248
|
}
|
|
121
249
|
|
|
122
|
-
|
|
250
|
+
function getAllButtons(msg) {
|
|
251
|
+
const buttons = [];
|
|
252
|
+
if (msg.components) {
|
|
253
|
+
for (const row of msg.components) {
|
|
254
|
+
if (row.components) {
|
|
255
|
+
for (const comp of row.components) {
|
|
256
|
+
if (comp.type === 2) buttons.push(comp);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return buttons;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function getAllSelectMenus(msg) {
|
|
265
|
+
const menus = [];
|
|
266
|
+
if (msg.components) {
|
|
267
|
+
for (const row of msg.components) {
|
|
268
|
+
if (row.components) {
|
|
269
|
+
for (const comp of row.components) {
|
|
270
|
+
if (comp.type === 3) menus.push(comp);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return menus;
|
|
276
|
+
}
|
|
277
|
+
|
|
123
278
|
function pickSafeButton(buttons, safeList) {
|
|
124
279
|
if (!buttons || buttons.length === 0) return null;
|
|
125
|
-
|
|
126
|
-
// First check user-configured safe answers
|
|
127
280
|
if (safeList && safeList.length > 0) {
|
|
128
281
|
for (const btn of buttons) {
|
|
129
282
|
const label = (btn.label || '').toLowerCase();
|
|
130
283
|
if (safeList.some((s) => label.includes(s.toLowerCase()))) return btn;
|
|
131
284
|
}
|
|
132
285
|
}
|
|
133
|
-
|
|
134
|
-
// Then check built-in safe list
|
|
135
286
|
for (const btn of buttons) {
|
|
136
287
|
const label = (btn.label || '').toLowerCase();
|
|
137
288
|
if (SAFE_SEARCH_LOCATIONS.some((s) => label.includes(s))) return btn;
|
|
138
289
|
if (SAFE_CRIME_OPTIONS.some((s) => label.includes(s))) return btn;
|
|
139
290
|
}
|
|
140
|
-
|
|
141
|
-
// Fallback: pick random
|
|
142
291
|
const clickable = buttons.filter((b) => !b.disabled);
|
|
143
292
|
return clickable.length > 0 ? clickable[Math.floor(Math.random() * clickable.length)] : null;
|
|
144
293
|
}
|
|
145
294
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (msg
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
295
|
+
// Debug: log all embed/component data for a message
|
|
296
|
+
function debugMessage(msg, label) {
|
|
297
|
+
if (!msg) return;
|
|
298
|
+
const text = getFullText(msg);
|
|
299
|
+
log('debug', `${label || 'MSG'}: ${c.dim}${text.substring(0, 120)}${c.reset}`);
|
|
300
|
+
const buttons = getAllButtons(msg);
|
|
301
|
+
if (buttons.length > 0) {
|
|
302
|
+
log('debug', ` Buttons: ${buttons.map(b => `[${b.label || b.emoji?.name || '?'}${b.disabled ? ' (disabled)' : ''}]`).join(' ')}`);
|
|
303
|
+
}
|
|
304
|
+
const menus = getAllSelectMenus(msg);
|
|
305
|
+
if (menus.length > 0) {
|
|
306
|
+
for (const m of menus) {
|
|
307
|
+
log('debug', ` SelectMenu: ${(m.options || []).map(o => o.label).join(', ')}`);
|
|
155
308
|
}
|
|
156
309
|
}
|
|
157
|
-
return buttons;
|
|
158
310
|
}
|
|
159
311
|
|
|
160
312
|
// ══════════════════════════════════════════════════════════════
|
|
@@ -169,22 +321,29 @@ class AccountWorker {
|
|
|
169
321
|
this.client = new Client();
|
|
170
322
|
this.channel = null;
|
|
171
323
|
this.running = false;
|
|
324
|
+
this.busy = false;
|
|
172
325
|
this.username = account.label || `Account ${idx + 1}`;
|
|
173
|
-
|
|
174
|
-
// Session stats
|
|
326
|
+
this.tickTimeout = null;
|
|
175
327
|
this.stats = { coins: 0, commands: 0, successes: 0, errors: 0, balance: 0 };
|
|
328
|
+
this.lastRunTime = {};
|
|
329
|
+
this.cycleCount = 0;
|
|
330
|
+
this.lastCommandRun = 0;
|
|
176
331
|
}
|
|
177
332
|
|
|
178
|
-
get tag() {
|
|
179
|
-
return `${this.color}${c.bold}${this.username}${c.reset}`;
|
|
180
|
-
}
|
|
181
|
-
|
|
333
|
+
get tag() { return `${this.color}${c.bold}${this.username}${c.reset}`; }
|
|
182
334
|
log(type, msg) { log(type, msg, this.tag); }
|
|
183
335
|
|
|
336
|
+
// Update the live dashboard status for this worker
|
|
337
|
+
setStatus(text) {
|
|
338
|
+
this.lastStatus = text;
|
|
339
|
+
renderDashboard();
|
|
340
|
+
}
|
|
341
|
+
|
|
184
342
|
waitForDankMemer(timeout = 15000) {
|
|
185
343
|
return new Promise((resolve) => {
|
|
186
344
|
const timer = setTimeout(() => {
|
|
187
345
|
this.client.removeListener('messageCreate', handler);
|
|
346
|
+
this.client.removeListener('messageUpdate', updateHandler);
|
|
188
347
|
resolve(null);
|
|
189
348
|
}, timeout);
|
|
190
349
|
const self = this;
|
|
@@ -192,75 +351,224 @@ class AccountWorker {
|
|
|
192
351
|
if (msg.author.id === DANK_MEMER_ID && msg.channel.id === self.channel.id) {
|
|
193
352
|
clearTimeout(timer);
|
|
194
353
|
self.client.removeListener('messageCreate', handler);
|
|
354
|
+
self.client.removeListener('messageUpdate', updateHandler);
|
|
195
355
|
resolve(msg);
|
|
196
356
|
}
|
|
197
357
|
}
|
|
358
|
+
function updateHandler(oldMsg, newMsg) {
|
|
359
|
+
if (newMsg.author?.id === DANK_MEMER_ID && newMsg.channel?.id === self.channel.id) {
|
|
360
|
+
clearTimeout(timer);
|
|
361
|
+
self.client.removeListener('messageCreate', handler);
|
|
362
|
+
self.client.removeListener('messageUpdate', updateHandler);
|
|
363
|
+
resolve(newMsg);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
198
366
|
this.client.on('messageCreate', handler);
|
|
367
|
+
this.client.on('messageUpdate', updateHandler);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Wait specifically for message edits (Dank Memer often edits messages)
|
|
372
|
+
waitForMessageUpdate(msgId, timeout = 10000) {
|
|
373
|
+
return new Promise((resolve) => {
|
|
374
|
+
const timer = setTimeout(() => {
|
|
375
|
+
this.client.removeListener('messageUpdate', handler);
|
|
376
|
+
resolve(null);
|
|
377
|
+
}, timeout);
|
|
378
|
+
const self = this;
|
|
379
|
+
function handler(oldMsg, newMsg) {
|
|
380
|
+
if (newMsg.id === msgId && newMsg.channel?.id === self.channel.id) {
|
|
381
|
+
clearTimeout(timer);
|
|
382
|
+
self.client.removeListener('messageUpdate', handler);
|
|
383
|
+
resolve(newMsg);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
this.client.on('messageUpdate', handler);
|
|
199
387
|
});
|
|
200
388
|
}
|
|
201
389
|
|
|
202
390
|
// ── Needs Item Detection ────────────────────────────────────
|
|
203
391
|
needsItem(text) {
|
|
204
392
|
const lower = text.toLowerCase();
|
|
205
|
-
if (lower.includes("don't have a shovel") || lower.includes('need
|
|
393
|
+
if (lower.includes("don't have a shovel") || (lower.includes('need') && lower.includes('shovel')) || lower.includes('you need a shovel'))
|
|
206
394
|
return 'shovel';
|
|
207
|
-
if (lower.includes("don't have a fishing") || lower.includes('need a fishing'))
|
|
395
|
+
if (lower.includes("don't have a fishing") || lower.includes('need a fishing') || lower.includes('you need a fishing pole'))
|
|
208
396
|
return 'fishing pole';
|
|
209
|
-
if (lower.includes("don't have a hunting rifle") || lower.includes('need a hunting rifle'))
|
|
397
|
+
if (lower.includes("don't have a hunting rifle") || lower.includes('need a hunting rifle') || lower.includes('you need a rifle'))
|
|
210
398
|
return 'hunting rifle';
|
|
211
399
|
return null;
|
|
212
400
|
}
|
|
213
401
|
|
|
214
|
-
async buyItem(itemName) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
402
|
+
async buyItem(itemName, quantity = 10) {
|
|
403
|
+
const MAX_RETRIES = 3;
|
|
404
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
405
|
+
this.log('buy', `Opening shop to buy ${c.bold}${quantity}x ${itemName}${c.reset}... (attempt ${attempt}/${MAX_RETRIES})`);
|
|
406
|
+
if (this.account.use_slash) {
|
|
407
|
+
await this.channel.sendSlash(DANK_MEMER_ID, 'shop', 'view').catch(() => this.channel.send('pls shop view'));
|
|
408
|
+
} else {
|
|
409
|
+
await this.channel.send('pls shop view');
|
|
410
|
+
}
|
|
411
|
+
let response = await this.waitForDankMemer(10000);
|
|
412
|
+
if (!response) {
|
|
413
|
+
this.log('warn', 'No response to shop view command.');
|
|
414
|
+
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const responseText = getFullText(response).toLowerCase();
|
|
419
|
+
const hasShopComponents = (response.components || []).some(row =>
|
|
420
|
+
(row.components || []).some(comp => comp.type === 3 || (comp.type === 2 && comp.label && comp.label.toLowerCase().includes('buy')))
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
if (!hasShopComponents && (responseText.includes('lucky') || responseText.includes('event') || responseText.includes('for the rest of the day'))) {
|
|
424
|
+
this.log('warn', 'Got event response instead of shop. Retrying...');
|
|
425
|
+
await humanDelay(3000, 5000);
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
if (!hasShopComponents && responseText.includes('shop')) {
|
|
429
|
+
const shopUI = await this.waitForDankMemer(8000);
|
|
430
|
+
if (shopUI) response = shopUI;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Navigate to Coin Shop
|
|
434
|
+
let coinShopMenuId = null;
|
|
435
|
+
let coinShopOption = null;
|
|
436
|
+
for (const row of response.components || []) {
|
|
437
|
+
for (const comp of row.components || []) {
|
|
438
|
+
if (comp.type === 3) {
|
|
439
|
+
const opt = (comp.options || []).find(o => o.label && o.label.includes('Coin Shop'));
|
|
440
|
+
if (opt) { coinShopMenuId = comp.customId; coinShopOption = opt; }
|
|
441
|
+
}
|
|
231
442
|
}
|
|
232
443
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
444
|
+
|
|
445
|
+
if (coinShopMenuId && coinShopOption) {
|
|
446
|
+
this.log('buy', 'Navigating to Coin Shop...');
|
|
447
|
+
try {
|
|
448
|
+
await response.selectMenu(coinShopMenuId, [coinShopOption.value]);
|
|
449
|
+
const updatedMsg = await this.waitForDankMemer(8000);
|
|
450
|
+
if (updatedMsg) response = updatedMsg;
|
|
451
|
+
} catch (e) {
|
|
452
|
+
this.log('error', `Failed to open Coin Shop: ${e.message}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
await humanDelay(1000, 2000);
|
|
456
|
+
|
|
457
|
+
// Find Buy button
|
|
458
|
+
let buyBtn = null;
|
|
459
|
+
const searchName = itemName.toLowerCase().replace('hunting ', '').replace('fishing ', '');
|
|
460
|
+
for (const row of response.components || []) {
|
|
461
|
+
for (const comp of row.components || []) {
|
|
462
|
+
if (comp.type === 2 && comp.label && comp.label.toLowerCase().includes(searchName)) {
|
|
463
|
+
buyBtn = comp; break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (buyBtn) break;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (!buyBtn) {
|
|
470
|
+
this.log('warn', `Could not find Buy button for ${itemName} (attempt ${attempt})`);
|
|
471
|
+
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
472
|
+
return false;
|
|
236
473
|
}
|
|
474
|
+
|
|
475
|
+
this.log('buy', `Clicking Buy ${itemName}...`);
|
|
476
|
+
try { await buyBtn.click(); } catch (e) {
|
|
477
|
+
this.log('error', `Buy click failed: ${e.message}`);
|
|
478
|
+
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Handle Modal
|
|
483
|
+
const modal = await new Promise((resolve) => {
|
|
484
|
+
const timer = setTimeout(() => resolve(null), 8000);
|
|
485
|
+
const handler = (m) => {
|
|
486
|
+
clearTimeout(timer);
|
|
487
|
+
this.client.removeListener('interactionModalCreate', handler);
|
|
488
|
+
resolve(m);
|
|
489
|
+
};
|
|
490
|
+
this.client.on('interactionModalCreate', handler);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
if (modal) {
|
|
494
|
+
this.log('buy', `Submitting quantity ${c.bold}${quantity}${c.reset} in modal...`);
|
|
495
|
+
try {
|
|
496
|
+
const quantityInputId = modal.components[0].components[0].customId;
|
|
497
|
+
await fetch('https://discord.com/api/v9/interactions', {
|
|
498
|
+
method: 'POST',
|
|
499
|
+
headers: { 'Authorization': this.client.token, 'Content-Type': 'application/json' },
|
|
500
|
+
body: JSON.stringify({
|
|
501
|
+
type: 5, application_id: modal.applicationId,
|
|
502
|
+
channel_id: this.channel.id, guild_id: this.channel.guild?.id,
|
|
503
|
+
data: {
|
|
504
|
+
id: modal.id, custom_id: modal.customId,
|
|
505
|
+
components: [{ type: 1, components: [{ type: 4, custom_id: quantityInputId, value: String(quantity) }] }]
|
|
506
|
+
},
|
|
507
|
+
session_id: this.client.sessionId || "dummy_session",
|
|
508
|
+
nonce: Date.now().toString()
|
|
509
|
+
})
|
|
510
|
+
});
|
|
511
|
+
} catch (e) {
|
|
512
|
+
this.log('error', `Modal submit failed: ${e.message}`);
|
|
513
|
+
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
} else {
|
|
517
|
+
this.log('warn', 'No modal appeared after clicking buy.');
|
|
518
|
+
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const confirmMsg = await this.waitForDankMemer(8000);
|
|
523
|
+
if (confirmMsg) {
|
|
524
|
+
const text = getFullText(confirmMsg).toLowerCase();
|
|
525
|
+
if (text.includes('bought') || text.includes('purchased') || text.includes('success')) {
|
|
526
|
+
this.log('success', `Bought ${c.bold}${quantity}x ${itemName}${c.reset}!`);
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
if (text.includes('not enough') || text.includes("can't afford") || text.includes('insufficient')) {
|
|
530
|
+
this.log('warn', `Not enough coins to buy ${itemName}.`);
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
this.log('success', `Submitted purchase for ${quantity}x ${itemName}.`);
|
|
535
|
+
return true;
|
|
237
536
|
}
|
|
238
|
-
this.log('
|
|
537
|
+
this.log('error', `Failed to buy ${itemName} after ${MAX_RETRIES} attempts.`);
|
|
239
538
|
return false;
|
|
240
539
|
}
|
|
241
540
|
|
|
242
541
|
// ── Check Balance ───────────────────────────────────────────
|
|
243
542
|
async checkBalance() {
|
|
244
|
-
|
|
543
|
+
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
544
|
+
await this.channel.send(`${prefix} bal`);
|
|
245
545
|
const response = await this.waitForDankMemer(10000);
|
|
246
546
|
if (response) {
|
|
247
547
|
const text = getFullText(response);
|
|
248
|
-
// Try to parse wallet balance
|
|
249
548
|
const walletMatch = text.match(/wallet[:\s]*[⏣💰]?\s*([\d,]+)/i);
|
|
250
549
|
if (walletMatch) {
|
|
251
550
|
this.stats.balance = parseInt(walletMatch[1].replace(/,/g, ''));
|
|
252
551
|
this.log('bal', `Wallet: ${c.bold}${c.green}⏣ ${this.stats.balance.toLocaleString()}${c.reset}`);
|
|
552
|
+
try {
|
|
553
|
+
await fetch(`${API_URL}/api/grinder/status`, {
|
|
554
|
+
method: 'POST',
|
|
555
|
+
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
556
|
+
body: JSON.stringify({ account_id: this.account.id, balance: this.stats.balance }),
|
|
557
|
+
});
|
|
558
|
+
} catch { /* silent */ }
|
|
253
559
|
}
|
|
254
560
|
}
|
|
255
561
|
}
|
|
256
562
|
|
|
257
|
-
//
|
|
563
|
+
// ══════════════════════════════════════════════════════════════
|
|
564
|
+
// ═ COMMAND HANDLERS
|
|
565
|
+
// ══════════════════════════════════════════════════════════════
|
|
258
566
|
|
|
259
567
|
async handleSearch(response) {
|
|
260
568
|
if (!response) return null;
|
|
569
|
+
debugMessage(response, 'SEARCH');
|
|
261
570
|
const buttons = getAllButtons(response);
|
|
262
571
|
if (buttons.length === 0) return getFullText(response).substring(0, 80);
|
|
263
|
-
|
|
264
572
|
const userSafe = safeParseJSON(this.account.search_answers, []);
|
|
265
573
|
const btn = pickSafeButton(buttons, userSafe);
|
|
266
574
|
if (btn) {
|
|
@@ -271,10 +579,7 @@ class AccountWorker {
|
|
|
271
579
|
if (followUp) {
|
|
272
580
|
const text = getFullText(followUp);
|
|
273
581
|
const coins = parseCoins(text);
|
|
274
|
-
if (coins > 0) {
|
|
275
|
-
this.stats.coins += coins;
|
|
276
|
-
return `${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`;
|
|
277
|
-
}
|
|
582
|
+
if (coins > 0) { this.stats.coins += coins; return `${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
278
583
|
if (text.toLowerCase().includes('nothing')) return `${btn.label} → found nothing`;
|
|
279
584
|
return `${btn.label} → done`;
|
|
280
585
|
}
|
|
@@ -286,9 +591,9 @@ class AccountWorker {
|
|
|
286
591
|
|
|
287
592
|
async handleCrime(response) {
|
|
288
593
|
if (!response) return null;
|
|
594
|
+
debugMessage(response, 'CRIME');
|
|
289
595
|
const buttons = getAllButtons(response);
|
|
290
596
|
if (buttons.length === 0) return getFullText(response).substring(0, 80);
|
|
291
|
-
|
|
292
597
|
const userSafe = safeParseJSON(this.account.crime_answers, []);
|
|
293
598
|
const btn = pickSafeButton(buttons, userSafe);
|
|
294
599
|
if (btn) {
|
|
@@ -299,10 +604,7 @@ class AccountWorker {
|
|
|
299
604
|
if (followUp) {
|
|
300
605
|
const text = getFullText(followUp);
|
|
301
606
|
const coins = parseCoins(text);
|
|
302
|
-
if (coins > 0) {
|
|
303
|
-
this.stats.coins += coins;
|
|
304
|
-
return `${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`;
|
|
305
|
-
}
|
|
607
|
+
if (coins > 0) { this.stats.coins += coins; return `${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
306
608
|
return `${btn.label} → done`;
|
|
307
609
|
}
|
|
308
610
|
return `Clicked: ${btn.label}`;
|
|
@@ -313,46 +615,34 @@ class AccountWorker {
|
|
|
313
615
|
|
|
314
616
|
async handleHighLow(response) {
|
|
315
617
|
if (!response) return null;
|
|
618
|
+
debugMessage(response, 'HIGHLOW');
|
|
316
619
|
const text = getFullText(response);
|
|
317
620
|
const match = text.match(/number.*?(\d+)/i) || text.match(/(\d+)/);
|
|
318
621
|
const buttons = getAllButtons(response);
|
|
319
|
-
|
|
320
622
|
if (match && buttons.length >= 2) {
|
|
321
623
|
const num = parseInt(match[1]);
|
|
322
624
|
await humanDelay();
|
|
323
625
|
let targetBtn;
|
|
324
|
-
if (num > 50)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
targetBtn = buttons.find((b) => (b.label || '').toLowerCase().includes('higher')) || buttons[0];
|
|
328
|
-
} else {
|
|
329
|
-
// Jackpot hint
|
|
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 {
|
|
330
629
|
const jackpot = buttons.find((b) => (b.label || '').toLowerCase().includes('jackpot'));
|
|
331
630
|
targetBtn = jackpot || buttons[Math.floor(Math.random() * 2)];
|
|
332
631
|
}
|
|
333
|
-
|
|
334
632
|
try {
|
|
335
633
|
await targetBtn.click();
|
|
336
634
|
const followUp = await this.waitForDankMemer(10000);
|
|
337
635
|
if (followUp) {
|
|
338
636
|
const ftText = getFullText(followUp);
|
|
339
637
|
const coins = parseCoins(ftText);
|
|
340
|
-
if (coins > 0) {
|
|
341
|
-
this.stats.coins += coins;
|
|
342
|
-
return `${num} → ${targetBtn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`;
|
|
343
|
-
}
|
|
344
|
-
// Multi-round: keep playing
|
|
638
|
+
if (coins > 0) { this.stats.coins += coins; return `${num} → ${targetBtn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
345
639
|
const moreButtons = getAllButtons(followUp);
|
|
346
|
-
if (moreButtons.length >= 2)
|
|
347
|
-
return await this.handleHighLow(followUp);
|
|
348
|
-
}
|
|
640
|
+
if (moreButtons.length >= 2) return await this.handleHighLow(followUp);
|
|
349
641
|
return `${num} → ${targetBtn.label}`;
|
|
350
642
|
}
|
|
351
643
|
return `${num} → ${targetBtn.label}`;
|
|
352
644
|
} catch { return null; }
|
|
353
645
|
}
|
|
354
|
-
|
|
355
|
-
// Fallback: click random
|
|
356
646
|
if (buttons.length > 0) {
|
|
357
647
|
const btn = buttons[Math.floor(Math.random() * buttons.length)];
|
|
358
648
|
await humanDelay();
|
|
@@ -362,12 +652,166 @@ class AccountWorker {
|
|
|
362
652
|
return null;
|
|
363
653
|
}
|
|
364
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.) ──
|
|
365
810
|
async handleGenericCommand(response) {
|
|
366
811
|
if (!response) return null;
|
|
812
|
+
debugMessage(response, 'GENERIC');
|
|
367
813
|
const text = getFullText(response);
|
|
368
814
|
const coins = parseCoins(text);
|
|
369
|
-
|
|
370
|
-
// Check if we need to buy something
|
|
371
815
|
const neededItem = this.needsItem(text);
|
|
372
816
|
if (neededItem) {
|
|
373
817
|
this.log('warn', `Missing ${c.bold}${neededItem}${c.reset} — auto-buying...`);
|
|
@@ -375,8 +819,7 @@ class AccountWorker {
|
|
|
375
819
|
if (bought) return `auto-bought ${neededItem}`;
|
|
376
820
|
return `need ${neededItem} (couldn't buy)`;
|
|
377
821
|
}
|
|
378
|
-
|
|
379
|
-
// Handle buttons if present
|
|
822
|
+
// Handle buttons if present (e.g., postmemes, farm choices)
|
|
380
823
|
const buttons = getAllButtons(response);
|
|
381
824
|
if (buttons.length > 0) {
|
|
382
825
|
const btn = buttons.find((b) => !b.disabled) || buttons[0];
|
|
@@ -389,22 +832,122 @@ class AccountWorker {
|
|
|
389
832
|
const fText = getFullText(followUp);
|
|
390
833
|
const fCoins = parseCoins(fText);
|
|
391
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
|
+
}
|
|
392
844
|
}
|
|
393
845
|
} catch {}
|
|
394
846
|
}
|
|
395
847
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
+
}
|
|
400
865
|
}
|
|
866
|
+
if (coins > 0) { this.stats.coins += coins; return `${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`; }
|
|
867
|
+
return text.substring(0, 60) || 'done';
|
|
868
|
+
}
|
|
401
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}`;
|
|
402
898
|
return text.substring(0, 60) || 'done';
|
|
403
899
|
}
|
|
404
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
|
+
|
|
405
934
|
// ── Run Single Command ──────────────────────────────────────
|
|
406
935
|
async runCommand(cmdName, prefix) {
|
|
407
|
-
|
|
936
|
+
let cmdString;
|
|
937
|
+
const betAmount = this.account.bet_amount || 1000;
|
|
938
|
+
|
|
939
|
+
// Build the command string
|
|
940
|
+
switch (cmdName) {
|
|
941
|
+
case 'dep max': cmdString = `${prefix} dep max`; break;
|
|
942
|
+
case 'blackjack': cmdString = `${prefix} bj ${betAmount}`; break;
|
|
943
|
+
case 'coinflip': cmdString = `${prefix} coinflip ${betAmount} heads`; break;
|
|
944
|
+
case 'roulette': cmdString = `${prefix} roulette ${betAmount} red`; break;
|
|
945
|
+
case 'slots': cmdString = `${prefix} slots ${betAmount}`; break;
|
|
946
|
+
case 'snakeeyes': cmdString = `${prefix} snakeeyes ${betAmount}`; break;
|
|
947
|
+
case 'work shift': cmdString = `${prefix} work shift`; break;
|
|
948
|
+
default: cmdString = `${prefix} ${cmdName}`;
|
|
949
|
+
}
|
|
950
|
+
|
|
408
951
|
this.log('cmd', `${c.white}${c.bold}${cmdString}${c.reset}`);
|
|
409
952
|
this.stats.commands++;
|
|
410
953
|
|
|
@@ -415,113 +958,206 @@ class AccountWorker {
|
|
|
415
958
|
if (!response) {
|
|
416
959
|
this.log('warn', `No response for ${cmdString}`);
|
|
417
960
|
this.stats.errors++;
|
|
418
|
-
await sendLog(cmdString, 'timeout', 'timeout');
|
|
961
|
+
await sendLog(this.username, cmdString, 'timeout', 'timeout');
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// Check for cooldown messages
|
|
966
|
+
const responseText = getFullText(response).toLowerCase();
|
|
967
|
+
if (responseText.includes('already claimed') ||
|
|
968
|
+
(responseText.includes('wait') && responseText.includes('before')) ||
|
|
969
|
+
responseText.includes('cooldown') || responseText.includes('slow down') ||
|
|
970
|
+
responseText.includes('come back') || responseText.includes('already used') ||
|
|
971
|
+
responseText.includes('try again in')) {
|
|
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');
|
|
419
985
|
return;
|
|
420
986
|
}
|
|
421
987
|
|
|
422
988
|
let result;
|
|
423
989
|
switch (cmdName) {
|
|
424
|
-
case 'search':
|
|
425
|
-
case 'crime':
|
|
426
|
-
case 'hl':
|
|
427
|
-
|
|
990
|
+
case 'search': result = await this.handleSearch(response); break;
|
|
991
|
+
case 'crime': result = await this.handleCrime(response); break;
|
|
992
|
+
case 'hl': result = await this.handleHighLow(response); break;
|
|
993
|
+
case 'scratch': result = await this.handleScratch(response); break;
|
|
994
|
+
case 'adventure': result = await this.handleAdventure(response); break;
|
|
995
|
+
case 'blackjack': result = await this.handleBlackjack(response); break;
|
|
996
|
+
case 'trivia': result = await this.handleTrivia(response); break;
|
|
997
|
+
case 'work shift': result = await this.handleWorkShift(response); break;
|
|
998
|
+
case 'coinflip':
|
|
999
|
+
case 'roulette':
|
|
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;
|
|
428
1005
|
}
|
|
429
1006
|
|
|
430
1007
|
this.stats.successes++;
|
|
431
1008
|
this.log('success', `${c.dim}${cmdString}${c.reset} → ${result || 'done'}`);
|
|
432
|
-
await sendLog(cmdString, result || 'done', 'success');
|
|
1009
|
+
await sendLog(this.username, cmdString, result || 'done', 'success');
|
|
433
1010
|
} catch (err) {
|
|
434
1011
|
this.stats.errors++;
|
|
435
1012
|
this.log('error', `${cmdString} failed: ${err.message}`);
|
|
436
|
-
await sendLog(cmdString, err.message, 'error');
|
|
1013
|
+
await sendLog(this.username, cmdString, err.message, 'error');
|
|
437
1014
|
}
|
|
438
1015
|
}
|
|
439
1016
|
|
|
440
|
-
// ──
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (this.stats.balance > 0) {
|
|
446
|
-
this.log('bal', `Wallet: ${c.bold}⏣ ${this.stats.balance.toLocaleString()}${c.reset}`);
|
|
447
|
-
}
|
|
448
|
-
this.log('info', `${c.dim}──────────────────────────────────────${c.reset}`);
|
|
1017
|
+
// ── Redis Cooldown Management ───────────────────────────────
|
|
1018
|
+
async setCooldown(cmdName, durationSeconds) {
|
|
1019
|
+
if (!redis) return;
|
|
1020
|
+
const key = `dkg:cd:${this.account.id}:${cmdName}`;
|
|
1021
|
+
await redis.set(key, '1', 'EX', Math.ceil(durationSeconds));
|
|
449
1022
|
}
|
|
450
1023
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
1024
|
+
async isCooldownReady(cmdName) {
|
|
1025
|
+
if (!redis) return true;
|
|
1026
|
+
const key = `dkg:cd:${this.account.id}:${cmdName}`;
|
|
1027
|
+
const val = await redis.get(key);
|
|
1028
|
+
return !val;
|
|
1029
|
+
}
|
|
455
1030
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
cmd_beg: { cmd: 'beg', cdKey: 'cd_beg', defaultCd: 20 },
|
|
460
|
-
cmd_search: { cmd: 'search', cdKey: 'cd_search', defaultCd: 20 },
|
|
461
|
-
cmd_hl: { cmd: 'hl', cdKey: 'cd_hl', defaultCd: 20 },
|
|
462
|
-
cmd_crime: { cmd: 'crime', cdKey: 'cd_crime', defaultCd: 40 },
|
|
463
|
-
cmd_pm: { cmd: 'pm', cdKey: 'cd_pm', defaultCd: 20 },
|
|
464
|
-
};
|
|
1031
|
+
printStats() {
|
|
1032
|
+
// Stats are shown in the live dashboard, no-op here
|
|
1033
|
+
}
|
|
465
1034
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
this.log('warn', 'Account deactivated from dashboard. Pausing...');
|
|
474
|
-
while (!this.account.active && this.running) {
|
|
475
|
-
await new Promise((r) => setTimeout(r, 10000));
|
|
476
|
-
await this.refreshConfig();
|
|
477
|
-
}
|
|
478
|
-
if (!this.running) break;
|
|
479
|
-
this.log('success', 'Account re-activated! Resuming...');
|
|
480
|
-
}
|
|
481
|
-
}
|
|
1035
|
+
// ── Main Non-Blocking Grind Scheduler ───────────────────────
|
|
1036
|
+
async tick() {
|
|
1037
|
+
if (!this.running) return;
|
|
1038
|
+
if (this.busy) {
|
|
1039
|
+
this.tickTimeout = setTimeout(() => this.tick(), 1000);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
482
1042
|
|
|
483
|
-
|
|
484
|
-
|
|
1043
|
+
const commandMap = [
|
|
1044
|
+
// Grinding commands (high frequency)
|
|
1045
|
+
{ key: 'cmd_beg', cmd: 'beg', cdKey: 'cd_beg', defaultCd: 40, priority: 5 },
|
|
1046
|
+
{ key: 'cmd_search', cmd: 'search', cdKey: 'cd_search', defaultCd: 25, priority: 4 },
|
|
1047
|
+
{ key: 'cmd_hl', cmd: 'hl', cdKey: 'cd_hl', defaultCd: 10, priority: 3 },
|
|
1048
|
+
{ key: 'cmd_pm', cmd: 'pm', cdKey: 'cd_pm', defaultCd: 20, priority: 3 },
|
|
1049
|
+
{ key: 'cmd_crime', cmd: 'crime', cdKey: 'cd_crime', defaultCd: 40, priority: 2 },
|
|
1050
|
+
{ key: 'cmd_hunt', cmd: 'hunt', cdKey: 'cd_hunt', defaultCd: 20, priority: 1 },
|
|
1051
|
+
{ key: 'cmd_dig', cmd: 'dig', cdKey: 'cd_dig', defaultCd: 20, priority: 1 },
|
|
1052
|
+
{ key: 'cmd_fish', cmd: 'fish', cdKey: 'cd_fish', defaultCd: 20, priority: 1 },
|
|
1053
|
+
{ key: 'cmd_farm', cmd: 'farm', cdKey: 'cd_farm', defaultCd: 10, priority: 2 },
|
|
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) {
|
|
1082
|
+
this.tickTimeout = setTimeout(() => this.tick(), 15000);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
485
1085
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
.map(([, val]) => val);
|
|
1086
|
+
let nextCmdToRun = null;
|
|
1087
|
+
let highestPriority = -1;
|
|
489
1088
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
1089
|
+
for (const info of enabledCommands) {
|
|
1090
|
+
const ready = await this.isCooldownReady(info.cmd);
|
|
1091
|
+
if (ready && info.priority > highestPriority) {
|
|
1092
|
+
nextCmdToRun = info;
|
|
1093
|
+
highestPriority = info.priority;
|
|
494
1094
|
}
|
|
1095
|
+
}
|
|
495
1096
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
1097
|
+
if (nextCmdToRun) {
|
|
1098
|
+
this.busy = true;
|
|
1099
|
+
const cd = (this.account[nextCmdToRun.cdKey] || nextCmdToRun.defaultCd);
|
|
1100
|
+
const jitter = 1 + Math.random() * 3;
|
|
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));
|
|
506
1112
|
}
|
|
507
1113
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
1114
|
+
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
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++;
|
|
1124
|
+
|
|
1125
|
+
if (this.cycleCount > 0 && this.cycleCount % 10 === 0) this.printStats();
|
|
1126
|
+
if (this.cycleCount > 0 && this.cycleCount % 20 === 0) {
|
|
1127
|
+
this.busy = true;
|
|
514
1128
|
await this.checkBalance();
|
|
515
|
-
|
|
1129
|
+
this.busy = false;
|
|
516
1130
|
}
|
|
517
1131
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
1132
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
1133
|
+
} else {
|
|
1134
|
+
this.setStatus(`${c.dim}waiting for cooldowns...${c.reset}`);
|
|
1135
|
+
this.tickTimeout = setTimeout(() => this.tick(), 1000);
|
|
521
1136
|
}
|
|
522
1137
|
}
|
|
523
1138
|
|
|
524
|
-
|
|
1139
|
+
async grindLoop() {
|
|
1140
|
+
if (this.running) return;
|
|
1141
|
+
this.running = true;
|
|
1142
|
+
this.busy = false;
|
|
1143
|
+
this.cycleCount = 0;
|
|
1144
|
+
this.lastCommandRun = 0;
|
|
1145
|
+
|
|
1146
|
+
this.configInterval = setInterval(async () => {
|
|
1147
|
+
if (!this.running) return;
|
|
1148
|
+
await this.refreshConfig();
|
|
1149
|
+
if (!this.account.active && !this.busy) {
|
|
1150
|
+
this.log('warn', 'Account deactivated from dashboard. Pausing...');
|
|
1151
|
+
this.busy = true;
|
|
1152
|
+
} else if (this.account.active && this.busy) {
|
|
1153
|
+
this.log('success', 'Account re-activated! Resuming...');
|
|
1154
|
+
this.busy = false;
|
|
1155
|
+
}
|
|
1156
|
+
}, 15000);
|
|
1157
|
+
|
|
1158
|
+
this.tick();
|
|
1159
|
+
}
|
|
1160
|
+
|
|
525
1161
|
async refreshConfig() {
|
|
526
1162
|
try {
|
|
527
1163
|
const res = await fetch(`${API_URL}/api/grinder/status`, {
|
|
@@ -535,19 +1171,15 @@ class AccountWorker {
|
|
|
535
1171
|
} catch { /* silent */ }
|
|
536
1172
|
}
|
|
537
1173
|
|
|
538
|
-
// ── Start Worker ────────────────────────────────────────────
|
|
539
1174
|
async start() {
|
|
540
1175
|
if (!this.account.discord_token) { this.log('error', 'No Discord token.'); return; }
|
|
541
1176
|
if (!this.account.channel_id) { this.log('error', 'No channel ID.'); return; }
|
|
542
1177
|
|
|
543
|
-
this.log('info',
|
|
1178
|
+
this.log('info', 'Connecting...');
|
|
544
1179
|
|
|
545
1180
|
return new Promise((resolve) => {
|
|
546
1181
|
this.client.on('ready', async () => {
|
|
547
|
-
// Use the actual Discord username
|
|
548
1182
|
this.username = this.client.user.tag || this.username;
|
|
549
|
-
|
|
550
|
-
// Report username back to API
|
|
551
1183
|
try {
|
|
552
1184
|
await fetch(`${API_URL}/api/grinder/status`, {
|
|
553
1185
|
method: 'POST',
|
|
@@ -566,24 +1198,31 @@ class AccountWorker {
|
|
|
566
1198
|
|
|
567
1199
|
this.log('info', `Channel: ${c.white}#${this.channel.name || this.account.channel_id}${c.reset}`);
|
|
568
1200
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
{ key: '
|
|
572
|
-
{ key: '
|
|
573
|
-
{ key: '
|
|
574
|
-
{ key: '
|
|
575
|
-
{ key: '
|
|
576
|
-
{ key: '
|
|
577
|
-
{ key: '
|
|
1201
|
+
const ALL_CMDS = [
|
|
1202
|
+
{ key: 'cmd_hunt', label: 'hunt' }, { key: 'cmd_dig', label: 'dig' },
|
|
1203
|
+
{ key: 'cmd_fish', label: 'fish' }, { key: 'cmd_beg', label: 'beg' },
|
|
1204
|
+
{ key: 'cmd_search', label: 'search' }, { key: 'cmd_hl', label: 'hl' },
|
|
1205
|
+
{ key: 'cmd_crime', label: 'crime' }, { key: 'cmd_pm', label: 'pm' },
|
|
1206
|
+
{ key: 'cmd_daily', label: 'daily' }, { key: 'cmd_weekly', label: 'weekly' },
|
|
1207
|
+
{ key: 'cmd_monthly', label: 'monthly' }, { key: 'cmd_work', label: 'work' },
|
|
1208
|
+
{ key: 'cmd_stream', label: 'stream' }, { key: 'cmd_scratch', label: 'scratch' },
|
|
1209
|
+
{ key: 'cmd_adventure', label: 'adventure' }, { key: 'cmd_farm', label: 'farm' },
|
|
1210
|
+
{ key: 'cmd_tidy', label: 'tidy' }, { key: 'cmd_blackjack', label: 'bj' },
|
|
1211
|
+
{ key: 'cmd_cointoss', label: 'coinflip' }, { key: 'cmd_roulette', label: 'roulette' },
|
|
1212
|
+
{ key: 'cmd_slots', label: 'slots' }, { key: 'cmd_snakeeyes', label: 'snakeeyes' },
|
|
1213
|
+
{ key: 'cmd_trivia', label: 'trivia' }, { key: 'cmd_use', label: 'use' },
|
|
1214
|
+
{ key: 'cmd_deposit', label: 'deposit' }, { key: 'cmd_drops', label: 'drops' },
|
|
1215
|
+
{ key: 'cmd_alert', label: 'alert' },
|
|
578
1216
|
].filter((ci) => this.account[ci.key] === true || this.account[ci.key] === 1);
|
|
579
1217
|
|
|
580
|
-
const cmdStr =
|
|
1218
|
+
const cmdStr = ALL_CMDS.map(ci => ci.label).join(', ');
|
|
581
1219
|
this.log('info', `Commands: ${c.white}${cmdStr || 'none'}${c.reset}`);
|
|
1220
|
+
if (this.account.bet_amount) {
|
|
1221
|
+
this.log('info', `Bet amount: ${c.yellow}⏣ ${this.account.bet_amount.toLocaleString()}${c.reset}`);
|
|
1222
|
+
}
|
|
582
1223
|
|
|
583
|
-
// Initial balance check
|
|
584
1224
|
await this.checkBalance();
|
|
585
1225
|
console.log('');
|
|
586
|
-
|
|
587
1226
|
this.grindLoop();
|
|
588
1227
|
resolve();
|
|
589
1228
|
});
|
|
@@ -594,6 +1233,8 @@ class AccountWorker {
|
|
|
594
1233
|
|
|
595
1234
|
stop() {
|
|
596
1235
|
this.running = false;
|
|
1236
|
+
if (this.tickTimeout) clearTimeout(this.tickTimeout);
|
|
1237
|
+
if (this.configInterval) clearInterval(this.configInterval);
|
|
597
1238
|
try { this.client.destroy(); } catch {}
|
|
598
1239
|
}
|
|
599
1240
|
}
|
|
@@ -605,17 +1246,16 @@ class AccountWorker {
|
|
|
605
1246
|
async function start(apiKey, apiUrl) {
|
|
606
1247
|
API_KEY = apiKey;
|
|
607
1248
|
API_URL = apiUrl;
|
|
1249
|
+
initRedis();
|
|
608
1250
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
console.log(
|
|
612
|
-
console.log(` ${c.
|
|
613
|
-
console.log(` ${c.magenta}${c.bold}│${c.reset} ${c.dim}Per-Cmd CD • Live Control • Smart${c.reset} ${c.magenta}${c.bold}│${c.reset}`);
|
|
614
|
-
console.log(` ${c.magenta}${c.bold}│${c.reset} ${c.magenta}${c.bold}│${c.reset}`);
|
|
615
|
-
console.log(` ${c.magenta}${c.bold}└─────────────────────────────────────────┘${c.reset}`);
|
|
1251
|
+
// Clear screen & show big banner
|
|
1252
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
1253
|
+
console.log(BANNER);
|
|
1254
|
+
console.log(` ${c.dim}v4.0${c.reset} ${c.dim}•${c.reset} ${c.white}30 Commands${c.reset} ${c.dim}•${c.reset} ${c.cyan}Redis Cooldowns${c.reset} ${c.dim}•${c.reset} ${c.green}Smart AI${c.reset}`);
|
|
616
1255
|
console.log('');
|
|
617
1256
|
|
|
618
1257
|
log('info', `API: ${c.dim}${API_URL}${c.reset}`);
|
|
1258
|
+
log('info', `Redis: ${c.dim}${REDIS_URL.replace(/:[^:]+@/, ':***@')}${c.reset}`);
|
|
619
1259
|
log('info', 'Fetching accounts...');
|
|
620
1260
|
|
|
621
1261
|
const data = await fetchConfig();
|
|
@@ -636,16 +1276,30 @@ async function start(apiKey, apiUrl) {
|
|
|
636
1276
|
await worker.start();
|
|
637
1277
|
}
|
|
638
1278
|
|
|
639
|
-
log('info', `${c.bold}All workers running.${c.reset} ${c.dim}Ctrl+C to stop.${c.reset}`);
|
|
640
1279
|
console.log('');
|
|
1280
|
+
startTime = Date.now();
|
|
1281
|
+
dashboardStarted = true;
|
|
1282
|
+
process.stdout.write(c.hide); // Hide cursor for clean UI
|
|
1283
|
+
|
|
1284
|
+
// Start the live dashboard refresh loop
|
|
1285
|
+
setInterval(() => renderDashboard(), 1000);
|
|
1286
|
+
renderDashboard(); // Initial render
|
|
641
1287
|
|
|
642
1288
|
process.on('SIGINT', () => {
|
|
1289
|
+
process.stdout.write(c.show); // Show cursor again
|
|
1290
|
+
dashboardStarted = false;
|
|
1291
|
+
console.log('');
|
|
643
1292
|
console.log('');
|
|
644
1293
|
log('warn', 'Shutting down...');
|
|
1294
|
+
console.log('');
|
|
645
1295
|
for (const w of workers) {
|
|
646
|
-
w.
|
|
1296
|
+
const rate = w.stats.commands > 0 ? ((w.stats.successes / w.stats.commands) * 100).toFixed(0) : 0;
|
|
1297
|
+
console.log(` ${w.color}${c.bold}${w.username}${c.reset} ${c.green}⏣ ${w.stats.coins.toLocaleString()}${c.reset} earned ${c.dim}│${c.reset} ${w.stats.commands} cmds ${c.dim}│${c.reset} ${rate}% success`);
|
|
647
1298
|
w.stop();
|
|
648
1299
|
}
|
|
1300
|
+
console.log('');
|
|
1301
|
+
console.log(` ${c.yellow}${c.bold}Total: ⏣ ${totalCoins.toLocaleString()}${c.reset} earned in ${formatUptime()}`);
|
|
1302
|
+
console.log('');
|
|
649
1303
|
setTimeout(() => process.exit(0), 2000);
|
|
650
1304
|
});
|
|
651
1305
|
}
|