dankgrinder 1.0.1 → 3.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 +451 -185
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -1,35 +1,48 @@
|
|
|
1
1
|
const { Client } = require('discord.js-selfbot-v13');
|
|
2
2
|
|
|
3
|
+
// ── Terminal Colors ──────────────────────────────────────────
|
|
3
4
|
const c = {
|
|
4
|
-
reset: '\x1b[0m',
|
|
5
|
-
green: '\x1b[32m',
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
magenta: '\x1b[35m',
|
|
10
|
-
dim: '\x1b[2m',
|
|
11
|
-
bold: '\x1b[1m',
|
|
12
|
-
white: '\x1b[37m',
|
|
13
|
-
blue: '\x1b[34m',
|
|
5
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
|
|
6
|
+
green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
7
|
+
magenta: '\x1b[35m', white: '\x1b[37m', blue: '\x1b[34m',
|
|
8
|
+
bgGreen: '\x1b[42m', bgRed: '\x1b[41m', bgYellow: '\x1b[43m', bgCyan: '\x1b[46m',
|
|
9
|
+
bgMagenta: '\x1b[45m', bgBlue: '\x1b[44m',
|
|
14
10
|
};
|
|
15
11
|
|
|
16
|
-
const
|
|
12
|
+
const WORKER_COLORS = [c.cyan, c.magenta, c.yellow, c.green, c.blue, c.red];
|
|
13
|
+
const DANK_MEMER_ID = '270904126974590976';
|
|
14
|
+
|
|
15
|
+
// ── Safe options for search/crime ──────────────────────────
|
|
16
|
+
const SAFE_SEARCH_LOCATIONS = [
|
|
17
|
+
'sofa', 'mailbox', 'dog', 'car', 'dresser', 'laundromat', 'bed',
|
|
18
|
+
'couch', 'pantry', 'fridge', 'kitchen', 'bathroom', 'attic',
|
|
19
|
+
'closet', 'shoe', 'vacuum', 'toilet', 'sink', 'shower',
|
|
20
|
+
'tree', 'grass', 'bushes', 'garden', 'park', 'backyard',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const SAFE_CRIME_OPTIONS = [
|
|
24
|
+
'tax evasion', 'fraud', 'cybercrime', 'hacking', 'identity theft',
|
|
25
|
+
'money laundering', 'tax fraud', 'insurance fraud', 'scam',
|
|
26
|
+
];
|
|
17
27
|
|
|
18
28
|
let API_KEY = '';
|
|
19
29
|
let API_URL = '';
|
|
20
30
|
const workers = [];
|
|
21
31
|
|
|
22
32
|
function log(type, msg, label) {
|
|
23
|
-
const time = new Date().toLocaleTimeString();
|
|
24
|
-
const
|
|
25
|
-
info: `${c.cyan}
|
|
33
|
+
const time = new Date().toLocaleTimeString('en-US', { hour12: true, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
34
|
+
const icons = {
|
|
35
|
+
info: `${c.cyan}│${c.reset}`,
|
|
26
36
|
success: `${c.green}✓${c.reset}`,
|
|
27
37
|
error: `${c.red}✗${c.reset}`,
|
|
28
|
-
warn: `${c.yellow}
|
|
38
|
+
warn: `${c.yellow}!${c.reset}`,
|
|
29
39
|
cmd: `${c.magenta}▸${c.reset}`,
|
|
40
|
+
coin: `${c.yellow}$${c.reset}`,
|
|
41
|
+
buy: `${c.blue}♦${c.reset}`,
|
|
42
|
+
bal: `${c.green}◈${c.reset}`,
|
|
30
43
|
};
|
|
31
|
-
const tag = label ? `${
|
|
32
|
-
console.log(` ${c.dim}${time}${c.reset} ${
|
|
44
|
+
const tag = label ? `${label} ` : '';
|
|
45
|
+
console.log(` ${c.dim}${time}${c.reset} ${icons[type] || icons.info} ${tag}${msg}`);
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
async function fetchConfig() {
|
|
@@ -38,178 +51,362 @@ async function fetchConfig() {
|
|
|
38
51
|
headers: { Authorization: `Bearer ${API_KEY}` },
|
|
39
52
|
});
|
|
40
53
|
const data = await res.json();
|
|
41
|
-
if (data.error) {
|
|
42
|
-
log('error', `Config fetch failed: ${data.error}`);
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
54
|
+
if (data.error) { log('error', `Config fetch failed: ${data.error}`); return null; }
|
|
45
55
|
return data;
|
|
46
|
-
} catch (err) {
|
|
47
|
-
log('error', `Cannot reach API: ${err.message}`);
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
56
|
+
} catch (err) { log('error', `Cannot reach API: ${err.message}`); return null; }
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
async function sendLog(command, response, status) {
|
|
53
60
|
try {
|
|
54
61
|
await fetch(`${API_URL}/api/grinder/log`, {
|
|
55
62
|
method: 'POST',
|
|
56
|
-
headers: {
|
|
57
|
-
Authorization: `Bearer ${API_KEY}`,
|
|
58
|
-
'Content-Type': 'application/json',
|
|
59
|
-
},
|
|
63
|
+
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
60
64
|
body: JSON.stringify({ command, response, status }),
|
|
61
65
|
});
|
|
62
|
-
} catch {
|
|
63
|
-
// silent fail for logging
|
|
64
|
-
}
|
|
66
|
+
} catch { /* silent */ }
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
function randomDelay(min, max) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
return new Promise((r) => setTimeout(r, (Math.random() * (max - min) + min) * 1000));
|
|
71
|
+
}
|
|
72
|
+
function humanDelay(min = 300, max = 800) {
|
|
73
|
+
return new Promise((r) => setTimeout(r, min + Math.random() * (max - min)));
|
|
74
|
+
}
|
|
75
|
+
function safeParseJSON(str, fallback = []) {
|
|
76
|
+
try { return JSON.parse(str || '[]'); } catch { return fallback; }
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
// ── Coin Parser ──────────────────────────────────────────────
|
|
80
|
+
function parseCoins(text) {
|
|
81
|
+
if (!text) return 0;
|
|
82
|
+
// Match patterns like "⏣ 1,250" or "+1,250 coins" or "**1,250**"
|
|
83
|
+
const patterns = [
|
|
84
|
+
/[⏣💰]\s*[\d,]+/g,
|
|
85
|
+
/\+\s*[\d,]+\s*coins?/gi,
|
|
86
|
+
/\*\*([\d,]+)\*\*/g,
|
|
87
|
+
/take\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
88
|
+
/earned\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
89
|
+
/found\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
90
|
+
/got\s*[⏣💰]?\s*([\d,]+)/gi,
|
|
91
|
+
];
|
|
92
|
+
let total = 0;
|
|
93
|
+
for (const pat of patterns) {
|
|
94
|
+
const matches = text.match(pat);
|
|
95
|
+
if (matches) {
|
|
96
|
+
for (const m of matches) {
|
|
97
|
+
const numStr = m.replace(/[^\d]/g, '');
|
|
98
|
+
if (numStr) total = Math.max(total, parseInt(numStr));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return total;
|
|
74
103
|
}
|
|
75
104
|
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
function getFullText(msg) {
|
|
106
|
+
let text = msg.content || '';
|
|
107
|
+
if (msg.embeds) {
|
|
108
|
+
for (const embed of msg.embeds) {
|
|
109
|
+
if (embed.title) text += ' ' + embed.title;
|
|
110
|
+
if (embed.description) text += ' ' + embed.description;
|
|
111
|
+
if (embed.fields) {
|
|
112
|
+
for (const f of embed.fields) {
|
|
113
|
+
text += ' ' + (f.name || '') + ' ' + (f.value || '');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (embed.footer?.text) text += ' ' + embed.footer.text;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return text;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Smart Button Picker ──────────────────────────────────────
|
|
123
|
+
function pickSafeButton(buttons, safeList) {
|
|
124
|
+
if (!buttons || buttons.length === 0) return null;
|
|
125
|
+
|
|
126
|
+
// First check user-configured safe answers
|
|
127
|
+
if (safeList && safeList.length > 0) {
|
|
128
|
+
for (const btn of buttons) {
|
|
129
|
+
const label = (btn.label || '').toLowerCase();
|
|
130
|
+
if (safeList.some((s) => label.includes(s.toLowerCase()))) return btn;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Then check built-in safe list
|
|
135
|
+
for (const btn of buttons) {
|
|
136
|
+
const label = (btn.label || '').toLowerCase();
|
|
137
|
+
if (SAFE_SEARCH_LOCATIONS.some((s) => label.includes(s))) return btn;
|
|
138
|
+
if (SAFE_CRIME_OPTIONS.some((s) => label.includes(s))) return btn;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Fallback: pick random
|
|
142
|
+
const clickable = buttons.filter((b) => !b.disabled);
|
|
143
|
+
return clickable.length > 0 ? clickable[Math.floor(Math.random() * clickable.length)] : null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getAllButtons(msg) {
|
|
147
|
+
const buttons = [];
|
|
148
|
+
if (msg.components) {
|
|
149
|
+
for (const row of msg.components) {
|
|
150
|
+
if (row.components) {
|
|
151
|
+
for (const comp of row.components) {
|
|
152
|
+
if (comp.type === 2) buttons.push(comp);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
81
156
|
}
|
|
157
|
+
return buttons;
|
|
82
158
|
}
|
|
83
159
|
|
|
84
|
-
//
|
|
160
|
+
// ══════════════════════════════════════════════════════════════
|
|
161
|
+
// ═ Worker: one per Discord account
|
|
162
|
+
// ══════════════════════════════════════════════════════════════
|
|
85
163
|
|
|
86
164
|
class AccountWorker {
|
|
87
165
|
constructor(account, idx) {
|
|
88
166
|
this.account = account;
|
|
89
|
-
this.
|
|
90
|
-
this.color =
|
|
167
|
+
this.idx = idx;
|
|
168
|
+
this.color = WORKER_COLORS[idx % WORKER_COLORS.length];
|
|
91
169
|
this.client = new Client();
|
|
92
170
|
this.channel = null;
|
|
93
171
|
this.running = false;
|
|
172
|
+
this.username = account.label || `Account ${idx + 1}`;
|
|
173
|
+
|
|
174
|
+
// Session stats
|
|
175
|
+
this.stats = { coins: 0, commands: 0, successes: 0, errors: 0, balance: 0 };
|
|
94
176
|
}
|
|
95
177
|
|
|
96
|
-
|
|
97
|
-
|
|
178
|
+
get tag() {
|
|
179
|
+
return `${this.color}${c.bold}${this.username}${c.reset}`;
|
|
98
180
|
}
|
|
99
181
|
|
|
182
|
+
log(type, msg) { log(type, msg, this.tag); }
|
|
183
|
+
|
|
100
184
|
waitForDankMemer(timeout = 15000) {
|
|
101
185
|
return new Promise((resolve) => {
|
|
102
186
|
const timer = setTimeout(() => {
|
|
103
187
|
this.client.removeListener('messageCreate', handler);
|
|
104
188
|
resolve(null);
|
|
105
189
|
}, timeout);
|
|
106
|
-
|
|
107
190
|
const self = this;
|
|
108
191
|
function handler(msg) {
|
|
109
|
-
if (msg.author.id ===
|
|
192
|
+
if (msg.author.id === DANK_MEMER_ID && msg.channel.id === self.channel.id) {
|
|
110
193
|
clearTimeout(timer);
|
|
111
194
|
self.client.removeListener('messageCreate', handler);
|
|
112
195
|
resolve(msg);
|
|
113
196
|
}
|
|
114
197
|
}
|
|
115
|
-
|
|
116
198
|
this.client.on('messageCreate', handler);
|
|
117
199
|
});
|
|
118
200
|
}
|
|
119
201
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const allSafe = [...searchAnswers, ...crimeAnswers];
|
|
132
|
-
|
|
133
|
-
if (allSafe.length > 0) {
|
|
134
|
-
const match = allSafe.find((a) => label.includes(a.toLowerCase()));
|
|
135
|
-
if (match) {
|
|
136
|
-
await humanDelay();
|
|
137
|
-
try {
|
|
138
|
-
await component.click();
|
|
139
|
-
return `Clicked: ${label}`;
|
|
140
|
-
} catch {
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
202
|
+
// ── Needs Item Detection ────────────────────────────────────
|
|
203
|
+
needsItem(text) {
|
|
204
|
+
const lower = text.toLowerCase();
|
|
205
|
+
if (lower.includes("don't have a shovel") || lower.includes('need to go buy') && lower.includes('shovel'))
|
|
206
|
+
return 'shovel';
|
|
207
|
+
if (lower.includes("don't have a fishing") || lower.includes('need a fishing'))
|
|
208
|
+
return 'fishing pole';
|
|
209
|
+
if (lower.includes("don't have a hunting rifle") || lower.includes('need a hunting rifle'))
|
|
210
|
+
return 'hunting rifle';
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
147
213
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
214
|
+
async buyItem(itemName) {
|
|
215
|
+
this.log('buy', `Buying ${c.bold}${itemName}${c.reset}...`);
|
|
216
|
+
await this.channel.send(`pls buy ${itemName}`);
|
|
217
|
+
const response = await this.waitForDankMemer(10000);
|
|
218
|
+
if (response) {
|
|
219
|
+
const text = getFullText(response);
|
|
220
|
+
// Sometimes Dank Memer asks for confirmation
|
|
221
|
+
const buttons = getAllButtons(response);
|
|
222
|
+
if (buttons.length > 0) {
|
|
223
|
+
const confirm = buttons.find((b) => {
|
|
224
|
+
const label = (b.label || '').toLowerCase();
|
|
225
|
+
return label.includes('yes') || label.includes('confirm') || label.includes('buy');
|
|
226
|
+
});
|
|
227
|
+
if (confirm) {
|
|
228
|
+
await humanDelay();
|
|
229
|
+
try { await confirm.click(); } catch {}
|
|
230
|
+
await this.waitForDankMemer(5000);
|
|
159
231
|
}
|
|
160
232
|
}
|
|
233
|
+
if (text.toLowerCase().includes('bought') || text.toLowerCase().includes('purchased')) {
|
|
234
|
+
this.log('success', `Bought ${c.bold}${itemName}${c.reset}!`);
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
161
237
|
}
|
|
238
|
+
this.log('warn', `Could not buy ${itemName}`);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
162
241
|
|
|
163
|
-
|
|
242
|
+
// ── Check Balance ───────────────────────────────────────────
|
|
243
|
+
async checkBalance() {
|
|
244
|
+
await this.channel.send('pls bal');
|
|
245
|
+
const response = await this.waitForDankMemer(10000);
|
|
246
|
+
if (response) {
|
|
247
|
+
const text = getFullText(response);
|
|
248
|
+
// Try to parse wallet balance
|
|
249
|
+
const walletMatch = text.match(/wallet[:\s]*[⏣💰]?\s*([\d,]+)/i);
|
|
250
|
+
if (walletMatch) {
|
|
251
|
+
this.stats.balance = parseInt(walletMatch[1].replace(/,/g, ''));
|
|
252
|
+
this.log('bal', `Wallet: ${c.bold}${c.green}⏣ ${this.stats.balance.toLocaleString()}${c.reset}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
164
255
|
}
|
|
165
256
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
257
|
+
// ── Command Handlers ────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
async handleSearch(response) {
|
|
260
|
+
if (!response) return null;
|
|
261
|
+
const buttons = getAllButtons(response);
|
|
262
|
+
if (buttons.length === 0) return getFullText(response).substring(0, 80);
|
|
263
|
+
|
|
264
|
+
const userSafe = safeParseJSON(this.account.search_answers, []);
|
|
265
|
+
const btn = pickSafeButton(buttons, userSafe);
|
|
266
|
+
if (btn) {
|
|
267
|
+
await humanDelay();
|
|
268
|
+
try {
|
|
269
|
+
await btn.click();
|
|
270
|
+
const followUp = await this.waitForDankMemer(10000);
|
|
271
|
+
if (followUp) {
|
|
272
|
+
const text = getFullText(followUp);
|
|
273
|
+
const coins = parseCoins(text);
|
|
274
|
+
if (coins > 0) {
|
|
275
|
+
this.stats.coins += coins;
|
|
276
|
+
return `${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`;
|
|
183
277
|
}
|
|
278
|
+
if (text.toLowerCase().includes('nothing')) return `${btn.label} → found nothing`;
|
|
279
|
+
return `${btn.label} → done`;
|
|
184
280
|
}
|
|
281
|
+
return `Clicked: ${btn.label}`;
|
|
282
|
+
} catch { return null; }
|
|
283
|
+
}
|
|
284
|
+
return 'No safe option found';
|
|
285
|
+
}
|
|
185
286
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
287
|
+
async handleCrime(response) {
|
|
288
|
+
if (!response) return null;
|
|
289
|
+
const buttons = getAllButtons(response);
|
|
290
|
+
if (buttons.length === 0) return getFullText(response).substring(0, 80);
|
|
291
|
+
|
|
292
|
+
const userSafe = safeParseJSON(this.account.crime_answers, []);
|
|
293
|
+
const btn = pickSafeButton(buttons, userSafe);
|
|
294
|
+
if (btn) {
|
|
295
|
+
await humanDelay();
|
|
296
|
+
try {
|
|
297
|
+
await btn.click();
|
|
298
|
+
const followUp = await this.waitForDankMemer(10000);
|
|
299
|
+
if (followUp) {
|
|
300
|
+
const text = getFullText(followUp);
|
|
301
|
+
const coins = parseCoins(text);
|
|
302
|
+
if (coins > 0) {
|
|
303
|
+
this.stats.coins += coins;
|
|
304
|
+
return `${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`;
|
|
195
305
|
}
|
|
306
|
+
return `${btn.label} → done`;
|
|
307
|
+
}
|
|
308
|
+
return `Clicked: ${btn.label}`;
|
|
309
|
+
} catch { return null; }
|
|
310
|
+
}
|
|
311
|
+
return 'No safe option found';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async handleHighLow(response) {
|
|
315
|
+
if (!response) return null;
|
|
316
|
+
const text = getFullText(response);
|
|
317
|
+
const match = text.match(/number.*?(\d+)/i) || text.match(/(\d+)/);
|
|
318
|
+
const buttons = getAllButtons(response);
|
|
319
|
+
|
|
320
|
+
if (match && buttons.length >= 2) {
|
|
321
|
+
const num = parseInt(match[1]);
|
|
322
|
+
await humanDelay();
|
|
323
|
+
let targetBtn;
|
|
324
|
+
if (num > 50) {
|
|
325
|
+
targetBtn = buttons.find((b) => (b.label || '').toLowerCase().includes('lower')) || buttons[1];
|
|
326
|
+
} else if (num < 50) {
|
|
327
|
+
targetBtn = buttons.find((b) => (b.label || '').toLowerCase().includes('higher')) || buttons[0];
|
|
328
|
+
} else {
|
|
329
|
+
// Jackpot hint
|
|
330
|
+
const jackpot = buttons.find((b) => (b.label || '').toLowerCase().includes('jackpot'));
|
|
331
|
+
targetBtn = jackpot || buttons[Math.floor(Math.random() * 2)];
|
|
332
|
+
}
|
|
196
333
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
334
|
+
try {
|
|
335
|
+
await targetBtn.click();
|
|
336
|
+
const followUp = await this.waitForDankMemer(10000);
|
|
337
|
+
if (followUp) {
|
|
338
|
+
const ftText = getFullText(followUp);
|
|
339
|
+
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
|
|
345
|
+
const moreButtons = getAllButtons(followUp);
|
|
346
|
+
if (moreButtons.length >= 2) {
|
|
347
|
+
return await this.handleHighLow(followUp);
|
|
202
348
|
}
|
|
349
|
+
return `${num} → ${targetBtn.label}`;
|
|
203
350
|
}
|
|
204
|
-
|
|
351
|
+
return `${num} → ${targetBtn.label}`;
|
|
352
|
+
} catch { return null; }
|
|
205
353
|
}
|
|
206
354
|
|
|
355
|
+
// Fallback: click random
|
|
356
|
+
if (buttons.length > 0) {
|
|
357
|
+
const btn = buttons[Math.floor(Math.random() * buttons.length)];
|
|
358
|
+
await humanDelay();
|
|
359
|
+
try { await btn.click(); } catch {}
|
|
360
|
+
return `Clicked: ${btn.label || 'button'}`;
|
|
361
|
+
}
|
|
207
362
|
return null;
|
|
208
363
|
}
|
|
209
364
|
|
|
365
|
+
async handleGenericCommand(response) {
|
|
366
|
+
if (!response) return null;
|
|
367
|
+
const text = getFullText(response);
|
|
368
|
+
const coins = parseCoins(text);
|
|
369
|
+
|
|
370
|
+
// Check if we need to buy something
|
|
371
|
+
const neededItem = this.needsItem(text);
|
|
372
|
+
if (neededItem) {
|
|
373
|
+
this.log('warn', `Missing ${c.bold}${neededItem}${c.reset} — auto-buying...`);
|
|
374
|
+
const bought = await this.buyItem(neededItem);
|
|
375
|
+
if (bought) return `auto-bought ${neededItem}`;
|
|
376
|
+
return `need ${neededItem} (couldn't buy)`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Handle buttons if present
|
|
380
|
+
const buttons = getAllButtons(response);
|
|
381
|
+
if (buttons.length > 0) {
|
|
382
|
+
const btn = buttons.find((b) => !b.disabled) || buttons[0];
|
|
383
|
+
if (btn) {
|
|
384
|
+
await humanDelay();
|
|
385
|
+
try {
|
|
386
|
+
await btn.click();
|
|
387
|
+
const followUp = await this.waitForDankMemer(8000);
|
|
388
|
+
if (followUp) {
|
|
389
|
+
const fText = getFullText(followUp);
|
|
390
|
+
const fCoins = parseCoins(fText);
|
|
391
|
+
if (fCoins > 0) { this.stats.coins += fCoins; return `${c.green}+⏣ ${fCoins.toLocaleString()}${c.reset}`; }
|
|
392
|
+
}
|
|
393
|
+
} catch {}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (coins > 0) {
|
|
398
|
+
this.stats.coins += coins;
|
|
399
|
+
return `${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return text.substring(0, 60) || 'done';
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ── Run Single Command ──────────────────────────────────────
|
|
210
406
|
async runCommand(cmdName, prefix) {
|
|
211
407
|
const cmdString = `${prefix} ${cmdName}`;
|
|
212
|
-
this.log('cmd', `${c.white}${cmdString}${c.reset}`);
|
|
408
|
+
this.log('cmd', `${c.white}${c.bold}${cmdString}${c.reset}`);
|
|
409
|
+
this.stats.commands++;
|
|
213
410
|
|
|
214
411
|
try {
|
|
215
412
|
await this.channel.send(cmdString);
|
|
@@ -217,53 +414,78 @@ class AccountWorker {
|
|
|
217
414
|
|
|
218
415
|
if (!response) {
|
|
219
416
|
this.log('warn', `No response for ${cmdString}`);
|
|
220
|
-
|
|
417
|
+
this.stats.errors++;
|
|
418
|
+
await sendLog(cmdString, 'timeout', 'timeout');
|
|
221
419
|
return;
|
|
222
420
|
}
|
|
223
421
|
|
|
224
422
|
let result;
|
|
225
|
-
|
|
226
|
-
result = await this.
|
|
227
|
-
|
|
228
|
-
result = await this.
|
|
229
|
-
|
|
230
|
-
result = response.content?.substring(0, 100) || 'Embed response';
|
|
231
|
-
if (response.components && response.components.length > 0) {
|
|
232
|
-
result = await this.handleInteraction(response);
|
|
233
|
-
}
|
|
423
|
+
switch (cmdName) {
|
|
424
|
+
case 'search': result = await this.handleSearch(response); break;
|
|
425
|
+
case 'crime': result = await this.handleCrime(response); break;
|
|
426
|
+
case 'hl': result = await this.handleHighLow(response); break;
|
|
427
|
+
default: result = await this.handleGenericCommand(response); break;
|
|
234
428
|
}
|
|
235
429
|
|
|
236
|
-
this.
|
|
430
|
+
this.stats.successes++;
|
|
431
|
+
this.log('success', `${c.dim}${cmdString}${c.reset} → ${result || 'done'}`);
|
|
237
432
|
await sendLog(cmdString, result || 'done', 'success');
|
|
238
433
|
} catch (err) {
|
|
434
|
+
this.stats.errors++;
|
|
239
435
|
this.log('error', `${cmdString} failed: ${err.message}`);
|
|
240
436
|
await sendLog(cmdString, err.message, 'error');
|
|
241
437
|
}
|
|
242
438
|
}
|
|
243
439
|
|
|
440
|
+
// ── Print Session Stats ─────────────────────────────────────
|
|
441
|
+
printStats() {
|
|
442
|
+
const rate = this.stats.commands > 0 ? ((this.stats.successes / this.stats.commands) * 100).toFixed(0) : 0;
|
|
443
|
+
this.log('info', `${c.dim}──────────────────────────────────────${c.reset}`);
|
|
444
|
+
this.log('coin', `Session: ${c.bold}${c.green}⏣ ${this.stats.coins.toLocaleString()}${c.reset} earned ${c.dim}│${c.reset} ${c.white}${this.stats.commands}${c.reset} cmds ${c.dim}│${c.reset} ${c.green}${rate}%${c.reset} success`);
|
|
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}`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ── Main Grind Loop ─────────────────────────────────────────
|
|
244
452
|
async grindLoop() {
|
|
245
453
|
if (this.running) return;
|
|
246
454
|
this.running = true;
|
|
247
455
|
|
|
248
456
|
const commandMap = {
|
|
249
|
-
cmd_hunt: 'hunt',
|
|
250
|
-
cmd_dig: 'dig',
|
|
251
|
-
cmd_beg: 'beg',
|
|
252
|
-
cmd_search: 'search',
|
|
253
|
-
cmd_hl: 'hl',
|
|
254
|
-
cmd_crime: 'crime',
|
|
255
|
-
cmd_pm: 'pm',
|
|
457
|
+
cmd_hunt: { cmd: 'hunt', cdKey: 'cd_hunt', defaultCd: 20 },
|
|
458
|
+
cmd_dig: { cmd: 'dig', cdKey: 'cd_dig', defaultCd: 20 },
|
|
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 },
|
|
256
464
|
};
|
|
257
465
|
|
|
466
|
+
let cycleCount = 0;
|
|
467
|
+
|
|
258
468
|
while (this.running) {
|
|
469
|
+
// Re-fetch config from API every cycle for live dashboard control
|
|
470
|
+
if (cycleCount > 0 && cycleCount % 3 === 0) {
|
|
471
|
+
await this.refreshConfig();
|
|
472
|
+
if (!this.account.active) {
|
|
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
|
+
}
|
|
482
|
+
|
|
259
483
|
const acc = this.account;
|
|
260
484
|
const prefix = acc.use_slash ? '/' : 'pls';
|
|
261
|
-
const minCD = acc.cooldown_min || 3;
|
|
262
|
-
const maxCD = acc.cooldown_max || 8;
|
|
263
485
|
|
|
264
486
|
const enabledCommands = Object.entries(commandMap)
|
|
265
487
|
.filter(([key]) => acc[key] === true || acc[key] === 1)
|
|
266
|
-
.map(([,
|
|
488
|
+
.map(([, val]) => val);
|
|
267
489
|
|
|
268
490
|
if (enabledCommands.length === 0) {
|
|
269
491
|
this.log('warn', 'No commands enabled. Waiting 30s...');
|
|
@@ -271,50 +493,95 @@ class AccountWorker {
|
|
|
271
493
|
continue;
|
|
272
494
|
}
|
|
273
495
|
|
|
274
|
-
for (const cmd of enabledCommands) {
|
|
496
|
+
for (const { cmd, cdKey, defaultCd } of enabledCommands) {
|
|
275
497
|
if (!this.running) break;
|
|
276
498
|
await this.runCommand(cmd, prefix);
|
|
277
|
-
|
|
499
|
+
|
|
500
|
+
// Per-command cooldown with jitter
|
|
501
|
+
const cd = (acc[cdKey] || defaultCd);
|
|
502
|
+
const jitter = 1 + Math.random() * 3; // 1-4s random jitter for safety
|
|
503
|
+
const totalWait = cd + jitter;
|
|
504
|
+
this.log('info', `${c.dim}⏳ cd ${cd}s + ${jitter.toFixed(1)}s jitter = ${totalWait.toFixed(1)}s${c.reset}`);
|
|
505
|
+
await new Promise((r) => setTimeout(r, totalWait * 1000));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
cycleCount++;
|
|
509
|
+
|
|
510
|
+
// Every 5 cycles: print stats + check balance
|
|
511
|
+
if (cycleCount % 5 === 0) {
|
|
512
|
+
this.printStats();
|
|
513
|
+
await humanDelay(500, 1500);
|
|
514
|
+
await this.checkBalance();
|
|
515
|
+
await humanDelay(2000, 4000);
|
|
278
516
|
}
|
|
279
517
|
|
|
280
518
|
const cycleDelay = 2 + Math.random() * 3;
|
|
281
|
-
this.log('info', `${c.dim}Cycle done. Next in ${cycleDelay.toFixed(1)}s${c.reset}`);
|
|
519
|
+
this.log('info', `${c.dim}Cycle ${cycleCount} done. Next in ${cycleDelay.toFixed(1)}s${c.reset}`);
|
|
282
520
|
await new Promise((r) => setTimeout(r, cycleDelay * 1000));
|
|
283
521
|
}
|
|
284
522
|
}
|
|
285
523
|
|
|
524
|
+
// ── Re-fetch Config from API ─────────────────────────────────
|
|
525
|
+
async refreshConfig() {
|
|
526
|
+
try {
|
|
527
|
+
const res = await fetch(`${API_URL}/api/grinder/status`, {
|
|
528
|
+
headers: { Authorization: `Bearer ${API_KEY}` },
|
|
529
|
+
});
|
|
530
|
+
const data = await res.json();
|
|
531
|
+
if (data.accounts) {
|
|
532
|
+
const updated = data.accounts.find((a) => a.id === this.account.id);
|
|
533
|
+
if (updated) this.account = updated;
|
|
534
|
+
}
|
|
535
|
+
} catch { /* silent */ }
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ── Start Worker ────────────────────────────────────────────
|
|
286
539
|
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
|
-
}
|
|
540
|
+
if (!this.account.discord_token) { this.log('error', 'No Discord token.'); return; }
|
|
541
|
+
if (!this.account.channel_id) { this.log('error', 'No channel ID.'); return; }
|
|
295
542
|
|
|
296
|
-
this.log('info',
|
|
543
|
+
this.log('info', `Connecting...`);
|
|
297
544
|
|
|
298
545
|
return new Promise((resolve) => {
|
|
299
546
|
this.client.on('ready', async () => {
|
|
300
|
-
|
|
301
|
-
this.
|
|
302
|
-
|
|
547
|
+
// Use the actual Discord username
|
|
548
|
+
this.username = this.client.user.tag || this.username;
|
|
549
|
+
|
|
550
|
+
// Report username back to API
|
|
551
|
+
try {
|
|
552
|
+
await fetch(`${API_URL}/api/grinder/status`, {
|
|
553
|
+
method: 'POST',
|
|
554
|
+
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
555
|
+
body: JSON.stringify({ account_id: this.account.id, discord_username: this.username }),
|
|
556
|
+
});
|
|
557
|
+
} catch { /* silent */ }
|
|
558
|
+
|
|
559
|
+
this.log('success', `Logged in! ${c.dim}(${this.client.guilds.cache.size} servers)${c.reset}`);
|
|
303
560
|
this.channel = await this.client.channels.fetch(this.account.channel_id).catch(() => null);
|
|
304
561
|
|
|
305
562
|
if (!this.channel) {
|
|
306
|
-
this.log('error', `
|
|
307
|
-
resolve();
|
|
308
|
-
return;
|
|
563
|
+
this.log('error', `Channel ${this.account.channel_id} not found`);
|
|
564
|
+
resolve(); return;
|
|
309
565
|
}
|
|
310
566
|
|
|
311
567
|
this.log('info', `Channel: ${c.white}#${this.channel.name || this.account.channel_id}${c.reset}`);
|
|
312
568
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
569
|
+
// Show per-command cooldowns
|
|
570
|
+
const cmdInfo = [
|
|
571
|
+
{ key: 'cmd_hunt', cd: 'cd_hunt', label: 'hunt' },
|
|
572
|
+
{ key: 'cmd_dig', cd: 'cd_dig', label: 'dig' },
|
|
573
|
+
{ key: 'cmd_beg', cd: 'cd_beg', label: 'beg' },
|
|
574
|
+
{ key: 'cmd_search', cd: 'cd_search', label: 'search' },
|
|
575
|
+
{ key: 'cmd_hl', cd: 'cd_hl', label: 'hl' },
|
|
576
|
+
{ key: 'cmd_crime', cd: 'cd_crime', label: 'crime' },
|
|
577
|
+
{ key: 'cmd_pm', cd: 'cd_pm', label: 'pm' },
|
|
578
|
+
].filter((ci) => this.account[ci.key] === true || this.account[ci.key] === 1);
|
|
579
|
+
|
|
580
|
+
const cmdStr = cmdInfo.map((ci) => `${ci.label}(${this.account[ci.cd] || 20}s)`).join(', ');
|
|
581
|
+
this.log('info', `Commands: ${c.white}${cmdStr || 'none'}${c.reset}`);
|
|
582
|
+
|
|
583
|
+
// Initial balance check
|
|
584
|
+
await this.checkBalance();
|
|
318
585
|
console.log('');
|
|
319
586
|
|
|
320
587
|
this.grindLoop();
|
|
@@ -331,55 +598,54 @@ class AccountWorker {
|
|
|
331
598
|
}
|
|
332
599
|
}
|
|
333
600
|
|
|
334
|
-
//
|
|
601
|
+
// ══════════════════════════════════════════════════════════════
|
|
602
|
+
// ═ Main Entry
|
|
603
|
+
// ══════════════════════════════════════════════════════════════
|
|
335
604
|
|
|
336
605
|
async function start(apiKey, apiUrl) {
|
|
337
606
|
API_KEY = apiKey;
|
|
338
607
|
API_URL = apiUrl;
|
|
339
608
|
|
|
340
609
|
console.log('');
|
|
341
|
-
console.log(` ${c.magenta}${c.bold}
|
|
342
|
-
console.log(` ${c.magenta}${c.bold}
|
|
343
|
-
console.log(` ${c.magenta}${c.bold}
|
|
344
|
-
console.log(` ${c.magenta}${c.bold}
|
|
345
|
-
console.log(` ${c.magenta}${c.bold}
|
|
346
|
-
console.log(` ${c.magenta}${c.bold}
|
|
610
|
+
console.log(` ${c.magenta}${c.bold}┌─────────────────────────────────────────┐${c.reset}`);
|
|
611
|
+
console.log(` ${c.magenta}${c.bold}│${c.reset} ${c.magenta}${c.bold}│${c.reset}`);
|
|
612
|
+
console.log(` ${c.magenta}${c.bold}│${c.reset} ${c.white}${c.bold}⚡ DankGrinder${c.reset} ${c.dim}v3.0${c.reset} ${c.magenta}${c.bold}│${c.reset}`);
|
|
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}`);
|
|
347
616
|
console.log('');
|
|
348
617
|
|
|
349
618
|
log('info', `API: ${c.dim}${API_URL}${c.reset}`);
|
|
350
|
-
log('info', 'Fetching
|
|
619
|
+
log('info', 'Fetching accounts...');
|
|
351
620
|
|
|
352
621
|
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
|
-
}
|
|
622
|
+
if (!data) { log('error', 'Failed to fetch config.'); process.exit(1); }
|
|
358
623
|
|
|
359
624
|
const { accounts } = data;
|
|
360
|
-
|
|
361
625
|
if (!accounts || accounts.length === 0) {
|
|
362
|
-
log('error', 'No active accounts
|
|
626
|
+
log('error', 'No active accounts. Add them in the dashboard.');
|
|
363
627
|
process.exit(1);
|
|
364
628
|
}
|
|
365
629
|
|
|
366
|
-
log('success',
|
|
630
|
+
log('success', `${c.bold}${accounts.length}${c.reset} active account(s) found`);
|
|
367
631
|
console.log('');
|
|
368
632
|
|
|
369
|
-
// Spawn a worker per account
|
|
370
633
|
for (let i = 0; i < accounts.length; i++) {
|
|
371
634
|
const worker = new AccountWorker(accounts[i], i);
|
|
372
635
|
workers.push(worker);
|
|
373
636
|
await worker.start();
|
|
374
637
|
}
|
|
375
638
|
|
|
376
|
-
log('info', `${c.bold}All workers running.
|
|
639
|
+
log('info', `${c.bold}All workers running.${c.reset} ${c.dim}Ctrl+C to stop.${c.reset}`);
|
|
377
640
|
console.log('');
|
|
378
641
|
|
|
379
642
|
process.on('SIGINT', () => {
|
|
380
643
|
console.log('');
|
|
381
|
-
log('warn', 'Shutting down
|
|
382
|
-
for (const w of workers)
|
|
644
|
+
log('warn', 'Shutting down...');
|
|
645
|
+
for (const w of workers) {
|
|
646
|
+
w.printStats();
|
|
647
|
+
w.stop();
|
|
648
|
+
}
|
|
383
649
|
setTimeout(() => process.exit(0), 2000);
|
|
384
650
|
});
|
|
385
651
|
}
|