dankgrinder 4.1.2 → 4.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dankgrinder.js +8 -4
- package/lib/commands/adventure.js +6 -3
- package/lib/commands/crime.js +23 -6
- package/lib/commands/generic.js +6 -3
- package/lib/commands/index.js +2 -0
- package/lib/commands/search.js +24 -9
- package/lib/commands/shop.js +9 -3
- package/lib/commands/stream.js +96 -0
- package/lib/commands/utils.js +25 -10
- package/lib/grinder.js +224 -76
- package/package.json +1 -1
package/bin/dankgrinder.js
CHANGED
|
@@ -19,10 +19,11 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
19
19
|
npx dankgrinder --key <API_KEY>
|
|
20
20
|
|
|
21
21
|
${C.b}Options:${C.r}
|
|
22
|
-
--key <key>
|
|
23
|
-
--url <url>
|
|
24
|
-
--
|
|
25
|
-
--
|
|
22
|
+
--key <key> API key from dashboard (required)
|
|
23
|
+
--url <url> Override dashboard URL (default: ${DEFAULT_URL})
|
|
24
|
+
--redis <url> Redis URL for cooldowns & trivia DB
|
|
25
|
+
--help, -h Show this help
|
|
26
|
+
--version, -v Show version
|
|
26
27
|
`);
|
|
27
28
|
process.exit(0);
|
|
28
29
|
}
|
|
@@ -34,13 +35,16 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
34
35
|
|
|
35
36
|
let apiKey = process.env.DANKGRINDER_KEY || '';
|
|
36
37
|
let apiUrl = '';
|
|
38
|
+
let redisUrl = '';
|
|
37
39
|
|
|
38
40
|
for (let i = 0; i < args.length; i++) {
|
|
39
41
|
if (args[i] === '--key' && args[i + 1]) apiKey = args[i + 1];
|
|
40
42
|
if (args[i] === '--url' && args[i + 1]) apiUrl = args[i + 1];
|
|
43
|
+
if (args[i] === '--redis' && args[i + 1]) redisUrl = args[i + 1];
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
apiUrl = apiUrl || process.env.DANKGRINDER_URL || DEFAULT_URL;
|
|
47
|
+
if (redisUrl) process.env.REDIS_URL = redisUrl;
|
|
44
48
|
|
|
45
49
|
if (!apiKey) {
|
|
46
50
|
console.error(`\n ${C.red}✗ Missing API key.${C.r}\n`);
|
|
@@ -365,9 +365,12 @@ async function runAdventure({ channel, waitForDankMemer, client }) {
|
|
|
365
365
|
|
|
366
366
|
// ── Select adventure type from dropdown ─────────────────────
|
|
367
367
|
if (menus.length > 0) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
368
|
+
try {
|
|
369
|
+
if (channel.messages && typeof channel.messages.fetch === 'function') {
|
|
370
|
+
const freshMsg = await channel.messages.fetch(response.id);
|
|
371
|
+
if (freshMsg) response = freshMsg;
|
|
372
|
+
}
|
|
373
|
+
} catch { /* proceed with original */ }
|
|
371
374
|
|
|
372
375
|
// Find the select menu row index
|
|
373
376
|
let menuRowIdx = -1;
|
package/lib/commands/crime.js
CHANGED
|
@@ -12,21 +12,38 @@ const SAFE_CRIME_OPTIONS = [
|
|
|
12
12
|
'tax evasion', 'fraud', 'cybercrime', 'hacking', 'identity theft',
|
|
13
13
|
'money laundering', 'tax fraud', 'insurance fraud', 'scam',
|
|
14
14
|
];
|
|
15
|
+
const SAFE_CRIME_SET = new Set(SAFE_CRIME_OPTIONS);
|
|
16
|
+
|
|
17
|
+
const RISKY_CRIME_OPTIONS = new Set([
|
|
18
|
+
'murder', 'arson', 'assault', 'kidnap', 'terrorism',
|
|
19
|
+
]);
|
|
15
20
|
|
|
16
21
|
function pickSafeButton(buttons, customSafe) {
|
|
17
22
|
if (!buttons || buttons.length === 0) return null;
|
|
23
|
+
const clickable = buttons.filter(b => !b.disabled);
|
|
24
|
+
if (clickable.length === 0) return null;
|
|
25
|
+
|
|
18
26
|
if (customSafe && customSafe.length > 0) {
|
|
19
|
-
|
|
27
|
+
const customSet = new Set(customSafe.map(s => s.toLowerCase()));
|
|
28
|
+
for (const btn of clickable) {
|
|
20
29
|
const label = (btn.label || '').toLowerCase();
|
|
21
|
-
|
|
30
|
+
for (const s of customSet) { if (label.includes(s)) return btn; }
|
|
22
31
|
}
|
|
23
32
|
}
|
|
24
|
-
|
|
33
|
+
|
|
34
|
+
for (const btn of clickable) {
|
|
25
35
|
const label = (btn.label || '').toLowerCase();
|
|
26
|
-
|
|
36
|
+
for (const s of SAFE_CRIME_SET) { if (label.includes(s)) return btn; }
|
|
27
37
|
}
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
|
|
39
|
+
const safe = clickable.filter(b => {
|
|
40
|
+
const label = (b.label || '').toLowerCase();
|
|
41
|
+
for (const r of RISKY_CRIME_OPTIONS) { if (label.includes(r)) return false; }
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const pool = safe.length > 0 ? safe : clickable;
|
|
46
|
+
return pool[Math.floor(Math.random() * pool.length)];
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
/**
|
package/lib/commands/generic.js
CHANGED
|
@@ -99,9 +99,12 @@ async function runGeneric({ channel, waitForDankMemer, cmdString, cmdName, clien
|
|
|
99
99
|
// Handle select menus
|
|
100
100
|
const menus = getAllSelectMenus(response);
|
|
101
101
|
if (menus.length > 0) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
try {
|
|
103
|
+
if (channel.messages && typeof channel.messages.fetch === 'function') {
|
|
104
|
+
const freshMsg = await channel.messages.fetch(response.id);
|
|
105
|
+
if (freshMsg) response = freshMsg;
|
|
106
|
+
}
|
|
107
|
+
} catch { /* proceed with original */ }
|
|
105
108
|
// Find row index of first select menu
|
|
106
109
|
let menuRowIdx = -1;
|
|
107
110
|
for (let i = 0; i < (response.components || []).length; i++) {
|
package/lib/commands/index.js
CHANGED
|
@@ -19,6 +19,7 @@ const { runWorkShift } = require('./work');
|
|
|
19
19
|
const { runCoinflip, runRoulette, runSlots, runSnakeeyes, runGamble } = require('./gamble');
|
|
20
20
|
const { runDeposit } = require('./deposit');
|
|
21
21
|
const { runGeneric, runAlert } = require('./generic');
|
|
22
|
+
const { runStream } = require('./stream');
|
|
22
23
|
const { buyItem, ITEM_COSTS } = require('./shop');
|
|
23
24
|
const { getPlayerLevel, meetsLevelRequirement } = require('./profile');
|
|
24
25
|
|
|
@@ -45,6 +46,7 @@ module.exports = {
|
|
|
45
46
|
runDeposit,
|
|
46
47
|
runGeneric,
|
|
47
48
|
runAlert,
|
|
49
|
+
runStream,
|
|
48
50
|
buyItem,
|
|
49
51
|
|
|
50
52
|
// Profile / Level
|
package/lib/commands/search.js
CHANGED
|
@@ -14,24 +14,39 @@ const SAFE_SEARCH_LOCATIONS = [
|
|
|
14
14
|
'closet', 'shoe', 'vacuum', 'toilet', 'sink', 'shower',
|
|
15
15
|
'tree', 'grass', 'bushes', 'garden', 'park', 'backyard',
|
|
16
16
|
];
|
|
17
|
+
const SAFE_SET = new Set(SAFE_SEARCH_LOCATIONS);
|
|
18
|
+
|
|
19
|
+
const DANGEROUS_LOCATIONS = new Set([
|
|
20
|
+
'police', 'area 51', 'jail', 'hospital', 'sewer',
|
|
21
|
+
'gun', 'warehouse', 'casino', 'bank', 'airport',
|
|
22
|
+
]);
|
|
17
23
|
|
|
18
24
|
function pickSafeButton(buttons, customSafe) {
|
|
19
25
|
if (!buttons || buttons.length === 0) return null;
|
|
20
|
-
|
|
26
|
+
const clickable = buttons.filter(b => !b.disabled);
|
|
27
|
+
if (clickable.length === 0) return null;
|
|
28
|
+
|
|
21
29
|
if (customSafe && customSafe.length > 0) {
|
|
22
|
-
|
|
30
|
+
const customSet = new Set(customSafe.map(s => s.toLowerCase()));
|
|
31
|
+
for (const btn of clickable) {
|
|
23
32
|
const label = (btn.label || '').toLowerCase();
|
|
24
|
-
|
|
33
|
+
for (const s of customSet) { if (label.includes(s)) return btn; }
|
|
25
34
|
}
|
|
26
35
|
}
|
|
27
|
-
|
|
28
|
-
for (const btn of
|
|
36
|
+
|
|
37
|
+
for (const btn of clickable) {
|
|
29
38
|
const label = (btn.label || '').toLowerCase();
|
|
30
|
-
|
|
39
|
+
for (const s of SAFE_SET) { if (label.includes(s)) return btn; }
|
|
31
40
|
}
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
41
|
+
|
|
42
|
+
const safe = clickable.filter(b => {
|
|
43
|
+
const label = (b.label || '').toLowerCase();
|
|
44
|
+
for (const d of DANGEROUS_LOCATIONS) { if (label.includes(d)) return false; }
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const pool = safe.length > 0 ? safe : clickable;
|
|
49
|
+
return pool[Math.floor(Math.random() * pool.length)];
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
/**
|
package/lib/commands/shop.js
CHANGED
|
@@ -16,6 +16,8 @@ const ITEM_COSTS = {
|
|
|
16
16
|
'shovel': 25000,
|
|
17
17
|
'fishing pole': 25000,
|
|
18
18
|
'adventure ticket': 250000,
|
|
19
|
+
'keyboard': 10000,
|
|
20
|
+
'mouse': 10000,
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -77,9 +79,13 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
// Step 2: Navigate to Coin Shop tab
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
// Try re-fetch for hydrated components; skip if unavailable
|
|
83
|
+
try {
|
|
84
|
+
if (channel.messages && typeof channel.messages.fetch === 'function') {
|
|
85
|
+
const freshShopMsg = await channel.messages.fetch(response.id);
|
|
86
|
+
if (freshShopMsg) response = freshShopMsg;
|
|
87
|
+
}
|
|
88
|
+
} catch { /* proceed with original response */ }
|
|
83
89
|
|
|
84
90
|
const csInfo = findSelectMenuOption(response, 'Coin Shop');
|
|
85
91
|
if (csInfo) {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const {
|
|
2
|
+
LOG, c, getFullText, parseCoins, getAllButtons,
|
|
3
|
+
safeClickButton, logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, needsItem,
|
|
4
|
+
} = require('./utils');
|
|
5
|
+
const { buyItem } = require('./shop');
|
|
6
|
+
|
|
7
|
+
const STREAM_ITEMS = ['keyboard', 'mouse'];
|
|
8
|
+
|
|
9
|
+
async function runStream({ channel, waitForDankMemer, client }) {
|
|
10
|
+
LOG.cmd(`${c.white}${c.bold}pls stream${c.reset}`);
|
|
11
|
+
|
|
12
|
+
await channel.send('pls stream');
|
|
13
|
+
let response = await waitForDankMemer(10000);
|
|
14
|
+
|
|
15
|
+
if (!response) {
|
|
16
|
+
LOG.warn('[stream] No response');
|
|
17
|
+
return { result: 'no response', coins: 0 };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (isHoldTight(response)) {
|
|
21
|
+
const reason = getHoldTightReason(response);
|
|
22
|
+
LOG.warn(`[stream] Hold Tight${reason ? ` (reason: /${reason})` : ''}`);
|
|
23
|
+
await sleep(30000);
|
|
24
|
+
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
logMsg(response, 'stream');
|
|
28
|
+
let text = getFullText(response);
|
|
29
|
+
|
|
30
|
+
const lower = text.toLowerCase();
|
|
31
|
+
if (lower.includes('missing items') || lower.includes('need following items') || lower.includes('need a keyboard') || lower.includes('need a mouse')) {
|
|
32
|
+
const itemsToBuy = [];
|
|
33
|
+
if (lower.includes('keyboard')) itemsToBuy.push('keyboard');
|
|
34
|
+
if (lower.includes('mouse')) itemsToBuy.push('mouse');
|
|
35
|
+
|
|
36
|
+
if (itemsToBuy.length === 0) itemsToBuy.push(...STREAM_ITEMS);
|
|
37
|
+
|
|
38
|
+
LOG.warn(`[stream] Missing items: ${c.bold}${itemsToBuy.join(', ')}${c.reset} — auto-buying...`);
|
|
39
|
+
|
|
40
|
+
for (const item of itemsToBuy) {
|
|
41
|
+
const bought = await buyItem({ channel, waitForDankMemer, client, itemName: item, quantity: 1 });
|
|
42
|
+
if (bought) {
|
|
43
|
+
LOG.success(`[stream] Bought ${c.bold}${item}${c.reset}`);
|
|
44
|
+
} else {
|
|
45
|
+
LOG.error(`[stream] Failed to buy ${item}`);
|
|
46
|
+
return { result: `need ${item} (buy failed)`, coins: 0 };
|
|
47
|
+
}
|
|
48
|
+
await humanDelay(1500, 2500);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
LOG.info('[stream] Retrying stream after buying items...');
|
|
52
|
+
await sleep(3000);
|
|
53
|
+
await channel.send('pls stream');
|
|
54
|
+
response = await waitForDankMemer(10000);
|
|
55
|
+
if (!response) return { result: 'no response after buy', coins: 0 };
|
|
56
|
+
logMsg(response, 'stream-retry');
|
|
57
|
+
text = getFullText(response);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const coins = parseCoins(text);
|
|
61
|
+
|
|
62
|
+
const buttons = getAllButtons(response);
|
|
63
|
+
if (buttons.length > 0) {
|
|
64
|
+
const actionBtn = buttons.find(b => !b.disabled && b.label &&
|
|
65
|
+
!b.label.toLowerCase().includes('end') && !b.label.toLowerCase().includes('stop'));
|
|
66
|
+
const btn = actionBtn || buttons.find(b => !b.disabled);
|
|
67
|
+
if (btn) {
|
|
68
|
+
LOG.info(`[stream] Clicking "${btn.label || '?'}"`);
|
|
69
|
+
await humanDelay();
|
|
70
|
+
try {
|
|
71
|
+
await safeClickButton(response, btn);
|
|
72
|
+
const followUp = await waitForDankMemer(8000);
|
|
73
|
+
if (followUp) {
|
|
74
|
+
logMsg(followUp, 'stream-action');
|
|
75
|
+
const fText = getFullText(followUp);
|
|
76
|
+
const fCoins = parseCoins(fText);
|
|
77
|
+
if (fCoins > 0) {
|
|
78
|
+
LOG.coin(`[stream] ${c.green}+⏣ ${fCoins.toLocaleString()}${c.reset}`);
|
|
79
|
+
return { result: `+⏣ ${fCoins.toLocaleString()}`, coins: fCoins };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
LOG.error(`[stream] Click error: ${e.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (coins > 0) {
|
|
89
|
+
LOG.coin(`[stream] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
90
|
+
return { result: `+⏣ ${coins.toLocaleString()}`, coins };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { result: text.substring(0, 60) || 'streamed', coins: 0, nextCooldownSec: 600 };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { runStream };
|
package/lib/commands/utils.js
CHANGED
|
@@ -13,7 +13,13 @@ const c = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
// ── Logging ──────────────────────────────────────────────────
|
|
16
|
+
// When dashboard is active, suppress direct console output from command handlers.
|
|
17
|
+
// grinder.js sets this to true once the live dashboard starts rendering.
|
|
18
|
+
let _dashboardActive = false;
|
|
19
|
+
function setDashboardActive(val) { _dashboardActive = val; }
|
|
20
|
+
|
|
16
21
|
function log(label, msg) {
|
|
22
|
+
if (_dashboardActive) return;
|
|
17
23
|
const time = new Date().toLocaleTimeString('en-US', { hour12: true, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
18
24
|
console.log(` ${c.dim}${time}${c.reset} ${label} ${msg}`);
|
|
19
25
|
}
|
|
@@ -250,18 +256,26 @@ function dumpMessage(msg, label) {
|
|
|
250
256
|
}
|
|
251
257
|
|
|
252
258
|
// ── Item Detection ───────────────────────────────────────────
|
|
259
|
+
const ITEM_PATTERNS = [
|
|
260
|
+
{ item: 'shovel', patterns: ["don't have a shovel", 'need a shovel', 'you need a shovel'] },
|
|
261
|
+
{ item: 'fishing pole', patterns: ["don't have a fishing", 'need a fishing', 'you need a fishing pole'] },
|
|
262
|
+
{ item: 'hunting rifle', patterns: ["don't have a hunting rifle", 'need a hunting rifle', 'you need a rifle'] },
|
|
263
|
+
{ item: 'adventure ticket', patterns: ["don't have a ticket", "don't have an adventure", 'need a ticket', 'need an adventure ticket'] },
|
|
264
|
+
{ item: 'keyboard', patterns: ["don't have a keyboard", 'need a keyboard', 'you need a keyboard', 'need following items'] },
|
|
265
|
+
{ item: 'mouse', patterns: ["don't have a mouse", 'need a mouse', 'you need a mouse'] },
|
|
266
|
+
];
|
|
267
|
+
|
|
253
268
|
function needsItem(text) {
|
|
254
269
|
const lower = text.toLowerCase();
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return 'adventure ticket';
|
|
270
|
+
for (const { item, patterns } of ITEM_PATTERNS) {
|
|
271
|
+
for (const p of patterns) {
|
|
272
|
+
if (lower.includes(p)) return item;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (lower.includes('need following items') || lower.includes('missing items')) {
|
|
276
|
+
if (lower.includes('keyboard')) return 'keyboard';
|
|
277
|
+
if (lower.includes('mouse')) return 'mouse';
|
|
278
|
+
}
|
|
265
279
|
return null;
|
|
266
280
|
}
|
|
267
281
|
|
|
@@ -269,6 +283,7 @@ module.exports = {
|
|
|
269
283
|
DANK_MEMER_ID,
|
|
270
284
|
c,
|
|
271
285
|
LOG,
|
|
286
|
+
setDashboardActive,
|
|
272
287
|
sleep,
|
|
273
288
|
humanDelay,
|
|
274
289
|
getFullText,
|
package/lib/grinder.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { Client } = require('discord.js-selfbot-v13');
|
|
2
2
|
const Redis = require('ioredis');
|
|
3
3
|
const commands = require('./commands');
|
|
4
|
+
const { setDashboardActive } = require('./commands/utils');
|
|
4
5
|
|
|
5
6
|
// ── Terminal Colors & ANSI ───────────────────────────────────
|
|
6
7
|
const c = {
|
|
@@ -108,6 +109,8 @@ function colorBanner() {
|
|
|
108
109
|
let dashboardLines = 0;
|
|
109
110
|
let dashboardStarted = false;
|
|
110
111
|
let dashboardRendering = false;
|
|
112
|
+
let lastRenderTime = 0;
|
|
113
|
+
let renderPending = false;
|
|
111
114
|
let totalBalance = 0;
|
|
112
115
|
let totalCoins = 0;
|
|
113
116
|
let totalCommands = 0;
|
|
@@ -115,6 +118,7 @@ let startTime = Date.now();
|
|
|
115
118
|
let shutdownCalled = false;
|
|
116
119
|
const recentLogs = [];
|
|
117
120
|
const MAX_LOGS = 4;
|
|
121
|
+
const RENDER_THROTTLE_MS = 250;
|
|
118
122
|
|
|
119
123
|
function formatUptime() {
|
|
120
124
|
const s = Math.floor((Date.now() - startTime) / 1000);
|
|
@@ -132,9 +136,22 @@ function formatCoins(n) {
|
|
|
132
136
|
return n.toLocaleString();
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
function scheduleRender() {
|
|
140
|
+
if (renderPending || !dashboardStarted) return;
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
const elapsed = now - lastRenderTime;
|
|
143
|
+
if (elapsed >= RENDER_THROTTLE_MS) {
|
|
144
|
+
renderDashboard();
|
|
145
|
+
} else {
|
|
146
|
+
renderPending = true;
|
|
147
|
+
setTimeout(() => { renderPending = false; renderDashboard(); }, RENDER_THROTTLE_MS - elapsed);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
135
151
|
function renderDashboard() {
|
|
136
|
-
if (!dashboardStarted || workers.length === 0 || dashboardRendering) return;
|
|
152
|
+
if (!dashboardStarted || workers.length === 0 || dashboardRendering || shutdownCalled) return;
|
|
137
153
|
dashboardRendering = true;
|
|
154
|
+
lastRenderTime = Date.now();
|
|
138
155
|
|
|
139
156
|
totalBalance = 0; totalCoins = 0; totalCommands = 0;
|
|
140
157
|
for (const w of workers) {
|
|
@@ -144,7 +161,7 @@ function renderDashboard() {
|
|
|
144
161
|
}
|
|
145
162
|
|
|
146
163
|
const lines = [];
|
|
147
|
-
const tw = Math.min(process.stdout.columns || 80,
|
|
164
|
+
const tw = Math.min(process.stdout.columns || 80, 76);
|
|
148
165
|
const thinBar = c.dim + '─'.repeat(tw) + c.reset;
|
|
149
166
|
const thickTop = rgb(139, 92, 246) + c.bold + '═'.repeat(tw) + c.reset;
|
|
150
167
|
const thickBot = rgb(34, 211, 238) + c.bold + '═'.repeat(tw) + c.reset;
|
|
@@ -152,30 +169,37 @@ function renderDashboard() {
|
|
|
152
169
|
lines.push(thickTop);
|
|
153
170
|
lines.push(
|
|
154
171
|
` ${rgb(192, 132, 252)}${c.bold}⏣ ${formatCoins(totalBalance)}${c.reset}` +
|
|
155
|
-
`
|
|
156
|
-
`
|
|
157
|
-
`
|
|
158
|
-
`
|
|
159
|
-
`
|
|
160
|
-
`
|
|
161
|
-
`
|
|
162
|
-
`
|
|
172
|
+
` ${c.dim}│${c.reset}` +
|
|
173
|
+
` ${rgb(52, 211, 153)}+${formatCoins(totalCoins)}${c.reset}` +
|
|
174
|
+
` ${c.dim}│${c.reset}` +
|
|
175
|
+
` ${c.white}${totalCommands}${c.reset}${c.dim} cmds${c.reset}` +
|
|
176
|
+
` ${c.dim}│${c.reset}` +
|
|
177
|
+
` ${rgb(251, 191, 36)}${formatUptime()}${c.reset}` +
|
|
178
|
+
` ${c.dim}│${c.reset}` +
|
|
179
|
+
` ${rgb(52, 211, 153)}● LIVE${c.reset}`
|
|
163
180
|
);
|
|
164
181
|
lines.push(thinBar);
|
|
165
182
|
|
|
183
|
+
const nameWidth = Math.min(14, tw > 60 ? 14 : 10);
|
|
184
|
+
const statusWidth = Math.max(12, tw - nameWidth - 30);
|
|
185
|
+
|
|
166
186
|
for (const wk of workers) {
|
|
167
|
-
const
|
|
187
|
+
const rawStatus = (wk.lastStatus || 'idle').replace(/\x1b\[[0-9;]*m/g, '');
|
|
188
|
+
const last = rawStatus.substring(0, statusWidth);
|
|
168
189
|
const dot = wk.running
|
|
169
|
-
? (wk.paused ? `${rgb(
|
|
190
|
+
? (wk.paused ? `${rgb(239, 68, 68)}⏸${c.reset}`
|
|
191
|
+
: wk.dashboardPaused ? `${rgb(251, 191, 36)}⏸${c.reset}`
|
|
192
|
+
: wk.busy ? `${rgb(251, 191, 36)}◉${c.reset}`
|
|
193
|
+
: `${rgb(52, 211, 153)}●${c.reset}`)
|
|
170
194
|
: `${rgb(239, 68, 68)}○${c.reset}`;
|
|
171
|
-
const name = `${wk.color}${c.bold}${(wk.username || '?').substring(0,
|
|
195
|
+
const name = `${wk.color}${c.bold}${(wk.username || '?').substring(0, nameWidth).padEnd(nameWidth)}${c.reset}`;
|
|
172
196
|
const bal = wk.stats.balance > 0
|
|
173
|
-
? `${rgb(251, 191, 36)}⏣${c.reset}${c.white}${
|
|
174
|
-
: `${c.dim}⏣
|
|
197
|
+
? `${rgb(251, 191, 36)}⏣${c.reset}${c.white}${formatCoins(wk.stats.balance).padStart(6)}${c.reset}`
|
|
198
|
+
: `${c.dim}⏣ -${c.reset}`;
|
|
175
199
|
const earned = wk.stats.coins > 0
|
|
176
|
-
? `${rgb(52, 211, 153)}+${formatCoins(wk.stats.coins)
|
|
177
|
-
: `${c.dim}
|
|
178
|
-
lines.push(` ${dot} ${name} ${bal}
|
|
200
|
+
? `${rgb(52, 211, 153)}+${formatCoins(wk.stats.coins)}${c.reset}`
|
|
201
|
+
: `${c.dim}+0${c.reset}`;
|
|
202
|
+
lines.push(` ${dot} ${name} ${bal} ${earned} ${c.dim}${last}${c.reset}`);
|
|
179
203
|
}
|
|
180
204
|
|
|
181
205
|
if (recentLogs.length > 0) {
|
|
@@ -203,12 +227,15 @@ function log(type, msg, label) {
|
|
|
203
227
|
info: '·', success: '✓', error: '✗', warn: '!',
|
|
204
228
|
cmd: '▸', coin: '$', buy: '♦', bal: '◈', debug: '·',
|
|
205
229
|
};
|
|
206
|
-
const
|
|
230
|
+
const tagRaw = label ? label.replace(/\x1b\[[0-9;]*m/g, '').substring(0, 12) : '';
|
|
207
231
|
const stripped = msg.replace(/\x1b\[[0-9;]*m/g, '');
|
|
232
|
+
const tw = Math.min(process.stdout.columns || 80, 76);
|
|
208
233
|
if (dashboardStarted) {
|
|
209
|
-
|
|
234
|
+
const maxLen = tw - 4;
|
|
235
|
+
const entry = `${time} ${icons[type] || '·'} ${tagRaw ? tagRaw + ' ' : ''}${stripped}`;
|
|
236
|
+
recentLogs.push(entry.substring(0, maxLen));
|
|
210
237
|
while (recentLogs.length > MAX_LOGS) recentLogs.shift();
|
|
211
|
-
|
|
238
|
+
scheduleRender();
|
|
212
239
|
} else {
|
|
213
240
|
const colorIcons = {
|
|
214
241
|
info: `${c.dim}·${c.reset}`, success: `${rgb(52, 211, 153)}✓${c.reset}`,
|
|
@@ -454,6 +481,8 @@ class AccountWorker {
|
|
|
454
481
|
this.cycleCount = 0;
|
|
455
482
|
this.lastCommandRun = 0;
|
|
456
483
|
this.paused = false;
|
|
484
|
+
this.dashboardPaused = false;
|
|
485
|
+
this.failStreak = 0;
|
|
457
486
|
this.globalCooldownUntil = 0;
|
|
458
487
|
this.commandQueue = null;
|
|
459
488
|
this.lastHealthCheck = Date.now();
|
|
@@ -463,13 +492,13 @@ class AccountWorker {
|
|
|
463
492
|
|
|
464
493
|
log(type, msg) {
|
|
465
494
|
const stripped = msg.replace(/\x1b\[[0-9;]*m/g, '');
|
|
466
|
-
this.lastStatus = stripped.substring(0,
|
|
495
|
+
if (type !== 'debug') this.lastStatus = stripped.substring(0, 28);
|
|
467
496
|
log(type, msg, this.tag);
|
|
468
497
|
}
|
|
469
498
|
|
|
470
499
|
setStatus(text) {
|
|
471
500
|
this.lastStatus = text;
|
|
472
|
-
if (dashboardStarted)
|
|
501
|
+
if (dashboardStarted) scheduleRender();
|
|
473
502
|
}
|
|
474
503
|
|
|
475
504
|
waitForDankMemer(timeout = 15000) {
|
|
@@ -540,15 +569,27 @@ class AccountWorker {
|
|
|
540
569
|
});
|
|
541
570
|
}
|
|
542
571
|
|
|
543
|
-
// ── Needs Item Detection
|
|
572
|
+
// ── Needs Item Detection (Trie-like pattern match) ─────────
|
|
573
|
+
static ITEM_PATTERNS = [
|
|
574
|
+
{ item: 'shovel', patterns: ["don't have a shovel", 'need a shovel', 'you need a shovel'] },
|
|
575
|
+
{ item: 'fishing pole', patterns: ["don't have a fishing", 'need a fishing', 'you need a fishing pole'] },
|
|
576
|
+
{ item: 'hunting rifle', patterns: ["don't have a hunting rifle", 'need a hunting rifle', 'you need a rifle'] },
|
|
577
|
+
{ item: 'adventure ticket', patterns: ["don't have a ticket", "don't have an adventure", 'need a ticket'] },
|
|
578
|
+
{ item: 'keyboard', patterns: ["don't have a keyboard", 'need a keyboard', 'you need a keyboard'] },
|
|
579
|
+
{ item: 'mouse', patterns: ["don't have a mouse", 'need a mouse', 'you need a mouse'] },
|
|
580
|
+
];
|
|
581
|
+
|
|
544
582
|
needsItem(text) {
|
|
545
583
|
const lower = text.toLowerCase();
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
584
|
+
for (const { item, patterns } of AccountWorker.ITEM_PATTERNS) {
|
|
585
|
+
for (const p of patterns) {
|
|
586
|
+
if (lower.includes(p)) return item;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (lower.includes('need following items') || lower.includes('missing items')) {
|
|
590
|
+
if (lower.includes('keyboard')) return 'keyboard';
|
|
591
|
+
if (lower.includes('mouse')) return 'mouse';
|
|
592
|
+
}
|
|
552
593
|
return null;
|
|
553
594
|
}
|
|
554
595
|
|
|
@@ -767,6 +808,7 @@ class AccountWorker {
|
|
|
767
808
|
case 'snakeeyes': cmdResult = await commands.runSnakeeyes(cmdOpts); break;
|
|
768
809
|
case 'dep max': cmdResult = await commands.runDeposit(cmdOpts); break;
|
|
769
810
|
case 'alert': cmdResult = await commands.runAlert(cmdOpts); break;
|
|
811
|
+
case 'stream': cmdResult = await commands.runStream(cmdOpts); break;
|
|
770
812
|
default: cmdResult = await commands.runGeneric({ ...cmdOpts, cmdString, cmdName }); break;
|
|
771
813
|
}
|
|
772
814
|
|
|
@@ -775,7 +817,7 @@ class AccountWorker {
|
|
|
775
817
|
|
|
776
818
|
// Rate limit detection
|
|
777
819
|
if (resultLower.includes('slow down') || resultLower.includes('rate limit') || resultLower.includes('too fast')) {
|
|
778
|
-
this.log('warn',
|
|
820
|
+
this.log('warn', `Rate limited! 60s global cooldown`);
|
|
779
821
|
this.globalCooldownUntil = Date.now() + 60000;
|
|
780
822
|
await this.setCooldown(cmdName, 60);
|
|
781
823
|
return;
|
|
@@ -784,13 +826,34 @@ class AccountWorker {
|
|
|
784
826
|
// Captcha/verification detection — pause immediately
|
|
785
827
|
if (resultLower.includes('captcha') || resultLower.includes('verification') ||
|
|
786
828
|
resultLower.includes('are you human') || resultLower.includes("prove you're not a bot")) {
|
|
787
|
-
this.log('error',
|
|
788
|
-
this.log('error', `${c.red}URGENT: Worker PAUSED. Re-enable manually from the dashboard.${c.reset}`);
|
|
829
|
+
this.log('error', `CAPTCHA DETECTED! Worker PAUSED.`);
|
|
789
830
|
this.paused = true;
|
|
790
831
|
await sendLog(this.username, cmdString, 'CAPTCHA DETECTED — worker paused', 'error');
|
|
791
832
|
return;
|
|
792
833
|
}
|
|
793
834
|
|
|
835
|
+
// Premium-only command detection — disable for 24h
|
|
836
|
+
if (resultLower.includes('only available on premium') || resultLower.includes('premium') ||
|
|
837
|
+
resultLower.includes('buy the ability to use this command')) {
|
|
838
|
+
this.log('warn', `${cmdName} requires premium — skipping for 24h`);
|
|
839
|
+
await this.setCooldown(cmdName, 86400);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Already claimed today (daily/weekly) — set long cooldown
|
|
844
|
+
if (resultLower.includes('already got your daily') || resultLower.includes('try again <t:')) {
|
|
845
|
+
this.log('info', `${cmdName} already claimed — waiting`);
|
|
846
|
+
const timeMatch = result.match(/<t:(\d+):R>/);
|
|
847
|
+
if (timeMatch) {
|
|
848
|
+
const nextAvail = parseInt(timeMatch[1]) * 1000;
|
|
849
|
+
const waitSec = Math.max(60, Math.ceil((nextAvail - Date.now()) / 1000));
|
|
850
|
+
await this.setCooldown(cmdName, waitSec);
|
|
851
|
+
} else {
|
|
852
|
+
await this.setCooldown(cmdName, cmdName === 'daily' ? 86400 : 604800);
|
|
853
|
+
}
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
|
|
794
857
|
const earned = Math.max(0, cmdResult.coins || 0);
|
|
795
858
|
const spent = Math.max(0, cmdResult.lost || 0);
|
|
796
859
|
if (earned > 0) this.stats.coins += earned;
|
|
@@ -798,7 +861,7 @@ class AccountWorker {
|
|
|
798
861
|
|
|
799
862
|
if (cmdResult.holdTightReason) {
|
|
800
863
|
const reason = cmdResult.holdTightReason;
|
|
801
|
-
this.log('warn', `Hold Tight
|
|
864
|
+
this.log('warn', `Hold Tight: /${reason} — 35s cooldown`);
|
|
802
865
|
const reasonMap = { postmemes: 'pm', highlow: 'hl', blackjack: 'bj', 'work shift': 'work shift' };
|
|
803
866
|
const mappedCmd = reasonMap[reason] || reason;
|
|
804
867
|
await this.setCooldown(mappedCmd, 35);
|
|
@@ -806,7 +869,7 @@ class AccountWorker {
|
|
|
806
869
|
}
|
|
807
870
|
|
|
808
871
|
this.stats.successes++;
|
|
809
|
-
const shortResult = result.substring(0,
|
|
872
|
+
const shortResult = result.substring(0, 30).replace(/\n/g, ' ');
|
|
810
873
|
this.setStatus(`${cmdName} → ${shortResult}`);
|
|
811
874
|
await sendLog(this.username, cmdString, result, 'success');
|
|
812
875
|
reportEarnings(this.account.id, this.username, earned, spent, cmdName);
|
|
@@ -869,15 +932,55 @@ class AccountWorker {
|
|
|
869
932
|
buildCommandQueue() {
|
|
870
933
|
const heap = new MinHeap();
|
|
871
934
|
const now = Date.now();
|
|
872
|
-
|
|
873
|
-
ci => this.account[ci.key]
|
|
935
|
+
let enabled = AccountWorker.COMMAND_MAP.filter(
|
|
936
|
+
ci => Boolean(this.account[ci.key])
|
|
874
937
|
);
|
|
938
|
+
|
|
939
|
+
if (enabled.length === 0) {
|
|
940
|
+
this.log('warn', `No commands enabled — auto-enabling safe defaults`);
|
|
941
|
+
const safeDefaults = ['cmd_hunt', 'cmd_dig', 'cmd_beg', 'cmd_search', 'cmd_hl', 'cmd_crime', 'cmd_pm'];
|
|
942
|
+
for (const key of safeDefaults) this.account[key] = true;
|
|
943
|
+
enabled = AccountWorker.COMMAND_MAP.filter(ci => Boolean(this.account[ci.key]));
|
|
944
|
+
}
|
|
945
|
+
|
|
875
946
|
for (const info of enabled) {
|
|
876
947
|
heap.push({ cmd: info.cmd, nextRunAt: now, priority: info.priority, info });
|
|
877
948
|
}
|
|
878
949
|
return heap;
|
|
879
950
|
}
|
|
880
951
|
|
|
952
|
+
// Merge config changes into existing queue without resetting cooldown timings.
|
|
953
|
+
// Uses a HashMap to drain old items, then rebuilds preserving nextRunAt.
|
|
954
|
+
mergeCommandQueue() {
|
|
955
|
+
const enabled = new Set(
|
|
956
|
+
AccountWorker.COMMAND_MAP.filter(ci => Boolean(this.account[ci.key])).map(ci => ci.cmd)
|
|
957
|
+
);
|
|
958
|
+
if (enabled.size === 0) return;
|
|
959
|
+
|
|
960
|
+
const existing = new Map();
|
|
961
|
+
if (this.commandQueue) {
|
|
962
|
+
while (this.commandQueue.size > 0) {
|
|
963
|
+
const item = this.commandQueue.pop();
|
|
964
|
+
existing.set(item.cmd, item);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const heap = new MinHeap();
|
|
969
|
+
const now = Date.now();
|
|
970
|
+
for (const info of AccountWorker.COMMAND_MAP) {
|
|
971
|
+
if (!enabled.has(info.cmd)) continue;
|
|
972
|
+
const old = existing.get(info.cmd);
|
|
973
|
+
if (old) {
|
|
974
|
+
old.info = info;
|
|
975
|
+
old.priority = info.priority;
|
|
976
|
+
heap.push(old);
|
|
977
|
+
} else {
|
|
978
|
+
heap.push({ cmd: info.cmd, nextRunAt: now, priority: info.priority, info });
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
this.commandQueue = heap;
|
|
982
|
+
}
|
|
983
|
+
|
|
881
984
|
// ── Health Check: verify Discord client is still connected ──
|
|
882
985
|
async healthCheck() {
|
|
883
986
|
if (!this.running || shutdownCalled) return;
|
|
@@ -906,7 +1009,12 @@ class AccountWorker {
|
|
|
906
1009
|
async tick() {
|
|
907
1010
|
if (!this.running || shutdownCalled) return;
|
|
908
1011
|
if (this.paused) {
|
|
909
|
-
this.setStatus(
|
|
1012
|
+
this.setStatus('PAUSED (captcha)');
|
|
1013
|
+
this.tickTimeout = setTimeout(() => this.tick(), 5000);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
if (this.dashboardPaused) {
|
|
1017
|
+
this.setStatus('paused (dashboard)');
|
|
910
1018
|
this.tickTimeout = setTimeout(() => this.tick(), 5000);
|
|
911
1019
|
return;
|
|
912
1020
|
}
|
|
@@ -915,46 +1023,46 @@ class AccountWorker {
|
|
|
915
1023
|
return;
|
|
916
1024
|
}
|
|
917
1025
|
|
|
918
|
-
// Global rate-limit cooldown
|
|
919
1026
|
const now = Date.now();
|
|
1027
|
+
|
|
920
1028
|
if (now < this.globalCooldownUntil) {
|
|
921
1029
|
const waitSec = Math.ceil((this.globalCooldownUntil - now) / 1000);
|
|
922
|
-
this.setStatus(
|
|
1030
|
+
this.setStatus(`rate limited (${waitSec}s)`);
|
|
923
1031
|
this.tickTimeout = setTimeout(() => this.tick(), 2000);
|
|
924
1032
|
return;
|
|
925
1033
|
}
|
|
926
1034
|
|
|
927
|
-
// Periodic health check
|
|
928
1035
|
try { await this.healthCheck(); } catch (e) {
|
|
929
1036
|
this.log('error', `Health check error: ${e.message}`);
|
|
930
1037
|
}
|
|
931
1038
|
|
|
932
|
-
// Rebuild queue if it doesn't exist or is empty (e.g. after config refresh)
|
|
933
1039
|
if (!this.commandQueue || this.commandQueue.size === 0) {
|
|
934
1040
|
this.commandQueue = this.buildCommandQueue();
|
|
935
1041
|
}
|
|
936
|
-
|
|
937
|
-
if (this.commandQueue.size === 0) {
|
|
1042
|
+
if (!this.commandQueue || this.commandQueue.size === 0) {
|
|
938
1043
|
this.tickTimeout = setTimeout(() => this.tick(), 15000);
|
|
939
1044
|
return;
|
|
940
1045
|
}
|
|
941
1046
|
|
|
942
|
-
// Peek the top item — is it ready?
|
|
943
1047
|
const top = this.commandQueue.peek();
|
|
944
1048
|
if (top.nextRunAt > now) {
|
|
945
1049
|
const waitMs = Math.min(top.nextRunAt - now, 2000);
|
|
946
|
-
this.setStatus(
|
|
1050
|
+
this.setStatus('cooldown...');
|
|
947
1051
|
this.tickTimeout = setTimeout(() => this.tick(), waitMs);
|
|
948
1052
|
return;
|
|
949
1053
|
}
|
|
950
1054
|
|
|
951
|
-
// Pop the command, check Redis cooldown as a secondary gate
|
|
952
1055
|
const item = this.commandQueue.pop();
|
|
1056
|
+
if (!item) {
|
|
1057
|
+
this.tickTimeout = setTimeout(() => this.tick(), 1000);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
953
1061
|
const ready = await this.isCooldownReady(item.cmd);
|
|
954
1062
|
if (!ready) {
|
|
955
1063
|
const cd = (this.account[item.info.cdKey] || item.info.defaultCd);
|
|
956
1064
|
item.nextRunAt = now + cd * 1000;
|
|
957
|
-
this.commandQueue.push(item);
|
|
1065
|
+
if (this.commandQueue) this.commandQueue.push(item);
|
|
958
1066
|
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
959
1067
|
return;
|
|
960
1068
|
}
|
|
@@ -966,7 +1074,6 @@ class AccountWorker {
|
|
|
966
1074
|
|
|
967
1075
|
await this.setCooldown(item.cmd, totalWait);
|
|
968
1076
|
|
|
969
|
-
// Global jitter between commands
|
|
970
1077
|
const timeSinceLastCmd = now - (this.lastCommandRun || 0);
|
|
971
1078
|
const globalJitter = 1500 + Math.random() * 1500;
|
|
972
1079
|
if (timeSinceLastCmd < globalJitter) {
|
|
@@ -974,15 +1081,28 @@ class AccountWorker {
|
|
|
974
1081
|
}
|
|
975
1082
|
|
|
976
1083
|
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
977
|
-
this.setStatus(
|
|
1084
|
+
this.setStatus(`pls ${item.cmd}`);
|
|
1085
|
+
|
|
1086
|
+
const beforeCoins = this.stats.coins;
|
|
978
1087
|
await this.runCommand(item.cmd, prefix);
|
|
1088
|
+
const earned = this.stats.coins - beforeCoins;
|
|
1089
|
+
|
|
1090
|
+
if (earned <= 0 && item.cmd !== 'dep max' && item.cmd !== 'alert') {
|
|
1091
|
+
this.failStreak++;
|
|
1092
|
+
} else {
|
|
1093
|
+
this.failStreak = 0;
|
|
1094
|
+
}
|
|
979
1095
|
|
|
980
1096
|
this.lastCommandRun = Date.now();
|
|
981
1097
|
await this.setCooldown(item.cmd, totalWait);
|
|
982
1098
|
|
|
983
|
-
//
|
|
984
|
-
|
|
985
|
-
|
|
1099
|
+
// Exponential backoff: if too many consecutive failures, slow down
|
|
1100
|
+
const backoffMultiplier = this.failStreak > 5 ? Math.min(this.failStreak - 4, 5) : 1;
|
|
1101
|
+
|
|
1102
|
+
if (this.commandQueue && this.running && !shutdownCalled) {
|
|
1103
|
+
item.nextRunAt = Date.now() + totalWait * 1000 * backoffMultiplier;
|
|
1104
|
+
this.commandQueue.push(item);
|
|
1105
|
+
}
|
|
986
1106
|
|
|
987
1107
|
this.busy = false;
|
|
988
1108
|
this.cycleCount++;
|
|
@@ -994,7 +1114,9 @@ class AccountWorker {
|
|
|
994
1114
|
this.busy = false;
|
|
995
1115
|
}
|
|
996
1116
|
|
|
997
|
-
this.
|
|
1117
|
+
if (this.running && !shutdownCalled) {
|
|
1118
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
1119
|
+
}
|
|
998
1120
|
}
|
|
999
1121
|
|
|
1000
1122
|
async grindLoop() {
|
|
@@ -1002,6 +1124,8 @@ class AccountWorker {
|
|
|
1002
1124
|
this.running = true;
|
|
1003
1125
|
this.busy = false;
|
|
1004
1126
|
this.paused = false;
|
|
1127
|
+
this.dashboardPaused = false;
|
|
1128
|
+
this.failStreak = 0;
|
|
1005
1129
|
this.cycleCount = 0;
|
|
1006
1130
|
this.lastCommandRun = 0;
|
|
1007
1131
|
this.commandQueue = this.buildCommandQueue();
|
|
@@ -1011,23 +1135,23 @@ class AccountWorker {
|
|
|
1011
1135
|
if (!this.running) return;
|
|
1012
1136
|
await this.refreshConfig();
|
|
1013
1137
|
|
|
1014
|
-
// Dashboard can un-pause a captcha-paused worker by re-activating it
|
|
1015
1138
|
if (this.account.active && this.paused) {
|
|
1016
|
-
this.log('success', '
|
|
1139
|
+
this.log('success', 'Captcha cleared! Resuming...');
|
|
1017
1140
|
this.paused = false;
|
|
1018
|
-
this.
|
|
1141
|
+
this.dashboardPaused = false;
|
|
1019
1142
|
}
|
|
1020
1143
|
|
|
1021
|
-
if (!this.account.active && !this.
|
|
1144
|
+
if (!this.account.active && !this.dashboardPaused) {
|
|
1022
1145
|
this.log('warn', 'Account deactivated from dashboard. Pausing...');
|
|
1023
|
-
this.
|
|
1024
|
-
} else if (this.account.active && this.
|
|
1146
|
+
this.dashboardPaused = true;
|
|
1147
|
+
} else if (this.account.active && this.dashboardPaused) {
|
|
1025
1148
|
this.log('success', 'Account re-activated! Resuming...');
|
|
1026
|
-
this.
|
|
1149
|
+
this.dashboardPaused = false;
|
|
1027
1150
|
}
|
|
1028
1151
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1152
|
+
if (this.commandQueue && !shutdownCalled) {
|
|
1153
|
+
this.mergeCommandQueue();
|
|
1154
|
+
}
|
|
1031
1155
|
}, 15000);
|
|
1032
1156
|
|
|
1033
1157
|
const safeTickLoop = async () => {
|
|
@@ -1095,10 +1219,11 @@ class AccountWorker {
|
|
|
1095
1219
|
{ key: 'cmd_trivia', l: 'trivia' }, { key: 'cmd_use', l: 'use' },
|
|
1096
1220
|
{ key: 'cmd_deposit', l: 'dep' }, { key: 'cmd_drops', l: 'drops' },
|
|
1097
1221
|
{ key: 'cmd_alert', l: 'alert' },
|
|
1098
|
-
].filter((ci) => this.account[ci.key]
|
|
1222
|
+
].filter((ci) => Boolean(this.account[ci.key]));
|
|
1099
1223
|
|
|
1100
|
-
const
|
|
1101
|
-
this.log('success',
|
|
1224
|
+
const chName = (this.channel.name || '?').substring(0, 12);
|
|
1225
|
+
this.log('success', `#${chName} · ${enabledCmds.length} cmds`);
|
|
1226
|
+
this.setStatus('starting...');
|
|
1102
1227
|
|
|
1103
1228
|
await this.checkBalance();
|
|
1104
1229
|
this.grindLoop();
|
|
@@ -1112,8 +1237,10 @@ class AccountWorker {
|
|
|
1112
1237
|
stop() {
|
|
1113
1238
|
this.running = false;
|
|
1114
1239
|
this.paused = false;
|
|
1115
|
-
|
|
1116
|
-
|
|
1240
|
+
this.dashboardPaused = false;
|
|
1241
|
+
this.busy = false;
|
|
1242
|
+
if (this.tickTimeout) { clearTimeout(this.tickTimeout); this.tickTimeout = null; }
|
|
1243
|
+
if (this.configInterval) { clearInterval(this.configInterval); this.configInterval = null; }
|
|
1117
1244
|
this.commandQueue = null;
|
|
1118
1245
|
try { this.client.destroy(); } catch {}
|
|
1119
1246
|
}
|
|
@@ -1135,7 +1262,7 @@ async function start(apiKey, apiUrl) {
|
|
|
1135
1262
|
|
|
1136
1263
|
console.log(colorBanner());
|
|
1137
1264
|
console.log(
|
|
1138
|
-
` ${rgb(139, 92, 246)}v4.
|
|
1265
|
+
` ${rgb(139, 92, 246)}v4.3${c.reset}` +
|
|
1139
1266
|
` ${c.dim}·${c.reset} ${c.white}30 Commands${c.reset}` +
|
|
1140
1267
|
` ${c.dim}·${c.reset} ${rgb(34, 211, 238)}Priority Queue${c.reset}` +
|
|
1141
1268
|
` ${c.dim}·${c.reset} ${rgb(52, 211, 153)}Redis Cooldowns${c.reset}` +
|
|
@@ -1153,7 +1280,7 @@ async function start(apiKey, apiUrl) {
|
|
|
1153
1280
|
const data = await fetchConfig();
|
|
1154
1281
|
if (!data) {
|
|
1155
1282
|
log('error', `Cannot connect to API`);
|
|
1156
|
-
log('error', `
|
|
1283
|
+
log('error', `Check your API key or API URL and try again.`);
|
|
1157
1284
|
process.exit(1);
|
|
1158
1285
|
}
|
|
1159
1286
|
|
|
@@ -1178,17 +1305,32 @@ async function start(apiKey, apiUrl) {
|
|
|
1178
1305
|
console.log('');
|
|
1179
1306
|
startTime = Date.now();
|
|
1180
1307
|
dashboardStarted = true;
|
|
1308
|
+
setDashboardActive(true);
|
|
1181
1309
|
process.stdout.write(c.hide);
|
|
1182
1310
|
|
|
1183
|
-
setInterval(() =>
|
|
1184
|
-
|
|
1311
|
+
setInterval(() => scheduleRender(), 1000);
|
|
1312
|
+
scheduleRender();
|
|
1185
1313
|
|
|
1314
|
+
let sigintHandled = false;
|
|
1186
1315
|
process.on('SIGINT', () => {
|
|
1316
|
+
if (sigintHandled) return;
|
|
1317
|
+
sigintHandled = true;
|
|
1187
1318
|
shutdownCalled = true;
|
|
1188
|
-
process.stdout.write(c.show);
|
|
1189
1319
|
dashboardStarted = false;
|
|
1320
|
+
setDashboardActive(false);
|
|
1321
|
+
process.stdout.write(c.show);
|
|
1322
|
+
|
|
1323
|
+
// Clear the dashboard area before printing summary
|
|
1324
|
+
if (dashboardLines > 0) {
|
|
1325
|
+
process.stdout.write(c.cursorUp(dashboardLines));
|
|
1326
|
+
for (let i = 0; i < dashboardLines; i++) {
|
|
1327
|
+
process.stdout.write(c.clearLine + '\r\n');
|
|
1328
|
+
}
|
|
1329
|
+
process.stdout.write(c.cursorUp(dashboardLines));
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1190
1332
|
const sepBar = rgb(139, 92, 246) + c.bold + '═'.repeat(tw) + c.reset;
|
|
1191
|
-
console.log('
|
|
1333
|
+
console.log('');
|
|
1192
1334
|
console.log(` ${rgb(251, 191, 36)}${c.bold}Session Summary${c.reset}`);
|
|
1193
1335
|
console.log(sepBar);
|
|
1194
1336
|
for (const wk of workers) {
|
|
@@ -1202,9 +1344,15 @@ async function start(apiKey, apiUrl) {
|
|
|
1202
1344
|
wk.stop();
|
|
1203
1345
|
}
|
|
1204
1346
|
console.log(sepBar);
|
|
1205
|
-
|
|
1347
|
+
|
|
1348
|
+
// Recalculate totals for accurate summary
|
|
1349
|
+
let finalCoins = 0;
|
|
1350
|
+
for (const wk of workers) finalCoins += wk.stats.coins || 0;
|
|
1351
|
+
console.log(` ${rgb(251, 191, 36)}${c.bold}Total: +⏣ ${finalCoins.toLocaleString()}${c.reset} ${c.dim}in ${formatUptime()}${c.reset}`);
|
|
1206
1352
|
console.log('');
|
|
1207
|
-
|
|
1353
|
+
|
|
1354
|
+
if (redis) { try { redis.disconnect(); } catch {} }
|
|
1355
|
+
setTimeout(() => process.exit(0), 1500);
|
|
1208
1356
|
});
|
|
1209
1357
|
}
|
|
1210
1358
|
|