dankgrinder 1.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/README.md +47 -0
- package/bin/dankgrinder.js +61 -0
- package/lib/grinder.js +387 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# DankGrinder CLI
|
|
2
|
+
|
|
3
|
+
Dank Memer automation engine — grind coins while you sleep.
|
|
4
|
+
|
|
5
|
+
## Installation & Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Run directly (no install needed)
|
|
9
|
+
npx dankgrinder --key dkg_your_api_key
|
|
10
|
+
|
|
11
|
+
# Or install globally
|
|
12
|
+
npm install -g dankgrinder
|
|
13
|
+
dankgrinder --key dkg_your_api_key
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Options
|
|
17
|
+
|
|
18
|
+
| Flag | Description | Default |
|
|
19
|
+
|------|-------------|---------|
|
|
20
|
+
| `--key <key>` | Your DankGrinder API key (required) | — |
|
|
21
|
+
| `--url <url>` | API server URL | `http://localhost:3000` |
|
|
22
|
+
| `--help` | Show help | — |
|
|
23
|
+
| `--version` | Show version | — |
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
1. Sign up at your DankGrinder dashboard
|
|
28
|
+
2. Go to **Config** → set your Discord token and channel ID
|
|
29
|
+
3. Enable the commands you want to automate
|
|
30
|
+
4. Go to **API Keys** → create a new key
|
|
31
|
+
5. Run: `npx dankgrinder --key dkg_your_key`
|
|
32
|
+
|
|
33
|
+
## Supported Commands
|
|
34
|
+
|
|
35
|
+
- `pls hunt` — Hunt animals for coins
|
|
36
|
+
- `pls dig` — Dig for treasure
|
|
37
|
+
- `pls beg` — Beg for coins
|
|
38
|
+
- `pls search` — Search locations
|
|
39
|
+
- `pls hl` — Play highlow
|
|
40
|
+
- `pls crime` — Commit crime
|
|
41
|
+
- `pls pm` — Post memes
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
- Node.js 18+
|
|
46
|
+
- A DankGrinder account with API key
|
|
47
|
+
- Discord token configured in dashboard
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { start } = require('../lib/grinder');
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
|
|
7
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
8
|
+
console.log(`
|
|
9
|
+
\x1b[35m╔══════════════════════════════════════╗
|
|
10
|
+
║ \x1b[1mDankGrinder CLI v2.0\x1b[0m\x1b[35m ║
|
|
11
|
+
║ Multi-Account Automation Engine ║
|
|
12
|
+
╚══════════════════════════════════════╝\x1b[0m
|
|
13
|
+
|
|
14
|
+
\x1b[1mUsage:\x1b[0m
|
|
15
|
+
npx dankgrinder --key <API_KEY> [options]
|
|
16
|
+
|
|
17
|
+
\x1b[1mOptions:\x1b[0m
|
|
18
|
+
--key <key> Your DankGrinder API key (required)
|
|
19
|
+
--url <url> API server URL (default: http://localhost:3000)
|
|
20
|
+
--help, -h Show this help message
|
|
21
|
+
--version, -v Show version
|
|
22
|
+
|
|
23
|
+
\x1b[1mExamples:\x1b[0m
|
|
24
|
+
npx dankgrinder --key dkg_abc123def456
|
|
25
|
+
npx dankgrinder --key dkg_abc123def456 --url https://myserver.com
|
|
26
|
+
|
|
27
|
+
\x1b[1mSetup:\x1b[0m
|
|
28
|
+
1. Sign up at your DankGrinder dashboard
|
|
29
|
+
2. Go to the Accounts page and add your Discord accounts
|
|
30
|
+
3. Configure per-account commands and cooldowns
|
|
31
|
+
4. Generate an API key from Auth Tokens
|
|
32
|
+
5. Run this CLI — it spawns one worker per active account
|
|
33
|
+
`);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
38
|
+
const pkg = require('../package.json');
|
|
39
|
+
console.log(`dankgrinder v${pkg.version}`);
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let apiKey = '';
|
|
44
|
+
let apiUrl = 'http://localhost:3000';
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < args.length; i++) {
|
|
47
|
+
if (args[i] === '--key' && args[i + 1]) apiKey = args[i + 1];
|
|
48
|
+
if (args[i] === '--url' && args[i + 1]) apiUrl = args[i + 1];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!apiKey) {
|
|
52
|
+
console.error('\x1b[31m✗ Missing API key.\x1b[0m');
|
|
53
|
+
console.error('');
|
|
54
|
+
console.error(' Usage: npx dankgrinder --key <YOUR_API_KEY>');
|
|
55
|
+
console.error('');
|
|
56
|
+
console.error(' Get your API key from the DankGrinder dashboard.');
|
|
57
|
+
console.error(' Run \x1b[36mnpx dankgrinder --help\x1b[0m for more info.');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
start(apiKey, apiUrl);
|
package/lib/grinder.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
const { Client } = require('discord.js-selfbot-v13');
|
|
2
|
+
|
|
3
|
+
const c = {
|
|
4
|
+
reset: '\x1b[0m',
|
|
5
|
+
green: '\x1b[32m',
|
|
6
|
+
red: '\x1b[31m',
|
|
7
|
+
yellow: '\x1b[33m',
|
|
8
|
+
cyan: '\x1b[36m',
|
|
9
|
+
magenta: '\x1b[35m',
|
|
10
|
+
dim: '\x1b[2m',
|
|
11
|
+
bold: '\x1b[1m',
|
|
12
|
+
white: '\x1b[37m',
|
|
13
|
+
blue: '\x1b[34m',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const ACCOUNT_COLORS = [c.cyan, c.magenta, c.yellow, c.green, c.blue, c.red];
|
|
17
|
+
|
|
18
|
+
let API_KEY = '';
|
|
19
|
+
let API_URL = '';
|
|
20
|
+
const workers = [];
|
|
21
|
+
|
|
22
|
+
function log(type, msg, label) {
|
|
23
|
+
const time = new Date().toLocaleTimeString();
|
|
24
|
+
const prefix = {
|
|
25
|
+
info: `${c.cyan}ℹ${c.reset}`,
|
|
26
|
+
success: `${c.green}✓${c.reset}`,
|
|
27
|
+
error: `${c.red}✗${c.reset}`,
|
|
28
|
+
warn: `${c.yellow}⚠${c.reset}`,
|
|
29
|
+
cmd: `${c.magenta}▸${c.reset}`,
|
|
30
|
+
};
|
|
31
|
+
const tag = label ? `${c.bold}[${label}]${c.reset} ` : '';
|
|
32
|
+
console.log(` ${c.dim}${time}${c.reset} ${prefix[type] || prefix.info} ${tag}${msg}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function fetchConfig() {
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(`${API_URL}/api/grinder/config`, {
|
|
38
|
+
headers: { Authorization: `Bearer ${API_KEY}` },
|
|
39
|
+
});
|
|
40
|
+
const data = await res.json();
|
|
41
|
+
if (data.error) {
|
|
42
|
+
log('error', `Config fetch failed: ${data.error}`);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return data;
|
|
46
|
+
} catch (err) {
|
|
47
|
+
log('error', `Cannot reach API: ${err.message}`);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function sendLog(command, response, status) {
|
|
53
|
+
try {
|
|
54
|
+
await fetch(`${API_URL}/api/grinder/log`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify({ command, response, status }),
|
|
61
|
+
});
|
|
62
|
+
} catch {
|
|
63
|
+
// silent fail for logging
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function randomDelay(min, max) {
|
|
68
|
+
const ms = (Math.random() * (max - min) + min) * 1000;
|
|
69
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function humanDelay() {
|
|
73
|
+
return new Promise((resolve) => setTimeout(resolve, 200 + Math.random() * 600));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function safeParseJSON(str, fallback) {
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(str || '[]');
|
|
79
|
+
} catch {
|
|
80
|
+
return fallback;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Worker: one per Discord account ──────────────────────────────
|
|
85
|
+
|
|
86
|
+
class AccountWorker {
|
|
87
|
+
constructor(account, idx) {
|
|
88
|
+
this.account = account;
|
|
89
|
+
this.label = account.label || `Account ${idx + 1}`;
|
|
90
|
+
this.color = ACCOUNT_COLORS[idx % ACCOUNT_COLORS.length];
|
|
91
|
+
this.client = new Client();
|
|
92
|
+
this.channel = null;
|
|
93
|
+
this.running = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
log(type, msg) {
|
|
97
|
+
log(type, msg, `${this.color}${this.label}${c.reset}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
waitForDankMemer(timeout = 15000) {
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
const timer = setTimeout(() => {
|
|
103
|
+
this.client.removeListener('messageCreate', handler);
|
|
104
|
+
resolve(null);
|
|
105
|
+
}, timeout);
|
|
106
|
+
|
|
107
|
+
const self = this;
|
|
108
|
+
function handler(msg) {
|
|
109
|
+
if (msg.author.id === '270904126974590976' && msg.channel.id === self.channel.id) {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
self.client.removeListener('messageCreate', handler);
|
|
112
|
+
resolve(msg);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.client.on('messageCreate', handler);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async handleInteraction(msg) {
|
|
121
|
+
if (!msg) return null;
|
|
122
|
+
|
|
123
|
+
if (msg.components && msg.components.length > 0) {
|
|
124
|
+
for (const row of msg.components) {
|
|
125
|
+
if (row.components) {
|
|
126
|
+
for (const component of row.components) {
|
|
127
|
+
if (component.type === 2) {
|
|
128
|
+
const label = component.label?.toLowerCase() || '';
|
|
129
|
+
const searchAnswers = safeParseJSON(this.account.search_answers, []);
|
|
130
|
+
const crimeAnswers = safeParseJSON(this.account.crime_answers, []);
|
|
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
|
+
}
|
|
147
|
+
|
|
148
|
+
const buttons = row.components.filter((comp) => comp.type === 2 && !comp.disabled);
|
|
149
|
+
if (buttons.length > 0) {
|
|
150
|
+
const btn = buttons[Math.floor(Math.random() * buttons.length)];
|
|
151
|
+
await humanDelay();
|
|
152
|
+
try {
|
|
153
|
+
await btn.click();
|
|
154
|
+
return `Clicked: ${btn.label || 'button'}`;
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return msg.content?.substring(0, 100) || 'Response received';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async handleHighLow(msg) {
|
|
167
|
+
if (!msg) return null;
|
|
168
|
+
|
|
169
|
+
if (msg.embeds && msg.embeds.length > 0) {
|
|
170
|
+
const embed = msg.embeds[0];
|
|
171
|
+
const desc = embed.description || '';
|
|
172
|
+
const match = desc.match(/(\d+)/);
|
|
173
|
+
if (match) {
|
|
174
|
+
const num = parseInt(match[1]);
|
|
175
|
+
const buttons = [];
|
|
176
|
+
if (msg.components) {
|
|
177
|
+
for (const row of msg.components) {
|
|
178
|
+
if (row.components) {
|
|
179
|
+
for (const comp of row.components) {
|
|
180
|
+
if (comp.type === 2) buttons.push(comp);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (buttons.length >= 2) {
|
|
187
|
+
await humanDelay();
|
|
188
|
+
let targetBtn;
|
|
189
|
+
if (num > 50) {
|
|
190
|
+
targetBtn = buttons.find((b) => b.label?.toLowerCase().includes('lower')) || buttons[1];
|
|
191
|
+
} else if (num < 50) {
|
|
192
|
+
targetBtn = buttons.find((b) => b.label?.toLowerCase().includes('higher')) || buttons[0];
|
|
193
|
+
} else {
|
|
194
|
+
targetBtn = buttons[Math.floor(Math.random() * 2)];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
await targetBtn.click();
|
|
199
|
+
return `HL: number was ${num}, clicked ${targetBtn.label}`;
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async runCommand(cmdName, prefix) {
|
|
211
|
+
const cmdString = `${prefix} ${cmdName}`;
|
|
212
|
+
this.log('cmd', `${c.white}${cmdString}${c.reset}`);
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
await this.channel.send(cmdString);
|
|
216
|
+
const response = await this.waitForDankMemer();
|
|
217
|
+
|
|
218
|
+
if (!response) {
|
|
219
|
+
this.log('warn', `No response for ${cmdString}`);
|
|
220
|
+
await sendLog(cmdString, 'No response (timeout)', 'timeout');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let result;
|
|
225
|
+
if (cmdName === 'hl') {
|
|
226
|
+
result = await this.handleHighLow(response);
|
|
227
|
+
} else if (['search', 'crime'].includes(cmdName)) {
|
|
228
|
+
result = await this.handleInteraction(response);
|
|
229
|
+
} else {
|
|
230
|
+
result = response.content?.substring(0, 100) || 'Embed response';
|
|
231
|
+
if (response.components && response.components.length > 0) {
|
|
232
|
+
result = await this.handleInteraction(response);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.log('success', `${cmdString} ${c.dim}→${c.reset} ${c.green}${result || 'done'}${c.reset}`);
|
|
237
|
+
await sendLog(cmdString, result || 'done', 'success');
|
|
238
|
+
} catch (err) {
|
|
239
|
+
this.log('error', `${cmdString} failed: ${err.message}`);
|
|
240
|
+
await sendLog(cmdString, err.message, 'error');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async grindLoop() {
|
|
245
|
+
if (this.running) return;
|
|
246
|
+
this.running = true;
|
|
247
|
+
|
|
248
|
+
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',
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
while (this.running) {
|
|
259
|
+
const acc = this.account;
|
|
260
|
+
const prefix = acc.use_slash ? '/' : 'pls';
|
|
261
|
+
const minCD = acc.cooldown_min || 3;
|
|
262
|
+
const maxCD = acc.cooldown_max || 8;
|
|
263
|
+
|
|
264
|
+
const enabledCommands = Object.entries(commandMap)
|
|
265
|
+
.filter(([key]) => acc[key] === true || acc[key] === 1)
|
|
266
|
+
.map(([, cmd]) => cmd);
|
|
267
|
+
|
|
268
|
+
if (enabledCommands.length === 0) {
|
|
269
|
+
this.log('warn', 'No commands enabled. Waiting 30s...');
|
|
270
|
+
await new Promise((r) => setTimeout(r, 30000));
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for (const cmd of enabledCommands) {
|
|
275
|
+
if (!this.running) break;
|
|
276
|
+
await this.runCommand(cmd, prefix);
|
|
277
|
+
await randomDelay(minCD, maxCD);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const cycleDelay = 2 + Math.random() * 3;
|
|
281
|
+
this.log('info', `${c.dim}Cycle done. Next in ${cycleDelay.toFixed(1)}s${c.reset}`);
|
|
282
|
+
await new Promise((r) => setTimeout(r, cycleDelay * 1000));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async start() {
|
|
287
|
+
if (!this.account.discord_token) {
|
|
288
|
+
this.log('error', 'No Discord token configured.');
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (!this.account.channel_id) {
|
|
292
|
+
this.log('error', 'No channel ID configured.');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
this.log('info', 'Connecting to Discord...');
|
|
297
|
+
|
|
298
|
+
return new Promise((resolve) => {
|
|
299
|
+
this.client.on('ready', async () => {
|
|
300
|
+
this.log('success', `Logged in as ${c.bold}${this.client.user.tag}${c.reset}`);
|
|
301
|
+
this.log('info', `Servers: ${c.white}${this.client.guilds.cache.size}${c.reset}`);
|
|
302
|
+
|
|
303
|
+
this.channel = await this.client.channels.fetch(this.account.channel_id).catch(() => null);
|
|
304
|
+
|
|
305
|
+
if (!this.channel) {
|
|
306
|
+
this.log('error', `Cannot find channel ${this.account.channel_id}`);
|
|
307
|
+
resolve();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
this.log('info', `Channel: ${c.white}#${this.channel.name || this.account.channel_id}${c.reset}`);
|
|
312
|
+
|
|
313
|
+
const enabled = Object.keys({ cmd_hunt: 1, cmd_dig: 1, cmd_beg: 1, cmd_search: 1, cmd_hl: 1, cmd_crime: 1, cmd_pm: 1 })
|
|
314
|
+
.filter((k) => this.account[k] === true || this.account[k] === 1)
|
|
315
|
+
.map((k) => k.replace('cmd_', ''));
|
|
316
|
+
this.log('info', `Commands: ${c.white}${enabled.join(', ') || 'none'}${c.reset}`);
|
|
317
|
+
this.log('info', `Cooldown: ${c.white}${this.account.cooldown_min || 3}-${this.account.cooldown_max || 8}s${c.reset}`);
|
|
318
|
+
console.log('');
|
|
319
|
+
|
|
320
|
+
this.grindLoop();
|
|
321
|
+
resolve();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
this.client.login(this.account.discord_token);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
stop() {
|
|
329
|
+
this.running = false;
|
|
330
|
+
try { this.client.destroy(); } catch {}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── Main Entry ──────────────────────────────────
|
|
335
|
+
|
|
336
|
+
async function start(apiKey, apiUrl) {
|
|
337
|
+
API_KEY = apiKey;
|
|
338
|
+
API_URL = apiUrl;
|
|
339
|
+
|
|
340
|
+
console.log('');
|
|
341
|
+
console.log(` ${c.magenta}${c.bold}╔══════════════════════════════════════════╗${c.reset}`);
|
|
342
|
+
console.log(` ${c.magenta}${c.bold}║${c.reset} ${c.magenta}${c.bold}║${c.reset}`);
|
|
343
|
+
console.log(` ${c.magenta}${c.bold}║${c.reset} ${c.white}${c.bold}DankGrinder${c.reset} ${c.dim}v2.0${c.reset} ${c.magenta}${c.bold}║${c.reset}`);
|
|
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}`);
|
|
347
|
+
console.log('');
|
|
348
|
+
|
|
349
|
+
log('info', `API: ${c.dim}${API_URL}${c.reset}`);
|
|
350
|
+
log('info', 'Fetching config & accounts...');
|
|
351
|
+
|
|
352
|
+
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
|
+
}
|
|
358
|
+
|
|
359
|
+
const { accounts } = data;
|
|
360
|
+
|
|
361
|
+
if (!accounts || accounts.length === 0) {
|
|
362
|
+
log('error', 'No active accounts found. Add accounts in the dashboard.');
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
log('success', `Found ${c.bold}${accounts.length}${c.reset} active account(s)`);
|
|
367
|
+
console.log('');
|
|
368
|
+
|
|
369
|
+
// Spawn a worker per account
|
|
370
|
+
for (let i = 0; i < accounts.length; i++) {
|
|
371
|
+
const worker = new AccountWorker(accounts[i], i);
|
|
372
|
+
workers.push(worker);
|
|
373
|
+
await worker.start();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
log('info', `${c.bold}All workers running. Press Ctrl+C to stop.${c.reset}`);
|
|
377
|
+
console.log('');
|
|
378
|
+
|
|
379
|
+
process.on('SIGINT', () => {
|
|
380
|
+
console.log('');
|
|
381
|
+
log('warn', 'Shutting down all workers...');
|
|
382
|
+
for (const w of workers) w.stop();
|
|
383
|
+
setTimeout(() => process.exit(0), 2000);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
module.exports = { start };
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dankgrinder",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Dank Memer automation engine — grind coins while you sleep",
|
|
5
|
+
"bin": {
|
|
6
|
+
"dankgrinder": "./bin/dankgrinder.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"lib/",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"dank-memer",
|
|
15
|
+
"discord",
|
|
16
|
+
"grinder",
|
|
17
|
+
"automation",
|
|
18
|
+
"selfbot"
|
|
19
|
+
],
|
|
20
|
+
"author": "DankGrinder",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"discord.js-selfbot-v13": "^3.6.1"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|