dankgrinder 1.0.1 → 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 +1130 -210
- package/package.json +3 -2
package/lib/grinder.js
CHANGED
|
@@ -1,35 +1,175 @@
|
|
|
1
1
|
const { Client } = require('discord.js-selfbot-v13');
|
|
2
|
+
const Redis = require('ioredis');
|
|
2
3
|
|
|
4
|
+
// ── Terminal Colors & ANSI ───────────────────────────────────
|
|
3
5
|
const c = {
|
|
4
|
-
reset: '\x1b[0m',
|
|
5
|
-
green: '\x1b[32m',
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
|
|
7
|
+
green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
8
|
+
magenta: '\x1b[35m', white: '\x1b[37m', blue: '\x1b[34m',
|
|
9
|
+
bgGreen: '\x1b[42m', bgRed: '\x1b[41m', bgYellow: '\x1b[43m', bgCyan: '\x1b[46m',
|
|
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',
|
|
14
19
|
};
|
|
15
20
|
|
|
16
|
-
const
|
|
21
|
+
const WORKER_COLORS = [c.cyan, c.magenta, c.yellow, c.green, c.blue, c.red];
|
|
22
|
+
const DANK_MEMER_ID = '270904126974590976';
|
|
23
|
+
|
|
24
|
+
// ── Safe options for search/crime ──────────────────────────
|
|
25
|
+
const SAFE_SEARCH_LOCATIONS = [
|
|
26
|
+
'sofa', 'mailbox', 'dog', 'car', 'dresser', 'laundromat', 'bed',
|
|
27
|
+
'couch', 'pantry', 'fridge', 'kitchen', 'bathroom', 'attic',
|
|
28
|
+
'closet', 'shoe', 'vacuum', 'toilet', 'sink', 'shower',
|
|
29
|
+
'tree', 'grass', 'bushes', 'garden', 'park', 'backyard',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const SAFE_CRIME_OPTIONS = [
|
|
33
|
+
'tax evasion', 'fraud', 'cybercrime', 'hacking', 'identity theft',
|
|
34
|
+
'money laundering', 'tax fraud', 'insurance fraud', 'scam',
|
|
35
|
+
];
|
|
17
36
|
|
|
18
37
|
let API_KEY = '';
|
|
19
38
|
let API_URL = '';
|
|
39
|
+
let REDIS_URL = process.env.REDIS_URL || 'redis://default:qXcezFjDHlCDtakRUZJmvsEzHoBgdLHi@shortline.proxy.rlwy.net:32007';
|
|
40
|
+
let redis = null;
|
|
20
41
|
const workers = [];
|
|
21
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
|
+
|
|
22
150
|
function log(type, msg, label) {
|
|
23
|
-
const time = new Date().toLocaleTimeString();
|
|
24
|
-
const
|
|
25
|
-
info:
|
|
26
|
-
|
|
27
|
-
error: `${c.red}✗${c.reset}`,
|
|
28
|
-
warn: `${c.yellow}⚠${c.reset}`,
|
|
29
|
-
cmd: `${c.magenta}▸${c.reset}`,
|
|
151
|
+
const time = new Date().toLocaleTimeString('en-US', { hour12: true, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
152
|
+
const icons = {
|
|
153
|
+
info: `│`, success: `✓`, error: `✗`, warn: `!`,
|
|
154
|
+
cmd: `▸`, coin: `$`, buy: `♦`, bal: `◈`, debug: `⊙`,
|
|
30
155
|
};
|
|
31
|
-
const tag = label ? `${
|
|
32
|
-
|
|
156
|
+
const tag = label ? `${label} ` : '';
|
|
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
|
+
}
|
|
33
173
|
}
|
|
34
174
|
|
|
35
175
|
async function fetchConfig() {
|
|
@@ -38,178 +178,778 @@ async function fetchConfig() {
|
|
|
38
178
|
headers: { Authorization: `Bearer ${API_KEY}` },
|
|
39
179
|
});
|
|
40
180
|
const data = await res.json();
|
|
41
|
-
if (data.error) {
|
|
42
|
-
log('error', `Config fetch failed: ${data.error}`);
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
181
|
+
if (data.error) { log('error', `Config fetch failed: ${data.error}`); return null; }
|
|
45
182
|
return data;
|
|
46
|
-
} catch (err) {
|
|
47
|
-
log('error', `Cannot reach API: ${err.message}`);
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
183
|
+
} catch (err) { log('error', `Cannot reach API: ${err.message}`); return null; }
|
|
50
184
|
}
|
|
51
185
|
|
|
52
|
-
async function sendLog(command, response, status) {
|
|
186
|
+
async function sendLog(accountName, command, response, status) {
|
|
53
187
|
try {
|
|
54
188
|
await fetch(`${API_URL}/api/grinder/log`, {
|
|
55
189
|
method: 'POST',
|
|
56
|
-
headers: {
|
|
57
|
-
|
|
58
|
-
'Content-Type': 'application/json',
|
|
59
|
-
},
|
|
60
|
-
body: JSON.stringify({ command, response, status }),
|
|
190
|
+
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
191
|
+
body: JSON.stringify({ account_name: accountName, command, response, status }),
|
|
61
192
|
});
|
|
62
|
-
} catch {
|
|
63
|
-
// silent fail for logging
|
|
64
|
-
}
|
|
193
|
+
} catch { /* silent */ }
|
|
65
194
|
}
|
|
66
195
|
|
|
67
196
|
function randomDelay(min, max) {
|
|
68
|
-
|
|
69
|
-
|
|
197
|
+
return new Promise((r) => setTimeout(r, (Math.random() * (max - min) + min) * 1000));
|
|
198
|
+
}
|
|
199
|
+
function humanDelay(min = 300, max = 800) {
|
|
200
|
+
return new Promise((r) => setTimeout(r, min + Math.random() * (max - min)));
|
|
201
|
+
}
|
|
202
|
+
function safeParseJSON(str, fallback = []) {
|
|
203
|
+
try { return JSON.parse(str || '[]'); } catch { return fallback; }
|
|
70
204
|
}
|
|
71
205
|
|
|
72
|
-
|
|
73
|
-
|
|
206
|
+
// ── Coin Parser ──────────────────────────────────────────────
|
|
207
|
+
function parseCoins(text) {
|
|
208
|
+
if (!text) return 0;
|
|
209
|
+
const patterns = [
|
|
210
|
+
/[⏣💰]\s*[\d,]+/g,
|
|
211
|
+
/\+\s*[\d,]+\s*coins?/gi,
|
|
212
|
+
/\*\*([\d,]+)\*\*/g,
|
|
213
|
+
/take\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
214
|
+
/earned\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
215
|
+
/found\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
216
|
+
/got\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
217
|
+
/won\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
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
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return total;
|
|
74
231
|
}
|
|
75
232
|
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
233
|
+
function getFullText(msg) {
|
|
234
|
+
let text = msg.content || '';
|
|
235
|
+
if (msg.embeds) {
|
|
236
|
+
for (const embed of msg.embeds) {
|
|
237
|
+
if (embed.title) text += ' ' + embed.title;
|
|
238
|
+
if (embed.description) text += ' ' + embed.description;
|
|
239
|
+
if (embed.fields) {
|
|
240
|
+
for (const f of embed.fields) {
|
|
241
|
+
text += ' ' + (f.name || '') + ' ' + (f.value || '');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (embed.footer?.text) text += ' ' + embed.footer.text;
|
|
245
|
+
}
|
|
81
246
|
}
|
|
247
|
+
return text;
|
|
82
248
|
}
|
|
83
249
|
|
|
84
|
-
|
|
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
|
+
|
|
278
|
+
function pickSafeButton(buttons, safeList) {
|
|
279
|
+
if (!buttons || buttons.length === 0) return null;
|
|
280
|
+
if (safeList && safeList.length > 0) {
|
|
281
|
+
for (const btn of buttons) {
|
|
282
|
+
const label = (btn.label || '').toLowerCase();
|
|
283
|
+
if (safeList.some((s) => label.includes(s.toLowerCase()))) return btn;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const btn of buttons) {
|
|
287
|
+
const label = (btn.label || '').toLowerCase();
|
|
288
|
+
if (SAFE_SEARCH_LOCATIONS.some((s) => label.includes(s))) return btn;
|
|
289
|
+
if (SAFE_CRIME_OPTIONS.some((s) => label.includes(s))) return btn;
|
|
290
|
+
}
|
|
291
|
+
const clickable = buttons.filter((b) => !b.disabled);
|
|
292
|
+
return clickable.length > 0 ? clickable[Math.floor(Math.random() * clickable.length)] : null;
|
|
293
|
+
}
|
|
294
|
+
|
|
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(', ')}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ══════════════════════════════════════════════════════════════
|
|
313
|
+
// ═ Worker: one per Discord account
|
|
314
|
+
// ══════════════════════════════════════════════════════════════
|
|
85
315
|
|
|
86
316
|
class AccountWorker {
|
|
87
317
|
constructor(account, idx) {
|
|
88
318
|
this.account = account;
|
|
89
|
-
this.
|
|
90
|
-
this.color =
|
|
319
|
+
this.idx = idx;
|
|
320
|
+
this.color = WORKER_COLORS[idx % WORKER_COLORS.length];
|
|
91
321
|
this.client = new Client();
|
|
92
322
|
this.channel = null;
|
|
93
323
|
this.running = false;
|
|
324
|
+
this.busy = false;
|
|
325
|
+
this.username = account.label || `Account ${idx + 1}`;
|
|
326
|
+
this.tickTimeout = null;
|
|
327
|
+
this.stats = { coins: 0, commands: 0, successes: 0, errors: 0, balance: 0 };
|
|
328
|
+
this.lastRunTime = {};
|
|
329
|
+
this.cycleCount = 0;
|
|
330
|
+
this.lastCommandRun = 0;
|
|
94
331
|
}
|
|
95
332
|
|
|
96
|
-
|
|
97
|
-
|
|
333
|
+
get tag() { return `${this.color}${c.bold}${this.username}${c.reset}`; }
|
|
334
|
+
log(type, msg) { log(type, msg, this.tag); }
|
|
335
|
+
|
|
336
|
+
// Update the live dashboard status for this worker
|
|
337
|
+
setStatus(text) {
|
|
338
|
+
this.lastStatus = text;
|
|
339
|
+
renderDashboard();
|
|
98
340
|
}
|
|
99
341
|
|
|
100
342
|
waitForDankMemer(timeout = 15000) {
|
|
101
343
|
return new Promise((resolve) => {
|
|
102
344
|
const timer = setTimeout(() => {
|
|
103
345
|
this.client.removeListener('messageCreate', handler);
|
|
346
|
+
this.client.removeListener('messageUpdate', updateHandler);
|
|
104
347
|
resolve(null);
|
|
105
348
|
}, timeout);
|
|
106
|
-
|
|
107
349
|
const self = this;
|
|
108
350
|
function handler(msg) {
|
|
109
|
-
if (msg.author.id ===
|
|
351
|
+
if (msg.author.id === DANK_MEMER_ID && msg.channel.id === self.channel.id) {
|
|
110
352
|
clearTimeout(timer);
|
|
111
353
|
self.client.removeListener('messageCreate', handler);
|
|
354
|
+
self.client.removeListener('messageUpdate', updateHandler);
|
|
112
355
|
resolve(msg);
|
|
113
356
|
}
|
|
114
357
|
}
|
|
115
|
-
|
|
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
|
+
}
|
|
116
366
|
this.client.on('messageCreate', handler);
|
|
367
|
+
this.client.on('messageUpdate', updateHandler);
|
|
117
368
|
});
|
|
118
369
|
}
|
|
119
370
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ── Needs Item Detection ────────────────────────────────────
|
|
391
|
+
needsItem(text) {
|
|
392
|
+
const lower = text.toLowerCase();
|
|
393
|
+
if (lower.includes("don't have a shovel") || (lower.includes('need') && lower.includes('shovel')) || lower.includes('you need a shovel'))
|
|
394
|
+
return 'shovel';
|
|
395
|
+
if (lower.includes("don't have a fishing") || lower.includes('need a fishing') || lower.includes('you need a fishing pole'))
|
|
396
|
+
return 'fishing pole';
|
|
397
|
+
if (lower.includes("don't have a hunting rifle") || lower.includes('need a hunting rifle') || lower.includes('you need a rifle'))
|
|
398
|
+
return 'hunting rifle';
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
|
|
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; }
|
|
146
441
|
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
147
444
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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;
|
|
158
464
|
}
|
|
159
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;
|
|
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;
|
|
536
|
+
}
|
|
537
|
+
this.log('error', `Failed to buy ${itemName} after ${MAX_RETRIES} attempts.`);
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ── Check Balance ───────────────────────────────────────────
|
|
542
|
+
async checkBalance() {
|
|
543
|
+
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
544
|
+
await this.channel.send(`${prefix} bal`);
|
|
545
|
+
const response = await this.waitForDankMemer(10000);
|
|
546
|
+
if (response) {
|
|
547
|
+
const text = getFullText(response);
|
|
548
|
+
const walletMatch = text.match(/wallet[:\s]*[⏣💰]?\s*([\d,]+)/i);
|
|
549
|
+
if (walletMatch) {
|
|
550
|
+
this.stats.balance = parseInt(walletMatch[1].replace(/,/g, ''));
|
|
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 */ }
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
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)];
|
|
160
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; }
|
|
161
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
|
+
}
|
|
162
654
|
|
|
163
|
-
|
|
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';
|
|
164
681
|
}
|
|
165
682
|
|
|
166
|
-
async
|
|
167
|
-
if (!
|
|
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
|
+
}
|
|
168
714
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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 {}
|
|
181
842
|
}
|
|
182
843
|
}
|
|
183
844
|
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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}`; }
|
|
195
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
|
+
}
|
|
196
869
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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}`;
|
|
202
891
|
}
|
|
203
|
-
}
|
|
892
|
+
} catch {}
|
|
204
893
|
}
|
|
205
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
|
+
}
|
|
206
900
|
|
|
207
|
-
|
|
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';
|
|
208
910
|
}
|
|
209
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
|
+
// ── Run Single Command ──────────────────────────────────────
|
|
210
935
|
async runCommand(cmdName, prefix) {
|
|
211
|
-
|
|
212
|
-
this.
|
|
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
|
+
|
|
951
|
+
this.log('cmd', `${c.white}${c.bold}${cmdString}${c.reset}`);
|
|
952
|
+
this.stats.commands++;
|
|
213
953
|
|
|
214
954
|
try {
|
|
215
955
|
await this.channel.send(cmdString);
|
|
@@ -217,106 +957,272 @@ class AccountWorker {
|
|
|
217
957
|
|
|
218
958
|
if (!response) {
|
|
219
959
|
this.log('warn', `No response for ${cmdString}`);
|
|
220
|
-
|
|
960
|
+
this.stats.errors++;
|
|
961
|
+
await sendLog(this.username, cmdString, 'timeout', 'timeout');
|
|
221
962
|
return;
|
|
222
963
|
}
|
|
223
964
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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`);
|
|
233
983
|
}
|
|
984
|
+
await sendLog(this.username, cmdString, 'on cooldown', 'cooldown');
|
|
985
|
+
return;
|
|
234
986
|
}
|
|
235
987
|
|
|
236
|
-
|
|
237
|
-
|
|
988
|
+
let result;
|
|
989
|
+
switch (cmdName) {
|
|
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;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
this.stats.successes++;
|
|
1008
|
+
this.log('success', `${c.dim}${cmdString}${c.reset} → ${result || 'done'}`);
|
|
1009
|
+
await sendLog(this.username, cmdString, result || 'done', 'success');
|
|
238
1010
|
} catch (err) {
|
|
1011
|
+
this.stats.errors++;
|
|
239
1012
|
this.log('error', `${cmdString} failed: ${err.message}`);
|
|
240
|
-
await sendLog(cmdString, err.message, 'error');
|
|
1013
|
+
await sendLog(this.username, cmdString, err.message, 'error');
|
|
241
1014
|
}
|
|
242
1015
|
}
|
|
243
1016
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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));
|
|
1022
|
+
}
|
|
247
1023
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
cmd_crime: 'crime',
|
|
255
|
-
cmd_pm: 'pm',
|
|
256
|
-
};
|
|
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
|
+
}
|
|
257
1030
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const minCD = acc.cooldown_min || 3;
|
|
262
|
-
const maxCD = acc.cooldown_max || 8;
|
|
1031
|
+
printStats() {
|
|
1032
|
+
// Stats are shown in the live dashboard, no-op here
|
|
1033
|
+
}
|
|
263
1034
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
}
|
|
267
1042
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
+
}
|
|
1085
|
+
|
|
1086
|
+
let nextCmdToRun = null;
|
|
1087
|
+
let highestPriority = -1;
|
|
1088
|
+
|
|
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;
|
|
272
1094
|
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
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));
|
|
1112
|
+
}
|
|
1113
|
+
|
|
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++;
|
|
273
1124
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
await
|
|
1125
|
+
if (this.cycleCount > 0 && this.cycleCount % 10 === 0) this.printStats();
|
|
1126
|
+
if (this.cycleCount > 0 && this.cycleCount % 20 === 0) {
|
|
1127
|
+
this.busy = true;
|
|
1128
|
+
await this.checkBalance();
|
|
1129
|
+
this.busy = false;
|
|
278
1130
|
}
|
|
279
1131
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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);
|
|
283
1136
|
}
|
|
284
1137
|
}
|
|
285
1138
|
|
|
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
|
+
|
|
1161
|
+
async refreshConfig() {
|
|
1162
|
+
try {
|
|
1163
|
+
const res = await fetch(`${API_URL}/api/grinder/status`, {
|
|
1164
|
+
headers: { Authorization: `Bearer ${API_KEY}` },
|
|
1165
|
+
});
|
|
1166
|
+
const data = await res.json();
|
|
1167
|
+
if (data.accounts) {
|
|
1168
|
+
const updated = data.accounts.find((a) => a.id === this.account.id);
|
|
1169
|
+
if (updated) this.account = updated;
|
|
1170
|
+
}
|
|
1171
|
+
} catch { /* silent */ }
|
|
1172
|
+
}
|
|
1173
|
+
|
|
286
1174
|
async start() {
|
|
287
|
-
if (!this.account.discord_token) {
|
|
288
|
-
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
if (!this.account.channel_id) {
|
|
292
|
-
this.log('error', 'No channel ID configured.');
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
1175
|
+
if (!this.account.discord_token) { this.log('error', 'No Discord token.'); return; }
|
|
1176
|
+
if (!this.account.channel_id) { this.log('error', 'No channel ID.'); return; }
|
|
295
1177
|
|
|
296
|
-
this.log('info', 'Connecting
|
|
1178
|
+
this.log('info', 'Connecting...');
|
|
297
1179
|
|
|
298
1180
|
return new Promise((resolve) => {
|
|
299
1181
|
this.client.on('ready', async () => {
|
|
300
|
-
this.
|
|
301
|
-
|
|
1182
|
+
this.username = this.client.user.tag || this.username;
|
|
1183
|
+
try {
|
|
1184
|
+
await fetch(`${API_URL}/api/grinder/status`, {
|
|
1185
|
+
method: 'POST',
|
|
1186
|
+
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
1187
|
+
body: JSON.stringify({ account_id: this.account.id, discord_username: this.username }),
|
|
1188
|
+
});
|
|
1189
|
+
} catch { /* silent */ }
|
|
302
1190
|
|
|
1191
|
+
this.log('success', `Logged in! ${c.dim}(${this.client.guilds.cache.size} servers)${c.reset}`);
|
|
303
1192
|
this.channel = await this.client.channels.fetch(this.account.channel_id).catch(() => null);
|
|
304
1193
|
|
|
305
1194
|
if (!this.channel) {
|
|
306
|
-
this.log('error', `
|
|
307
|
-
resolve();
|
|
308
|
-
return;
|
|
1195
|
+
this.log('error', `Channel ${this.account.channel_id} not found`);
|
|
1196
|
+
resolve(); return;
|
|
309
1197
|
}
|
|
310
1198
|
|
|
311
1199
|
this.log('info', `Channel: ${c.white}#${this.channel.name || this.account.channel_id}${c.reset}`);
|
|
312
1200
|
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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' },
|
|
1216
|
+
].filter((ci) => this.account[ci.key] === true || this.account[ci.key] === 1);
|
|
319
1217
|
|
|
1218
|
+
const cmdStr = ALL_CMDS.map(ci => ci.label).join(', ');
|
|
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
|
+
}
|
|
1223
|
+
|
|
1224
|
+
await this.checkBalance();
|
|
1225
|
+
console.log('');
|
|
320
1226
|
this.grindLoop();
|
|
321
1227
|
resolve();
|
|
322
1228
|
});
|
|
@@ -327,59 +1233,73 @@ class AccountWorker {
|
|
|
327
1233
|
|
|
328
1234
|
stop() {
|
|
329
1235
|
this.running = false;
|
|
1236
|
+
if (this.tickTimeout) clearTimeout(this.tickTimeout);
|
|
1237
|
+
if (this.configInterval) clearInterval(this.configInterval);
|
|
330
1238
|
try { this.client.destroy(); } catch {}
|
|
331
1239
|
}
|
|
332
1240
|
}
|
|
333
1241
|
|
|
334
|
-
//
|
|
1242
|
+
// ══════════════════════════════════════════════════════════════
|
|
1243
|
+
// ═ Main Entry
|
|
1244
|
+
// ══════════════════════════════════════════════════════════════
|
|
335
1245
|
|
|
336
1246
|
async function start(apiKey, apiUrl) {
|
|
337
1247
|
API_KEY = apiKey;
|
|
338
1248
|
API_URL = apiUrl;
|
|
1249
|
+
initRedis();
|
|
339
1250
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
console.log(
|
|
343
|
-
console.log(` ${c.
|
|
344
|
-
console.log(` ${c.magenta}${c.bold}║${c.reset} ${c.dim}Multi-Account Automation Engine${c.reset} ${c.magenta}${c.bold}║${c.reset}`);
|
|
345
|
-
console.log(` ${c.magenta}${c.bold}║${c.reset} ${c.magenta}${c.bold}║${c.reset}`);
|
|
346
|
-
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}`);
|
|
347
1255
|
console.log('');
|
|
348
1256
|
|
|
349
1257
|
log('info', `API: ${c.dim}${API_URL}${c.reset}`);
|
|
350
|
-
log('info',
|
|
1258
|
+
log('info', `Redis: ${c.dim}${REDIS_URL.replace(/:[^:]+@/, ':***@')}${c.reset}`);
|
|
1259
|
+
log('info', 'Fetching accounts...');
|
|
351
1260
|
|
|
352
1261
|
const data = await fetchConfig();
|
|
353
|
-
|
|
354
|
-
if (!data) {
|
|
355
|
-
log('error', 'Failed to fetch config. Check your API key and server.');
|
|
356
|
-
process.exit(1);
|
|
357
|
-
}
|
|
1262
|
+
if (!data) { log('error', 'Failed to fetch config.'); process.exit(1); }
|
|
358
1263
|
|
|
359
1264
|
const { accounts } = data;
|
|
360
|
-
|
|
361
1265
|
if (!accounts || accounts.length === 0) {
|
|
362
|
-
log('error', 'No active accounts
|
|
1266
|
+
log('error', 'No active accounts. Add them in the dashboard.');
|
|
363
1267
|
process.exit(1);
|
|
364
1268
|
}
|
|
365
1269
|
|
|
366
|
-
log('success',
|
|
1270
|
+
log('success', `${c.bold}${accounts.length}${c.reset} active account(s) found`);
|
|
367
1271
|
console.log('');
|
|
368
1272
|
|
|
369
|
-
// Spawn a worker per account
|
|
370
1273
|
for (let i = 0; i < accounts.length; i++) {
|
|
371
1274
|
const worker = new AccountWorker(accounts[i], i);
|
|
372
1275
|
workers.push(worker);
|
|
373
1276
|
await worker.start();
|
|
374
1277
|
}
|
|
375
1278
|
|
|
376
|
-
log('info', `${c.bold}All workers running. Press Ctrl+C to stop.${c.reset}`);
|
|
377
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
|
|
378
1287
|
|
|
379
1288
|
process.on('SIGINT', () => {
|
|
1289
|
+
process.stdout.write(c.show); // Show cursor again
|
|
1290
|
+
dashboardStarted = false;
|
|
1291
|
+
console.log('');
|
|
1292
|
+
console.log('');
|
|
1293
|
+
log('warn', 'Shutting down...');
|
|
1294
|
+
console.log('');
|
|
1295
|
+
for (const w of workers) {
|
|
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`);
|
|
1298
|
+
w.stop();
|
|
1299
|
+
}
|
|
1300
|
+
console.log('');
|
|
1301
|
+
console.log(` ${c.yellow}${c.bold}Total: ⏣ ${totalCoins.toLocaleString()}${c.reset} earned in ${formatUptime()}`);
|
|
380
1302
|
console.log('');
|
|
381
|
-
log('warn', 'Shutting down all workers...');
|
|
382
|
-
for (const w of workers) w.stop();
|
|
383
1303
|
setTimeout(() => process.exit(0), 2000);
|
|
384
1304
|
});
|
|
385
1305
|
}
|