dankgrinder 4.3.1 โ 4.5.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/commands/drops.js +116 -0
- package/lib/commands/gamble.js +9 -6
- package/lib/commands/index.js +2 -0
- package/lib/commands/stream.js +98 -43
- package/lib/commands/utils.js +1 -1
- package/lib/grinder.js +82 -23
- package/package.json +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const {
|
|
2
|
+
LOG, c, getFullText, parseCoins, getAllButtons,
|
|
3
|
+
safeClickButton, logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
4
|
+
} = require('./utils');
|
|
5
|
+
|
|
6
|
+
async function runDrops({ channel, waitForDankMemer, redis, accountId }) {
|
|
7
|
+
LOG.cmd(`${c.white}${c.bold}pls drops${c.reset}`);
|
|
8
|
+
|
|
9
|
+
await channel.send('pls drops');
|
|
10
|
+
const response = await waitForDankMemer(10000);
|
|
11
|
+
|
|
12
|
+
if (!response) return { result: 'no response', coins: 0 };
|
|
13
|
+
|
|
14
|
+
if (isHoldTight(response)) {
|
|
15
|
+
const reason = getHoldTightReason(response);
|
|
16
|
+
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
logMsg(response, 'drops');
|
|
20
|
+
const text = getFullText(response);
|
|
21
|
+
const lower = text.toLowerCase();
|
|
22
|
+
|
|
23
|
+
const drops = [];
|
|
24
|
+
|
|
25
|
+
for (const embed of response.embeds || []) {
|
|
26
|
+
const desc = embed.description || '';
|
|
27
|
+
const items = desc.split(/\n\n+/);
|
|
28
|
+
for (const block of items) {
|
|
29
|
+
const nameMatch = block.match(/\*\*(.+?)\*\*/);
|
|
30
|
+
const costMatch = block.match(/Cost:\s*[โฃ๐ฐ]?\s*([\d,]+)/i);
|
|
31
|
+
const stockMatch = block.match(/Stock:\s*(\d+|Infinite)/i);
|
|
32
|
+
const dateMatch = block.match(/<t:(\d+):[tTdDfFR]>/);
|
|
33
|
+
if (nameMatch) {
|
|
34
|
+
drops.push({
|
|
35
|
+
name: nameMatch[1],
|
|
36
|
+
cost: costMatch ? parseInt(costMatch[1].replace(/,/g, '')) : 0,
|
|
37
|
+
stock: stockMatch ? (stockMatch[1] === 'Infinite' ? Infinity : parseInt(stockMatch[1])) : 0,
|
|
38
|
+
dropTimestamp: dateMatch ? parseInt(dateMatch[1]) : 0,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Also parse from text lines
|
|
45
|
+
const textBlocks = text.split(/\n/).filter(l => l.trim());
|
|
46
|
+
for (let i = 0; i < textBlocks.length; i++) {
|
|
47
|
+
const line = textBlocks[i];
|
|
48
|
+
if (line.includes('Cost:') && i > 0) {
|
|
49
|
+
const prevLine = textBlocks[i - 1];
|
|
50
|
+
const nameMatch = prevLine.match(/\*\*(.+?)\*\*/) || prevLine.match(/(\w[\w\s']+)/);
|
|
51
|
+
const costMatch = line.match(/Cost:\s*[โฃ๐ฐo]?\s*([\d,]+)/i);
|
|
52
|
+
if (nameMatch && costMatch) {
|
|
53
|
+
const cost = parseInt(costMatch[1].replace(/,/g, ''));
|
|
54
|
+
const existing = drops.find(d => d.name === nameMatch[1]);
|
|
55
|
+
if (!existing && cost > 0) {
|
|
56
|
+
drops.push({ name: nameMatch[1], cost, stock: 0, dropTimestamp: 0 });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Store drop schedule in Redis: track dates so we can auto-buy
|
|
63
|
+
if (redis && drops.length > 0) {
|
|
64
|
+
try {
|
|
65
|
+
await redis.set('dkg:drops:latest', JSON.stringify(drops), 'EX', 172800);
|
|
66
|
+
// Schedule individual drops with their timestamps
|
|
67
|
+
for (const drop of drops) {
|
|
68
|
+
if (drop.dropTimestamp > 0) {
|
|
69
|
+
const key = `dkg:drops:schedule:${drop.name.replace(/\s+/g, '_').toLowerCase()}`;
|
|
70
|
+
await redis.set(key, JSON.stringify(drop), 'EX', 172800);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Try to buy any drops that are currently available (stock > 0, no future date)
|
|
77
|
+
const now = Math.floor(Date.now() / 1000);
|
|
78
|
+
const buttons = getAllButtons(response);
|
|
79
|
+
let boughtCount = 0;
|
|
80
|
+
for (const drop of drops) {
|
|
81
|
+
if (drop.stock > 0 && (drop.dropTimestamp === 0 || drop.dropTimestamp <= now)) {
|
|
82
|
+
const buyBtn = buttons.find(b =>
|
|
83
|
+
!b.disabled && b.label && (
|
|
84
|
+
b.label.toLowerCase().includes('buy') ||
|
|
85
|
+
b.label.toLowerCase().includes(drop.name.toLowerCase().split(' ')[0])
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
if (buyBtn) {
|
|
89
|
+
try {
|
|
90
|
+
await humanDelay(100, 300);
|
|
91
|
+
await safeClickButton(response, buyBtn);
|
|
92
|
+
boughtCount++;
|
|
93
|
+
LOG.coin(`[drops] ${c.green}Bought ${drop.name}!${c.reset}`);
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (drops.length > 0) {
|
|
100
|
+
const summary = drops.slice(0, 3).map(d => {
|
|
101
|
+
const ts = d.dropTimestamp > 0 ? ` @${new Date(d.dropTimestamp * 1000).toLocaleDateString()}` : '';
|
|
102
|
+
return `${d.name}(โฃ${d.cost > 0 ? d.cost.toLocaleString() : '?'}${ts})`;
|
|
103
|
+
}).join(', ');
|
|
104
|
+
const boughtMsg = boughtCount > 0 ? ` โ bought ${boughtCount}` : '';
|
|
105
|
+
LOG.info(`[drops] ${drops.length} drops: ${summary}${boughtMsg}`);
|
|
106
|
+
return { result: `${drops.length} drops: ${summary}${boughtMsg}`, coins: 0 };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (lower.includes('no drops') || lower.includes('nothing')) {
|
|
110
|
+
return { result: 'no drops available', coins: 0 };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { result: text.substring(0, 50) || 'drops checked', coins: 0 };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = { runDrops };
|
package/lib/commands/gamble.js
CHANGED
|
@@ -70,26 +70,29 @@ async function runGamble({ channel, waitForDankMemer, cmdName, cmdString }) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const lower = text.toLowerCase();
|
|
73
|
-
if (lower.includes('won')) return { result: `${cmdName} โ
|
|
74
|
-
if (lower.includes('lost'))
|
|
73
|
+
if (lower.includes('won')) return { result: `${cmdName} โ won`, coins };
|
|
74
|
+
if (lower.includes('lost')) {
|
|
75
|
+
const lostCoins = parseCoins(text);
|
|
76
|
+
return { result: `${cmdName} โ lost`, coins: 0, lost: lostCoins };
|
|
77
|
+
}
|
|
75
78
|
|
|
76
79
|
return { result: `${cmdName} done`, coins: 0 };
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
// Convenience wrappers
|
|
80
|
-
async function runCoinflip({ channel, waitForDankMemer, betAmount =
|
|
83
|
+
async function runCoinflip({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
81
84
|
return runGamble({ channel, waitForDankMemer, cmdName: 'coinflip', cmdString: `pls coinflip ${betAmount} heads` });
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
async function runRoulette({ channel, waitForDankMemer, betAmount =
|
|
87
|
+
async function runRoulette({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
85
88
|
return runGamble({ channel, waitForDankMemer, cmdName: 'roulette', cmdString: `pls roulette ${betAmount} red` });
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
async function runSlots({ channel, waitForDankMemer, betAmount =
|
|
91
|
+
async function runSlots({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
89
92
|
return runGamble({ channel, waitForDankMemer, cmdName: 'slots', cmdString: `pls slots ${betAmount}` });
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
async function runSnakeeyes({ channel, waitForDankMemer, betAmount =
|
|
95
|
+
async function runSnakeeyes({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
93
96
|
return runGamble({ channel, waitForDankMemer, cmdName: 'snakeeyes', cmdString: `pls snakeeyes ${betAmount}` });
|
|
94
97
|
}
|
|
95
98
|
|
package/lib/commands/index.js
CHANGED
|
@@ -20,6 +20,7 @@ const { runCoinflip, runRoulette, runSlots, runSnakeeyes, runGamble } = require(
|
|
|
20
20
|
const { runDeposit } = require('./deposit');
|
|
21
21
|
const { runGeneric, runAlert } = require('./generic');
|
|
22
22
|
const { runStream } = require('./stream');
|
|
23
|
+
const { runDrops } = require('./drops');
|
|
23
24
|
const { buyItem, ITEM_COSTS } = require('./shop');
|
|
24
25
|
const { getPlayerLevel, meetsLevelRequirement } = require('./profile');
|
|
25
26
|
|
|
@@ -47,6 +48,7 @@ module.exports = {
|
|
|
47
48
|
runGeneric,
|
|
48
49
|
runAlert,
|
|
49
50
|
runStream,
|
|
51
|
+
runDrops,
|
|
50
52
|
buyItem,
|
|
51
53
|
|
|
52
54
|
// Profile / Level
|
package/lib/commands/stream.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const {
|
|
2
|
-
LOG, c, getFullText, parseCoins, getAllButtons,
|
|
2
|
+
LOG, c, getFullText, parseCoins, getAllButtons, getAllSelectMenus,
|
|
3
3
|
safeClickButton, logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, needsItem,
|
|
4
4
|
} = require('./utils');
|
|
5
5
|
const { buyItem } = require('./shop');
|
|
@@ -10,7 +10,7 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
10
10
|
LOG.cmd(`${c.white}${c.bold}pls stream${c.reset}`);
|
|
11
11
|
|
|
12
12
|
await channel.send('pls stream');
|
|
13
|
-
let response = await waitForDankMemer(
|
|
13
|
+
let response = await waitForDankMemer(12000);
|
|
14
14
|
|
|
15
15
|
if (!response) {
|
|
16
16
|
LOG.warn('[stream] No response');
|
|
@@ -19,78 +19,133 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
19
19
|
|
|
20
20
|
if (isHoldTight(response)) {
|
|
21
21
|
const reason = getHoldTightReason(response);
|
|
22
|
-
LOG.warn(`[stream] Hold Tight${reason ? ` (reason: /${reason})` : ''}`);
|
|
23
|
-
await sleep(30000);
|
|
24
22
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
logMsg(response, 'stream');
|
|
28
26
|
let text = getFullText(response);
|
|
27
|
+
let lower = text.toLowerCase();
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
if (lower.includes('missing items') || lower.includes('need following items') ||
|
|
29
|
+
// Missing items โ buy keyboard + mouse
|
|
30
|
+
if (lower.includes('missing items') || lower.includes('need following items') ||
|
|
31
|
+
lower.includes('need a keyboard') || lower.includes('need a mouse')) {
|
|
32
32
|
const itemsToBuy = [];
|
|
33
33
|
if (lower.includes('keyboard')) itemsToBuy.push('keyboard');
|
|
34
34
|
if (lower.includes('mouse')) itemsToBuy.push('mouse');
|
|
35
|
-
|
|
36
35
|
if (itemsToBuy.length === 0) itemsToBuy.push(...STREAM_ITEMS);
|
|
37
36
|
|
|
38
|
-
LOG.warn(`[stream] Missing items: ${c.bold}${itemsToBuy.join(', ')}${c.reset} โ auto-buying...`);
|
|
39
|
-
|
|
40
37
|
for (const item of itemsToBuy) {
|
|
41
38
|
const bought = await buyItem({ channel, waitForDankMemer, client, itemName: item, quantity: 1 });
|
|
42
|
-
if (bought) {
|
|
43
|
-
|
|
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);
|
|
39
|
+
if (!bought) return { result: `need ${item} (buy failed)`, coins: 0 };
|
|
40
|
+
await humanDelay(500, 1000);
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
await sleep(3000);
|
|
43
|
+
await sleep(2000);
|
|
53
44
|
await channel.send('pls stream');
|
|
54
|
-
response = await waitForDankMemer(
|
|
45
|
+
response = await waitForDankMemer(12000);
|
|
55
46
|
if (!response) return { result: 'no response after buy', coins: 0 };
|
|
56
47
|
logMsg(response, 'stream-retry');
|
|
57
48
|
text = getFullText(response);
|
|
49
|
+
lower = text.toLowerCase();
|
|
58
50
|
}
|
|
59
51
|
|
|
60
|
-
|
|
52
|
+
// Stream Manager โ select a game from dropdown, then click "Go Live"
|
|
53
|
+
if (lower.includes('stream manager') || lower.includes('what game do you want to stream')) {
|
|
54
|
+
const menus = getAllSelectMenus(response);
|
|
55
|
+
if (menus.length > 0) {
|
|
56
|
+
const menu = menus[0];
|
|
57
|
+
const options = menu.options || [];
|
|
58
|
+
if (options.length > 0) {
|
|
59
|
+
// Pick a random game (variety is better for Dank Memer)
|
|
60
|
+
const pick = options[Math.floor(Math.random() * options.length)];
|
|
61
|
+
LOG.info(`[stream] Selecting game: "${pick.label}"`);
|
|
62
|
+
|
|
63
|
+
let menuRowIdx = -1;
|
|
64
|
+
for (let i = 0; i < (response.components || []).length; i++) {
|
|
65
|
+
for (const comp of (response.components[i].components || [])) {
|
|
66
|
+
if (comp.type === 'STRING_SELECT' || comp.type === 3) { menuRowIdx = i; break; }
|
|
67
|
+
}
|
|
68
|
+
if (menuRowIdx >= 0) break;
|
|
69
|
+
}
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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 };
|
|
71
|
+
if (menuRowIdx >= 0) {
|
|
72
|
+
try {
|
|
73
|
+
const selectResult = await response.selectMenu(menuRowIdx, [pick.value]);
|
|
74
|
+
if (selectResult) {
|
|
75
|
+
response = selectResult;
|
|
76
|
+
logMsg(response, 'stream-game-selected');
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
LOG.error(`[stream] Select game failed: ${e.message}`);
|
|
80
80
|
}
|
|
81
|
+
await humanDelay(200, 500);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Click "Go Live" button
|
|
87
|
+
const buttons = getAllButtons(response);
|
|
88
|
+
const goLiveBtn = buttons.find(b => !b.disabled && b.label && b.label.toLowerCase().includes('go live'));
|
|
89
|
+
if (goLiveBtn) {
|
|
90
|
+
LOG.info('[stream] Clicking "Go Live"');
|
|
91
|
+
await humanDelay(100, 300);
|
|
92
|
+
try {
|
|
93
|
+
const liveResult = await safeClickButton(response, goLiveBtn);
|
|
94
|
+
if (liveResult) {
|
|
95
|
+
response = liveResult;
|
|
96
|
+
logMsg(response, 'stream-live');
|
|
97
|
+
text = getFullText(response);
|
|
98
|
+
lower = text.toLowerCase();
|
|
81
99
|
}
|
|
82
100
|
} catch (e) {
|
|
83
|
-
LOG.error(`[stream]
|
|
101
|
+
LOG.error(`[stream] Go Live click failed: ${e.message}`);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
const anyBtn = buttons.find(b => !b.disabled && b.label && !b.label.toLowerCase().includes('back'));
|
|
105
|
+
if (anyBtn) {
|
|
106
|
+
try { await safeClickButton(response, anyBtn); } catch {}
|
|
84
107
|
}
|
|
85
108
|
}
|
|
86
109
|
}
|
|
87
110
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
// After going live, there might be interactive stream events
|
|
112
|
+
// Keep clicking non-end buttons for up to 5 rounds
|
|
113
|
+
for (let round = 0; round < 5; round++) {
|
|
114
|
+
const followUp = await waitForDankMemer(15000);
|
|
115
|
+
if (!followUp) break;
|
|
116
|
+
|
|
117
|
+
logMsg(followUp, `stream-round-${round}`);
|
|
118
|
+
const fText = getFullText(followUp);
|
|
119
|
+
const fLower = fText.toLowerCase();
|
|
120
|
+
const fCoins = parseCoins(fText);
|
|
121
|
+
|
|
122
|
+
if (fLower.includes('stream ended') || fLower.includes('stream is over') || fLower.includes('you earned')) {
|
|
123
|
+
if (fCoins > 0) {
|
|
124
|
+
LOG.coin(`[stream] ${c.green}+โฃ ${fCoins.toLocaleString()}${c.reset}`);
|
|
125
|
+
return { result: `stream โ +โฃ ${fCoins.toLocaleString()}`, coins: fCoins, nextCooldownSec: 600 };
|
|
126
|
+
}
|
|
127
|
+
return { result: 'stream ended', coins: 0, nextCooldownSec: 600 };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const btns = getAllButtons(followUp);
|
|
131
|
+
const actionBtn = btns.find(b => !b.disabled && b.label &&
|
|
132
|
+
!b.label.toLowerCase().includes('end') && !b.label.toLowerCase().includes('stop') && !b.label.toLowerCase().includes('back'));
|
|
133
|
+
if (actionBtn) {
|
|
134
|
+
LOG.info(`[stream] Clicking "${actionBtn.label}"`);
|
|
135
|
+
await humanDelay(100, 300);
|
|
136
|
+
try { await safeClickButton(followUp, actionBtn); } catch {}
|
|
137
|
+
} else if (btns.length > 0) {
|
|
138
|
+
const first = btns.find(b => !b.disabled);
|
|
139
|
+
if (first) { try { await safeClickButton(followUp, first); } catch {} }
|
|
140
|
+
} else {
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
91
143
|
}
|
|
92
144
|
|
|
93
|
-
|
|
145
|
+
const coins = parseCoins(text);
|
|
146
|
+
if (coins > 0) return { result: `stream โ +โฃ ${coins.toLocaleString()}`, coins, nextCooldownSec: 600 };
|
|
147
|
+
|
|
148
|
+
return { result: text.substring(0, 50) || 'streamed', coins: 0, nextCooldownSec: 600 };
|
|
94
149
|
}
|
|
95
150
|
|
|
96
151
|
module.exports = { runStream };
|
package/lib/commands/utils.js
CHANGED
|
@@ -38,7 +38,7 @@ const LOG = {
|
|
|
38
38
|
// โโ Sleep โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
39
39
|
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
40
40
|
|
|
41
|
-
function humanDelay(min =
|
|
41
|
+
function humanDelay(min = 50, max = 200) {
|
|
42
42
|
return new Promise(r => setTimeout(r, min + Math.random() * (max - min)));
|
|
43
43
|
}
|
|
44
44
|
|
package/lib/grinder.js
CHANGED
|
@@ -1,7 +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
|
+
const { setDashboardActive, getFullText } = require('./commands/utils');
|
|
5
5
|
|
|
6
6
|
// โโ Terminal Colors & ANSI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
7
7
|
const c = {
|
|
@@ -284,7 +284,7 @@ async function reportEarnings(accountId, accountName, earned, spent, command) {
|
|
|
284
284
|
function randomDelay(min, max) {
|
|
285
285
|
return new Promise((r) => setTimeout(r, (Math.random() * (max - min) + min) * 1000));
|
|
286
286
|
}
|
|
287
|
-
function humanDelay(min =
|
|
287
|
+
function humanDelay(min = 80, max = 250) {
|
|
288
288
|
return new Promise((r) => setTimeout(r, min + Math.random() * (max - min)));
|
|
289
289
|
}
|
|
290
290
|
function safeParseJSON(str, fallback = []) {
|
|
@@ -759,10 +759,11 @@ class AccountWorker {
|
|
|
759
759
|
// handles Hold Tight / cooldowns / item-buying internally.
|
|
760
760
|
async runCommand(cmdName, prefix) {
|
|
761
761
|
let cmdString;
|
|
762
|
-
const betAmount = this.account.bet_amount ||
|
|
762
|
+
const betAmount = Math.max(5000, this.account.bet_amount || 5000);
|
|
763
763
|
|
|
764
764
|
switch (cmdName) {
|
|
765
765
|
case 'dep max': cmdString = `${prefix} dep max`; break;
|
|
766
|
+
case 'with max': cmdString = `${prefix} with max`; break;
|
|
766
767
|
case 'blackjack': cmdString = `${prefix} bj ${betAmount}`; break;
|
|
767
768
|
case 'coinflip': cmdString = `${prefix} coinflip ${betAmount} heads`; break;
|
|
768
769
|
case 'roulette': cmdString = `${prefix} roulette ${betAmount} red`; break;
|
|
@@ -809,6 +810,7 @@ class AccountWorker {
|
|
|
809
810
|
case 'dep max': cmdResult = await commands.runDeposit(cmdOpts); break;
|
|
810
811
|
case 'alert': cmdResult = await commands.runAlert(cmdOpts); break;
|
|
811
812
|
case 'stream': cmdResult = await commands.runStream(cmdOpts); break;
|
|
813
|
+
case 'drops': cmdResult = await commands.runDrops(cmdOpts); break;
|
|
812
814
|
default: cmdResult = await commands.runGeneric({ ...cmdOpts, cmdString, cmdName }); break;
|
|
813
815
|
}
|
|
814
816
|
|
|
@@ -823,12 +825,37 @@ class AccountWorker {
|
|
|
823
825
|
return;
|
|
824
826
|
}
|
|
825
827
|
|
|
826
|
-
// Captcha/verification detection โ
|
|
827
|
-
if (resultLower.includes('captcha') || resultLower.includes('verification') ||
|
|
828
|
-
resultLower.includes('
|
|
829
|
-
|
|
828
|
+
// Captcha/verification detection โ deactivate account and stop
|
|
829
|
+
if (resultLower.includes('captcha') || resultLower.includes('verification required') ||
|
|
830
|
+
resultLower.includes('verify your account') || resultLower.includes('pass verification') ||
|
|
831
|
+
resultLower.includes('are you human') || resultLower.includes("prove you're not a bot") ||
|
|
832
|
+
resultLower.includes('complete the captcha') || resultLower.includes('continue playing')) {
|
|
833
|
+
this.log('error', `VERIFICATION REQUIRED! Deactivating account.`);
|
|
834
|
+
this.log('error', `Solve it in Discord, then re-enable from dashboard.`);
|
|
830
835
|
this.paused = true;
|
|
831
|
-
|
|
836
|
+
this.account.active = false;
|
|
837
|
+
// Deactivate in DB so dashboard shows it as paused
|
|
838
|
+
try {
|
|
839
|
+
await fetch(`${API_URL}/api/grinder/status`, {
|
|
840
|
+
method: 'POST',
|
|
841
|
+
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
842
|
+
body: JSON.stringify({ account_id: this.account.id, active: false }),
|
|
843
|
+
});
|
|
844
|
+
} catch {}
|
|
845
|
+
await sendLog(this.username, cmdString, 'VERIFICATION โ account deactivated', 'error');
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Min bet detection โ raise bet amount
|
|
850
|
+
if (resultLower.includes('cannot bet less than') || resultLower.includes('minimum bet')) {
|
|
851
|
+
const betMatch = result.match(/โฃ\s*([\d,]+)/);
|
|
852
|
+
if (betMatch) {
|
|
853
|
+
const minBet = parseInt(betMatch[1].replace(/,/g, ''));
|
|
854
|
+
if (minBet > 0) {
|
|
855
|
+
this.account.bet_amount = minBet;
|
|
856
|
+
this.log('info', `${cmdName} min bet: โฃ ${minBet.toLocaleString()}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
832
859
|
return;
|
|
833
860
|
}
|
|
834
861
|
|
|
@@ -854,6 +881,13 @@ class AccountWorker {
|
|
|
854
881
|
return;
|
|
855
882
|
}
|
|
856
883
|
|
|
884
|
+
// Scratch level-gate: if level too low, disable for 24h
|
|
885
|
+
if (cmdResult.skipReason === 'level') {
|
|
886
|
+
this.log('warn', `${cmdName} level too low โ retry in 24h`);
|
|
887
|
+
await this.setCooldown(cmdName, 86400);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
857
891
|
const earned = Math.max(0, cmdResult.coins || 0);
|
|
858
892
|
const spent = Math.max(0, cmdResult.lost || 0);
|
|
859
893
|
if (earned > 0) this.stats.coins += earned;
|
|
@@ -900,6 +934,7 @@ class AccountWorker {
|
|
|
900
934
|
|
|
901
935
|
// โโ Command Map (shared across ticks, used to build the heap) โโ
|
|
902
936
|
static COMMAND_MAP = [
|
|
937
|
+
// Core grinding commands (short cooldowns, run frequently)
|
|
903
938
|
{ key: 'cmd_beg', cmd: 'beg', cdKey: 'cd_beg', defaultCd: 40, priority: 5 },
|
|
904
939
|
{ key: 'cmd_search', cmd: 'search', cdKey: 'cd_search', defaultCd: 25, priority: 4 },
|
|
905
940
|
{ key: 'cmd_hl', cmd: 'hl', cdKey: 'cd_hl', defaultCd: 10, priority: 3 },
|
|
@@ -910,23 +945,25 @@ class AccountWorker {
|
|
|
910
945
|
{ key: 'cmd_fish', cmd: 'fish', cdKey: 'cd_fish', defaultCd: 20, priority: 1 },
|
|
911
946
|
{ key: 'cmd_farm', cmd: 'farm', cdKey: 'cd_farm', defaultCd: 10, priority: 2 },
|
|
912
947
|
{ key: 'cmd_tidy', cmd: 'tidy', cdKey: 'cd_tidy', defaultCd: 40, priority: 1 },
|
|
948
|
+
{ key: 'cmd_trivia', cmd: 'trivia', cdKey: 'cd_trivia', defaultCd: 10, priority: 2 },
|
|
949
|
+
// Gambling (fast cycle)
|
|
913
950
|
{ key: 'cmd_blackjack', cmd: 'blackjack', cdKey: 'cd_blackjack', defaultCd: 3, priority: 3 },
|
|
914
951
|
{ key: 'cmd_cointoss', cmd: 'coinflip', cdKey: 'cd_cointoss', defaultCd: 2, priority: 3 },
|
|
915
952
|
{ key: 'cmd_roulette', cmd: 'roulette', cdKey: 'cd_roulette', defaultCd: 3, priority: 3 },
|
|
916
953
|
{ key: 'cmd_slots', cmd: 'slots', cdKey: 'cd_slots', defaultCd: 3, priority: 3 },
|
|
917
954
|
{ key: 'cmd_snakeeyes', cmd: 'snakeeyes', cdKey: 'cd_snakeeyes', defaultCd: 3, priority: 3 },
|
|
918
|
-
|
|
955
|
+
// Utility
|
|
919
956
|
{ key: 'cmd_use', cmd: 'use', cdKey: 'cd_use', defaultCd: 10, priority: 1 },
|
|
920
|
-
|
|
921
|
-
{ key: 'cmd_stream', cmd: 'stream', cdKey: 'cd_stream', defaultCd: 600, priority:
|
|
922
|
-
{ key: '
|
|
957
|
+
// Interactive commands
|
|
958
|
+
{ key: 'cmd_stream', cmd: 'stream', cdKey: 'cd_stream', defaultCd: 600, priority: 6 },
|
|
959
|
+
{ key: 'cmd_scratch', cmd: 'scratch', cdKey: 'cd_scratch', defaultCd: 21600, priority: 4 },
|
|
960
|
+
{ key: 'cmd_adventure', cmd: 'adventure', cdKey: 'cd_adventure', defaultCd: 300, priority: 4 },
|
|
961
|
+
{ key: 'cmd_work', cmd: 'work shift', cdKey: 'cd_work', defaultCd: 3600, priority: 6 },
|
|
962
|
+
// Time-gated
|
|
923
963
|
{ key: 'cmd_daily', cmd: 'daily', cdKey: 'cd_daily', defaultCd: 86400, priority: 10 },
|
|
924
|
-
{ key: '
|
|
925
|
-
{ key: '
|
|
926
|
-
|
|
927
|
-
{ key: 'cmd_scratch', cmd: 'scratch', cdKey: 'cd_scratch', defaultCd: 3600, priority: 6 },
|
|
928
|
-
{ key: 'cmd_adventure', cmd: 'adventure', cdKey: 'cd_adventure', defaultCd: 300, priority: 6 },
|
|
929
|
-
{ key: 'cmd_alert', cmd: 'alert', cdKey: 'cd_alert', defaultCd: 300, priority: 9 },
|
|
964
|
+
{ key: 'cmd_deposit', cmd: 'dep max', cdKey: 'cd_deposit', defaultCd: 300, priority: 7 },
|
|
965
|
+
{ key: 'cmd_drops', cmd: 'drops', cdKey: 'cd_drops', defaultCd: 86400, priority: 2 },
|
|
966
|
+
// Alert is NOT scheduled โ it's reactive (listener-based, see grindLoop)
|
|
930
967
|
];
|
|
931
968
|
|
|
932
969
|
buildCommandQueue() {
|
|
@@ -1075,7 +1112,7 @@ class AccountWorker {
|
|
|
1075
1112
|
await this.setCooldown(item.cmd, totalWait);
|
|
1076
1113
|
|
|
1077
1114
|
const timeSinceLastCmd = now - (this.lastCommandRun || 0);
|
|
1078
|
-
const globalJitter =
|
|
1115
|
+
const globalJitter = 500 + Math.random() * 1000;
|
|
1079
1116
|
if (timeSinceLastCmd < globalJitter) {
|
|
1080
1117
|
await new Promise(r => setTimeout(r, globalJitter - timeSinceLastCmd));
|
|
1081
1118
|
}
|
|
@@ -1131,14 +1168,33 @@ class AccountWorker {
|
|
|
1131
1168
|
this.commandQueue = this.buildCommandQueue();
|
|
1132
1169
|
this.lastHealthCheck = Date.now();
|
|
1133
1170
|
|
|
1171
|
+
// Reactive alert listener: run `pls alert` only when Dank Memer
|
|
1172
|
+
// sends an unsolicited message containing alert-like keywords
|
|
1173
|
+
this._alertHandler = (msg) => {
|
|
1174
|
+
if (!this.running || this.paused || this.dashboardPaused || shutdownCalled) return;
|
|
1175
|
+
if (msg.author?.id !== DANK_MEMER_ID || msg.channel?.id !== this.channel?.id) return;
|
|
1176
|
+
const text = getFullText(msg).toLowerCase();
|
|
1177
|
+
if (text.includes('alert') || text.includes('notification') ||
|
|
1178
|
+
text.includes('you have a pending') || text.includes('check your alerts')) {
|
|
1179
|
+
if (!this.busy) {
|
|
1180
|
+
this.log('info', 'Alert detected โ running pls alert');
|
|
1181
|
+
this.busy = true;
|
|
1182
|
+
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
1183
|
+
this.runCommand('alert', prefix).finally(() => { this.busy = false; });
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
this.client.on('messageCreate', this._alertHandler);
|
|
1188
|
+
|
|
1134
1189
|
this.configInterval = setInterval(async () => {
|
|
1135
1190
|
if (!this.running) return;
|
|
1136
1191
|
await this.refreshConfig();
|
|
1137
1192
|
|
|
1138
1193
|
if (this.account.active && this.paused) {
|
|
1139
|
-
this.log('success', '
|
|
1194
|
+
this.log('success', 'Account re-enabled! Resuming...');
|
|
1140
1195
|
this.paused = false;
|
|
1141
1196
|
this.dashboardPaused = false;
|
|
1197
|
+
if (this.commandQueue) this.mergeCommandQueue();
|
|
1142
1198
|
}
|
|
1143
1199
|
|
|
1144
1200
|
if (!this.account.active && !this.dashboardPaused) {
|
|
@@ -1217,8 +1273,7 @@ class AccountWorker {
|
|
|
1217
1273
|
{ key: 'cmd_cointoss', l: 'flip' }, { key: 'cmd_roulette', l: 'roul' },
|
|
1218
1274
|
{ key: 'cmd_slots', l: 'slots' }, { key: 'cmd_snakeeyes', l: 'snake' },
|
|
1219
1275
|
{ key: 'cmd_trivia', l: 'trivia' }, { key: 'cmd_use', l: 'use' },
|
|
1220
|
-
{ key: 'cmd_deposit', l: 'dep' },
|
|
1221
|
-
{ key: 'cmd_alert', l: 'alert' },
|
|
1276
|
+
{ key: 'cmd_deposit', l: 'dep' }, { key: 'cmd_drops', l: 'drops' },
|
|
1222
1277
|
].filter((ci) => Boolean(this.account[ci.key]));
|
|
1223
1278
|
|
|
1224
1279
|
const chName = (this.channel.name || '?').substring(0, 12);
|
|
@@ -1241,6 +1296,10 @@ class AccountWorker {
|
|
|
1241
1296
|
this.busy = false;
|
|
1242
1297
|
if (this.tickTimeout) { clearTimeout(this.tickTimeout); this.tickTimeout = null; }
|
|
1243
1298
|
if (this.configInterval) { clearInterval(this.configInterval); this.configInterval = null; }
|
|
1299
|
+
if (this._alertHandler) {
|
|
1300
|
+
try { this.client.removeListener('messageCreate', this._alertHandler); } catch {}
|
|
1301
|
+
this._alertHandler = null;
|
|
1302
|
+
}
|
|
1244
1303
|
this.commandQueue = null;
|
|
1245
1304
|
try { this.client.destroy(); } catch {}
|
|
1246
1305
|
}
|
|
@@ -1262,7 +1321,7 @@ async function start(apiKey, apiUrl) {
|
|
|
1262
1321
|
|
|
1263
1322
|
console.log(colorBanner());
|
|
1264
1323
|
console.log(
|
|
1265
|
-
` ${rgb(139, 92, 246)}v4.
|
|
1324
|
+
` ${rgb(139, 92, 246)}v4.5${c.reset}` +
|
|
1266
1325
|
` ${c.dim}ยท${c.reset} ${c.white}30 Commands${c.reset}` +
|
|
1267
1326
|
` ${c.dim}ยท${c.reset} ${rgb(34, 211, 238)}Priority Queue${c.reset}` +
|
|
1268
1327
|
` ${c.dim}ยท${c.reset} ${rgb(52, 211, 153)}Redis Cooldowns${c.reset}` +
|