dankgrinder 6.16.0 → 6.19.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/adventure.js +5 -5
- package/lib/commands/postmemes.js +4 -4
- package/lib/commands/stream.js +7 -7
- package/lib/commands/utils.js +52 -35
- package/lib/grinder.js +161 -190
- package/lib/rawLogger.js +9 -4
- package/package.json +1 -1
|
@@ -64,7 +64,7 @@ async function clickAndRefetch(channel, msg, btn) {
|
|
|
64
64
|
LOG.error(`[adventure] Click error: ${e.message}`);
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
|
-
await sleep(
|
|
67
|
+
await sleep(100);
|
|
68
68
|
return await refetchMsg(channel, msg.id);
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -174,7 +174,7 @@ async function playAdventureRounds(channel, msg) {
|
|
|
174
174
|
const choice = pickSafeChoice(choices);
|
|
175
175
|
if (choice) {
|
|
176
176
|
LOG.info(`[adventure] → Choosing: "${choice.label}"`);
|
|
177
|
-
await sleep(
|
|
177
|
+
await sleep(50);
|
|
178
178
|
const afterChoice = await clickAndRefetch(channel, current, choice);
|
|
179
179
|
if (afterChoice) {
|
|
180
180
|
current = afterChoice;
|
|
@@ -208,7 +208,7 @@ async function playAdventureRounds(channel, msg) {
|
|
|
208
208
|
} else if (nextBtnNow && nextBtnNow.disabled) {
|
|
209
209
|
// Next is disabled but no choices found — might be loading
|
|
210
210
|
LOG.debug(`[adventure] Next disabled, no choices — waiting...`);
|
|
211
|
-
await sleep(
|
|
211
|
+
await sleep(150);
|
|
212
212
|
const refreshed = await refetchMsg(channel, current.id);
|
|
213
213
|
if (refreshed) {
|
|
214
214
|
current = refreshed;
|
|
@@ -221,7 +221,7 @@ async function playAdventureRounds(channel, msg) {
|
|
|
221
221
|
// No next button at all
|
|
222
222
|
LOG.debug(`[adventure] No Next button — checking if done`);
|
|
223
223
|
if (isAdventureDone(current)) break;
|
|
224
|
-
await sleep(
|
|
224
|
+
await sleep(200);
|
|
225
225
|
const refreshed = await refetchMsg(channel, current.id);
|
|
226
226
|
if (refreshed) {
|
|
227
227
|
current = refreshed;
|
|
@@ -374,7 +374,7 @@ async function runAdventure({ channel, waitForDankMemer, client }) {
|
|
|
374
374
|
} catch (e) {
|
|
375
375
|
LOG.error(`[adventure] Select error: ${e.message}`);
|
|
376
376
|
}
|
|
377
|
-
await sleep(
|
|
377
|
+
await sleep(150);
|
|
378
378
|
}
|
|
379
379
|
}
|
|
380
380
|
|
|
@@ -63,7 +63,7 @@ async function runPostMemes({ channel, waitForDankMemer }) {
|
|
|
63
63
|
if (initLower.includes('cannot post another meme') || initLower.includes('dead meme') ||
|
|
64
64
|
initLower.includes('another meme for another')) {
|
|
65
65
|
const minMatch = initText.match(RE_COOLDOWN_MIN);
|
|
66
|
-
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 :
|
|
66
|
+
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 : 150;
|
|
67
67
|
LOG.warn(`[pm] Cooldown: ${cdSec}s`);
|
|
68
68
|
return { result: `pm cooldown ${cdSec}s`, coins: 0, nextCooldownSec: cdSec };
|
|
69
69
|
}
|
|
@@ -110,7 +110,7 @@ async function runPostMemes({ channel, waitForDankMemer }) {
|
|
|
110
110
|
try {
|
|
111
111
|
await response.selectMenu(locRowIdx, [opt.value]);
|
|
112
112
|
} catch (e) { LOG.error(`[pm] Platform select error: ${e.message}`); }
|
|
113
|
-
await sleep(
|
|
113
|
+
await sleep(100);
|
|
114
114
|
const updated = await refetchMsg(channel, msgId);
|
|
115
115
|
if (updated) { response = updated; logMsg(response, 'pm-after-platform'); }
|
|
116
116
|
}
|
|
@@ -123,7 +123,7 @@ async function runPostMemes({ channel, waitForDankMemer }) {
|
|
|
123
123
|
try {
|
|
124
124
|
await response.selectMenu(kindRowIdx, [opt.value]);
|
|
125
125
|
} catch (e) { LOG.error(`[pm] Kind select error: ${e.message}`); }
|
|
126
|
-
await sleep(
|
|
126
|
+
await sleep(100);
|
|
127
127
|
const updated = await refetchMsg(channel, msgId);
|
|
128
128
|
if (updated) { response = updated; logMsg(response, 'pm-after-kind'); }
|
|
129
129
|
}
|
|
@@ -136,7 +136,7 @@ async function runPostMemes({ channel, waitForDankMemer }) {
|
|
|
136
136
|
LOG.info(`[pm] Clicking "${postBtn.label}"...`);
|
|
137
137
|
try {
|
|
138
138
|
await safeClickButton(response, postBtn);
|
|
139
|
-
await sleep(
|
|
139
|
+
await sleep(100);
|
|
140
140
|
const final = await refetchMsg(channel, msgId);
|
|
141
141
|
if (final) {
|
|
142
142
|
logMsg(final, 'pm-result');
|
package/lib/commands/stream.js
CHANGED
|
@@ -230,10 +230,10 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
230
230
|
for (const item of itemsToBuy) {
|
|
231
231
|
const bought = await buyItem({ channel, waitForDankMemer, client, itemName: item, quantity: 1 });
|
|
232
232
|
if (!bought) return { result: `need ${item} (buy failed)`, coins: 0, nextCooldownSec: 1800 };
|
|
233
|
-
await humanDelay(
|
|
233
|
+
await humanDelay(200, 400);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
await sleep(
|
|
236
|
+
await sleep(800);
|
|
237
237
|
await channel.send('pls stream');
|
|
238
238
|
response = await waitForDankMemer(12000);
|
|
239
239
|
if (!response) return { result: 'no response after buy', coins: 0, nextCooldownSec: 180 };
|
|
@@ -256,7 +256,7 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
256
256
|
|
|
257
257
|
const selected = await selectRandomStreamOption(response);
|
|
258
258
|
if (selected) {
|
|
259
|
-
await humanDelay(
|
|
259
|
+
await humanDelay(80, 180);
|
|
260
260
|
const updatedAfterSelect = (await waitForDankMemer(5000)) || (await refetchMsg(channel, response.id));
|
|
261
261
|
if (updatedAfterSelect) {
|
|
262
262
|
response = updatedAfterSelect;
|
|
@@ -294,7 +294,7 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
await sleep(
|
|
297
|
+
await sleep(150);
|
|
298
298
|
const fresh = await refetchMsg(channel, response.id);
|
|
299
299
|
if (fresh) {
|
|
300
300
|
response = fresh;
|
|
@@ -305,7 +305,7 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
LOG.info('[stream] Clicking "Go Live"');
|
|
308
|
-
await humanDelay(
|
|
308
|
+
await humanDelay(50, 120);
|
|
309
309
|
try {
|
|
310
310
|
const before = response;
|
|
311
311
|
const liveResult = await safeClickButton(response, goLiveBtn);
|
|
@@ -340,7 +340,7 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
340
340
|
);
|
|
341
341
|
if (fallbackBtn) {
|
|
342
342
|
LOG.info('[stream] Go Live transition not detected; trying setup fallback');
|
|
343
|
-
await humanDelay(
|
|
343
|
+
await humanDelay(40, 100);
|
|
344
344
|
try {
|
|
345
345
|
const fallbackRes = await safeClickButton(response, fallbackBtn);
|
|
346
346
|
const afterFallback = fallbackRes || (await waitForStreamTransition({
|
|
@@ -376,7 +376,7 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
376
376
|
const action = pickRandom(actions);
|
|
377
377
|
const actionAt = new Date();
|
|
378
378
|
LOG.info(`[stream] Live action: "${action.label}"`);
|
|
379
|
-
await humanDelay(
|
|
379
|
+
await humanDelay(60, 150);
|
|
380
380
|
try {
|
|
381
381
|
const clicked = await safeClickButton(response, action);
|
|
382
382
|
let updated = clicked || (await waitForDankMemer(6000)) || (await refetchMsg(channel, response.id));
|
package/lib/commands/utils.js
CHANGED
|
@@ -311,49 +311,66 @@ function findSelectMenuOption(msg, label) {
|
|
|
311
311
|
// Safe button click — tries library methods first, falls back to raw HTTP for CV2.
|
|
312
312
|
// When CV2 fallback is used, waits for the message to update so callers always
|
|
313
313
|
// get the updated message back (instead of null, which broke multi-round games).
|
|
314
|
-
async function safeClickButton(msg, button) {
|
|
314
|
+
async function safeClickButton(msg, button, retries = 2) {
|
|
315
315
|
// Skip LINK buttons (external URLs) — they have style=LINK/5 and no customId
|
|
316
316
|
if (button.style === 'LINK' || button.style === 5 || (!button.customId && !button.custom_id && typeof button.click !== 'function')) {
|
|
317
317
|
return null;
|
|
318
318
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
const id = button.customId || button.custom_id;
|
|
323
|
-
if (id && typeof msg.clickButton === 'function') {
|
|
319
|
+
|
|
320
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
324
321
|
try {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (interactionAck) msg._lastInteractionAck = interactionAck;
|
|
335
|
-
// Wait for Dank Memer to process the interaction and update the message
|
|
336
|
-
const updatedMsg = await new Promise((resolve) => {
|
|
337
|
-
const timeout = setTimeout(() => {
|
|
338
|
-
msg.client?.removeListener?.('messageUpdate', handler);
|
|
339
|
-
resolve(null);
|
|
340
|
-
}, 8000);
|
|
341
|
-
const handler = (_, newMsg) => {
|
|
342
|
-
if (newMsg.id === msg.id) {
|
|
343
|
-
clearTimeout(timeout);
|
|
344
|
-
msg.client?.removeListener?.('messageUpdate', handler);
|
|
345
|
-
resolve(newMsg);
|
|
322
|
+
if (typeof button.click === 'function') {
|
|
323
|
+
return await button.click();
|
|
324
|
+
}
|
|
325
|
+
const id = button.customId || button.custom_id;
|
|
326
|
+
if (id && typeof msg.clickButton === 'function') {
|
|
327
|
+
try {
|
|
328
|
+
return await msg.clickButton(id);
|
|
329
|
+
} catch {
|
|
330
|
+
// Fall through to CV2 raw interaction fallback.
|
|
346
331
|
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
332
|
+
}
|
|
333
|
+
// CV2 fallback: send interaction via raw HTTP, then wait for the message
|
|
334
|
+
// to update so we can return the updated message to the caller.
|
|
335
|
+
if (id) {
|
|
336
|
+
const interactionAck = await clickCV2Button(msg, id);
|
|
337
|
+
if (interactionAck) msg._lastInteractionAck = interactionAck;
|
|
338
|
+
// Wait for Dank Memer to process the interaction and update the message
|
|
339
|
+
const updatedMsg = await new Promise((resolve) => {
|
|
340
|
+
const timeout = setTimeout(() => {
|
|
341
|
+
msg.client?.removeListener?.('messageUpdate', handler);
|
|
342
|
+
resolve(null);
|
|
343
|
+
}, 6000);
|
|
344
|
+
const handler = (_, newMsg) => {
|
|
345
|
+
if (newMsg.id === msg.id) {
|
|
346
|
+
clearTimeout(timeout);
|
|
347
|
+
msg.client?.removeListener?.('messageUpdate', handler);
|
|
348
|
+
resolve(newMsg);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
msg.client?.on?.('messageUpdate', handler);
|
|
352
|
+
});
|
|
353
|
+
if (updatedMsg) {
|
|
354
|
+
await ensureCV2(updatedMsg);
|
|
355
|
+
return updatedMsg;
|
|
356
|
+
}
|
|
357
|
+
// Retry if we got null
|
|
358
|
+
if (attempt < retries) {
|
|
359
|
+
await new Promise(r => setTimeout(r, 300 + attempt * 200));
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
throw new Error('No click method available on button');
|
|
365
|
+
} catch (err) {
|
|
366
|
+
if (attempt < retries) {
|
|
367
|
+
await new Promise(r => setTimeout(r, 300 + attempt * 200));
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
throw err;
|
|
353
371
|
}
|
|
354
|
-
return null;
|
|
355
372
|
}
|
|
356
|
-
|
|
373
|
+
return null;
|
|
357
374
|
}
|
|
358
375
|
|
|
359
376
|
// ── Hold Tight Detection ─────────────────────────────────────
|
package/lib/grinder.js
CHANGED
|
@@ -1507,210 +1507,116 @@ class AccountWorker {
|
|
|
1507
1507
|
if (!t) return false;
|
|
1508
1508
|
const lower = t.toLowerCase();
|
|
1509
1509
|
return lower.includes('balance') || lower.includes('balances') || lower.includes('global rank')
|
|
1510
|
-
|| lower.includes('wallet') || /<a?:coin:\d
|
|
1511
|
-
};
|
|
1512
|
-
|
|
1513
|
-
const readBalanceText = async (msg, forceCV2 = false) => {
|
|
1514
|
-
if (!msg) return '';
|
|
1515
|
-
const needsCv2 = forceCV2
|
|
1516
|
-
|| isCV2(msg)
|
|
1517
|
-
|| (Array.isArray(msg.components) && msg.components.length > 0
|
|
1518
|
-
&& (!msg.content || msg.content.length === 0)
|
|
1519
|
-
&& (!msg.embeds || msg.embeds.length === 0));
|
|
1520
|
-
if (needsCv2) await ensureCV2(msg, forceCV2);
|
|
1521
|
-
return stripAnsi(getFullText(msg)).replace(/\s+/g, ' ').trim();
|
|
1522
|
-
};
|
|
1523
|
-
|
|
1524
|
-
const findRecentBalanceMessage = async () => {
|
|
1525
|
-
if (!this.channel?.messages?.fetch) return null;
|
|
1526
|
-
for (let attempt = 0; attempt < 6; attempt++) {
|
|
1527
|
-
try {
|
|
1528
|
-
const recent = await this.channel.messages.fetch({ limit: 12 });
|
|
1529
|
-
const candidates = [...recent.values()].filter((m) =>
|
|
1530
|
-
m?.author?.id === DANK_MEMER_ID && (m.createdTimestamp || 0) >= sentAt - 10000
|
|
1531
|
-
);
|
|
1532
|
-
for (const m of candidates) {
|
|
1533
|
-
const t = await readBalanceText(m, true);
|
|
1534
|
-
if (looksLikeBalance(t)) return m;
|
|
1535
|
-
}
|
|
1536
|
-
} catch {}
|
|
1537
|
-
await new Promise((r) => setTimeout(r, 700));
|
|
1538
|
-
}
|
|
1539
|
-
return null;
|
|
1510
|
+
|| lower.includes('wallet') || /<a?:coin:\d+>/i.test(t) || /<a?:bank:\d+>/i.test(t);
|
|
1540
1511
|
};
|
|
1541
1512
|
|
|
1513
|
+
// Fast path: send command, wait 3s, read from rawLogger
|
|
1542
1514
|
if (this.account.use_slash && this.channel?.sendSlash) {
|
|
1543
1515
|
await this.channel.sendSlash(DANK_MEMER_ID, 'balance').catch(() => this.channel.send('/balance'));
|
|
1544
1516
|
} else {
|
|
1545
1517
|
await this.channel.send(`${prefix} bal`);
|
|
1546
1518
|
}
|
|
1547
|
-
|
|
1519
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
1548
1520
|
|
|
1549
|
-
//
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1521
|
+
// Try rawLogger first — it captures CV2 text from gateway instantly
|
|
1522
|
+
let text = '';
|
|
1523
|
+
const rawData = rawLogger.getLastRaw(this.channel?.id);
|
|
1524
|
+
if (rawData && rawData.cv2Text && looksLikeBalance(rawData.cv2Text)) {
|
|
1525
|
+
text = rawData.cv2Text;
|
|
1526
|
+
} else if (rawData && rawData.allText && looksLikeBalance(rawData.allText)) {
|
|
1527
|
+
text = rawData.allText;
|
|
1553
1528
|
}
|
|
1554
1529
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
if (edited) {
|
|
1562
|
-
text = await readBalanceText(edited, true);
|
|
1563
|
-
response = edited;
|
|
1564
|
-
}
|
|
1530
|
+
// If rawLogger didn't capture it, fall back to waitForDankMemer
|
|
1531
|
+
if (!text || !looksLikeBalance(text)) {
|
|
1532
|
+
let response = await this.waitForDankMemer(8000);
|
|
1533
|
+
if (!response && this.account.use_slash) {
|
|
1534
|
+
await this.channel.send('pls bal');
|
|
1535
|
+
response = await this.waitForDankMemer(8000);
|
|
1565
1536
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
const fetched = await Promise.resolve(this.channel.messages.fetch(response.id)).catch(() => null);
|
|
1570
|
-
if (fetched) {
|
|
1571
|
-
const fetchedText = await readBalanceText(fetched, true);
|
|
1572
|
-
if (fetchedText) {
|
|
1573
|
-
text = fetchedText;
|
|
1574
|
-
response = fetched;
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1537
|
+
if (response) {
|
|
1538
|
+
if (isCV2(response)) await ensureCV2(response);
|
|
1539
|
+
text = stripAnsi(getFullText(response)).replace(/\s+/g, ' ').trim();
|
|
1577
1540
|
}
|
|
1578
|
-
|
|
1579
|
-
// Fallback: scan latest Dank messages right after command send.
|
|
1580
|
-
if (!text || !looksLikeBalance(text)) {
|
|
1581
|
-
const recentBalance = await findRecentBalanceMessage();
|
|
1582
|
-
if (recentBalance) {
|
|
1583
|
-
text = await readBalanceText(recentBalance, true);
|
|
1584
|
-
response = recentBalance;
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
// Last resort: wait for CV2 content propagation then re-fetch
|
|
1589
|
-
if ((!text || !looksLikeBalance(text)) && response.id && this.channel?.messages?.fetch) {
|
|
1590
|
-
await new Promise(r => setTimeout(r, 3000));
|
|
1591
|
-
try {
|
|
1592
|
-
const fresh = await this.channel.messages.fetch(response.id);
|
|
1593
|
-
if (fresh) {
|
|
1594
|
-
const freshText = await readBalanceText(fresh, true);
|
|
1595
|
-
if (freshText && looksLikeBalance(freshText)) {
|
|
1596
|
-
text = freshText;
|
|
1597
|
-
response = fresh;
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
} catch {}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
// Absolute last: re-scan channel messages after the extra wait
|
|
1541
|
+
// One more rawLogger check after the wait
|
|
1604
1542
|
if (!text || !looksLikeBalance(text)) {
|
|
1605
|
-
const
|
|
1606
|
-
if (
|
|
1607
|
-
|
|
1608
|
-
response = recentBalance2;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
// Raw logger fallback — CV2 text is captured directly from gateway
|
|
1613
|
-
if (!text || !looksLikeBalance(text)) {
|
|
1614
|
-
const rawData = rawLogger.getLastRaw(this.channel?.id);
|
|
1615
|
-
if (rawData && rawData.cv2Text) {
|
|
1616
|
-
const rawText = rawData.cv2Text;
|
|
1617
|
-
if (looksLikeBalance(rawText)) {
|
|
1618
|
-
text = rawText;
|
|
1619
|
-
this.log('debug', 'Balance: using rawLogger CV2 text fallback');
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
// Also try from Redis raw message
|
|
1623
|
-
if ((!text || !looksLikeBalance(text)) && response?.id) {
|
|
1624
|
-
try {
|
|
1625
|
-
const rawMsg = await rawLogger.getMsg(response.id);
|
|
1626
|
-
if (rawMsg?.allText && looksLikeBalance(rawMsg.allText)) {
|
|
1627
|
-
text = rawMsg.allText;
|
|
1628
|
-
this.log('debug', 'Balance: using rawLogger Redis fallback');
|
|
1629
|
-
}
|
|
1630
|
-
} catch {}
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
if (!text) {
|
|
1635
|
-
this.log('warn', 'Balance response was empty after waiting for update');
|
|
1636
|
-
return;
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
if (!looksLikeBalance(text)) {
|
|
1640
|
-
this.log('warn', `Balance response did not look like balance card: "${text.substring(0, 140)}"`);
|
|
1641
|
-
return;
|
|
1543
|
+
const raw2 = rawLogger.getLastRaw(this.channel?.id);
|
|
1544
|
+
if (raw2?.cv2Text && looksLikeBalance(raw2.cv2Text)) text = raw2.cv2Text;
|
|
1545
|
+
else if (raw2?.allText && looksLikeBalance(raw2.allText)) text = raw2.allText;
|
|
1642
1546
|
}
|
|
1547
|
+
}
|
|
1643
1548
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1549
|
+
if (!text || !looksLikeBalance(text)) {
|
|
1550
|
+
this.log('warn', 'Balance: no data after all attempts');
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1647
1553
|
|
|
1648
|
-
//
|
|
1554
|
+
// Parse wallet and bank
|
|
1555
|
+
let wallet = 0, bank = 0, matched = '';
|
|
1649
1556
|
const coinMatch = text.match(/<a?:Coin:\d+>\s*([\d,]+)/i);
|
|
1650
1557
|
const bankEmojiMatch = text.match(/<a?:Bank:\d+>\s*([\d,]+)/i);
|
|
1651
1558
|
const bankSlashMatch = text.match(/(?:<a?:Bank:\d+>\s*)?([\d,]+)\s*\/\s*[\d,]+/i);
|
|
1652
1559
|
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1560
|
+
// Legacy embed format: Wallet: ⏣ 1,234,567
|
|
1561
|
+
const walletMatch = text.match(/wallet[:\s]*\*{0,2}\s*[⏣💰]?\s*\*{0,2}\s*([\d,]+)/i);
|
|
1562
|
+
const bankTextMatch = text.match(/bank[:\s]*\*{0,2}\s*[⏣💰]?\s*\*{0,2}\s*([\d,]+)/i);
|
|
1656
1563
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1564
|
+
// Fallback: any numbers near ⏣ or just plain numbers in CV2 text
|
|
1565
|
+
const allNums = [...text.matchAll(/(?:⏣\s*)?(\d[\d,]*\d)/g)].map(m => parseInt(m[1].replace(/,/g, ''), 10)).filter(n => n > 0);
|
|
1659
1566
|
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1567
|
+
if (coinMatch) {
|
|
1568
|
+
wallet = parseInt(coinMatch[1].replace(/,/g, ''), 10);
|
|
1569
|
+
matched = 'cv2-emoji';
|
|
1570
|
+
} else if (walletMatch) {
|
|
1571
|
+
wallet = parseInt(walletMatch[1].replace(/,/g, ''), 10);
|
|
1572
|
+
matched = 'legacy-wallet';
|
|
1573
|
+
} else if (allNums.length > 0) {
|
|
1574
|
+
wallet = Math.max(...allNums);
|
|
1575
|
+
matched = 'fallback-nums';
|
|
1576
|
+
}
|
|
1670
1577
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1578
|
+
if (bankEmojiMatch) {
|
|
1579
|
+
bank = parseInt(bankEmojiMatch[1].replace(/,/g, ''), 10);
|
|
1580
|
+
matched += matched ? '+bank-emoji' : 'bank-emoji';
|
|
1581
|
+
} else if (bankTextMatch) {
|
|
1582
|
+
bank = parseInt(bankTextMatch[1].replace(/,/g, ''), 10);
|
|
1583
|
+
matched += matched ? '+bank-text' : 'bank-text';
|
|
1584
|
+
} else if (bankSlashMatch) {
|
|
1585
|
+
bank = parseInt(bankSlashMatch[1].replace(/,/g, ''), 10);
|
|
1586
|
+
matched += matched ? '+bank-slash' : 'bank-slash';
|
|
1587
|
+
}
|
|
1681
1588
|
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1589
|
+
if (wallet === 0 && bank === 0) {
|
|
1590
|
+
this.log('warn', `Balance parse returned 0 — raw text: "${text.substring(0, 200)}"`);
|
|
1591
|
+
// Don't overwrite a known-good balance with 0
|
|
1592
|
+
if (this.stats.balance > 0 || this.stats.bankBalance > 0) return;
|
|
1593
|
+
}
|
|
1687
1594
|
|
|
1688
1595
|
this.stats.balance = wallet;
|
|
1689
1596
|
this.stats.bankBalance = bank;
|
|
1690
1597
|
this.log('bal', `Wallet: ${c.bold}${c.green}⏣ ${wallet.toLocaleString()}${c.reset} Bank: ${c.bold}${c.cyan}⏣ ${bank.toLocaleString()}${c.reset} Total: ${c.bold}⏣ ${(wallet + bank).toLocaleString()}${c.reset} ${c.dim}(${matched || 'none'})${c.reset}`);
|
|
1691
1598
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
try {
|
|
1695
|
-
await redis.set(`dkg:bal:${this.account.id}`, JSON.stringify({ wallet, bank, ts: Date.now() }));
|
|
1696
|
-
} catch {}
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
// Always report to dashboard API
|
|
1599
|
+
// Store in Redis for persistence
|
|
1600
|
+
if (redis) {
|
|
1700
1601
|
try {
|
|
1701
|
-
await
|
|
1702
|
-
|
|
1703
|
-
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
1704
|
-
body: JSON.stringify({
|
|
1705
|
-
account_id: this.account.id,
|
|
1706
|
-
balance: wallet,
|
|
1707
|
-
bank_balance: bank,
|
|
1708
|
-
total_balance: wallet + bank,
|
|
1709
|
-
lifesavers: this._lifesavers ?? null,
|
|
1710
|
-
}),
|
|
1711
|
-
});
|
|
1712
|
-
} catch { /* silent */ }
|
|
1602
|
+
await redis.set(`dkg:bal:${this.account.id}`, JSON.stringify({ wallet, bank, ts: Date.now() }));
|
|
1603
|
+
} catch {}
|
|
1713
1604
|
}
|
|
1605
|
+
|
|
1606
|
+
// Always report to dashboard API
|
|
1607
|
+
try {
|
|
1608
|
+
await fetch(`${API_URL}/api/grinder/status`, {
|
|
1609
|
+
method: 'POST',
|
|
1610
|
+
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
1611
|
+
body: JSON.stringify({
|
|
1612
|
+
account_id: this.account.id,
|
|
1613
|
+
balance: wallet,
|
|
1614
|
+
bank_balance: bank,
|
|
1615
|
+
total_balance: wallet + bank,
|
|
1616
|
+
lifesavers: this._lifesavers ?? null,
|
|
1617
|
+
}),
|
|
1618
|
+
});
|
|
1619
|
+
} catch { /* silent */ }
|
|
1714
1620
|
}
|
|
1715
1621
|
|
|
1716
1622
|
// ── Check DM History for deaths/level-ups ──────────────────
|
|
@@ -1805,20 +1711,68 @@ class AccountWorker {
|
|
|
1805
1711
|
if (shutdownCalled || !this.running) return;
|
|
1806
1712
|
this.stats.commands++;
|
|
1807
1713
|
|
|
1714
|
+
// ── Monthly: only run if balance ≥ 18M (advancements requirement) ──
|
|
1715
|
+
if (cmdName === 'monthly') {
|
|
1716
|
+
const totalBal = (this.stats.balance || 0) + (this.stats.bankBalance || 0);
|
|
1717
|
+
if (totalBal < 18_000_000) {
|
|
1718
|
+
this.log('warn', `[monthly] SKIPPED — balance ${(totalBal / 1e6).toFixed(1)}M < 18M`);
|
|
1719
|
+
await this.setCooldown(cmdName, 86400);
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
// Check if disabled (not premium / no advancement)
|
|
1723
|
+
if (redis) {
|
|
1724
|
+
try {
|
|
1725
|
+
const disabled = await redis.get(`dkg:disabled:${this.account.id}:monthly`);
|
|
1726
|
+
if (disabled) {
|
|
1727
|
+
this.log('warn', `[monthly] SKIPPED — disabled (needs advancement purchase)`);
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
} catch {}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// ── Daily/Monthly: check if already claimed today (avoid wasting a command) ──
|
|
1735
|
+
if (cmdName === 'daily' || cmdName === 'monthly') {
|
|
1736
|
+
if (redis) {
|
|
1737
|
+
try {
|
|
1738
|
+
const done = await redis.get(`dkg:done:${this.account.id}:${cmdName}`);
|
|
1739
|
+
if (done) {
|
|
1740
|
+
const ttl = await redis.ttl(`dkg:done:${this.account.id}:${cmdName}`);
|
|
1741
|
+
this.log('info', `[${cmdName}] already claimed — ${Math.ceil(ttl / 60)}min left`);
|
|
1742
|
+
await this.setCooldown(cmdName, Math.max(60, ttl));
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
} catch {}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// ── Deposit: max once per hour, only when enabled ──
|
|
1750
|
+
if (cmdName === 'dep max') {
|
|
1751
|
+
if (this._lastDepositAt && Date.now() - this._lastDepositAt < 3600_000) {
|
|
1752
|
+
return; // silently skip — too soon
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1808
1756
|
// ── Lifesaver protection: skip crime/search if 0 lifesavers ──
|
|
1809
1757
|
if (cmdName === 'crime' || cmdName === 'search') {
|
|
1758
|
+
// Fast path: check in-memory lifesaver count (set from inv + DM check)
|
|
1759
|
+
if (this._lifesavers === 0) {
|
|
1760
|
+
this.log('warn', `[${cmdName}] SKIPPED — 0 lifesavers (in-memory)`);
|
|
1761
|
+
await this.setCooldown(cmdName, 3600);
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1810
1764
|
const noLifesaver = await rawLogger.hasNoLifesaverAlert(this.channel?.id);
|
|
1811
1765
|
if (noLifesaver) {
|
|
1812
1766
|
this.log('warn', `[${cmdName}] SKIPPED — no lifesavers! (death detected in DMs)`);
|
|
1813
|
-
await this.setCooldown(cmdName, 3600);
|
|
1767
|
+
await this.setCooldown(cmdName, 3600);
|
|
1814
1768
|
return;
|
|
1815
1769
|
}
|
|
1816
|
-
// Also check Redis key for lifesaver count
|
|
1817
1770
|
if (redis) {
|
|
1818
1771
|
try {
|
|
1819
1772
|
const lsCount = await redis.get(`dkg:lifesavers:${this.account.id}`);
|
|
1820
1773
|
if (lsCount === '0') {
|
|
1821
|
-
this.
|
|
1774
|
+
this._lifesavers = 0;
|
|
1775
|
+
this.log('warn', `[${cmdName}] SKIPPED — 0 lifesavers (Redis)`);
|
|
1822
1776
|
await this.setCooldown(cmdName, 3600);
|
|
1823
1777
|
return;
|
|
1824
1778
|
}
|
|
@@ -1885,7 +1839,7 @@ class AccountWorker {
|
|
|
1885
1839
|
// PostMemes / command-specific cooldown from response
|
|
1886
1840
|
if (resultLower.includes('cannot post another meme') || resultLower.includes('dead meme')) {
|
|
1887
1841
|
const minMatch = result.match(/(\d+)\s*minute/i);
|
|
1888
|
-
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 :
|
|
1842
|
+
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 + 30 : 150; // dead meme = N min + 30s buffer
|
|
1889
1843
|
this.log('warn', `${cmdName} on cooldown: ${cdSec}s`);
|
|
1890
1844
|
await this.setCooldown(cmdName, cdSec);
|
|
1891
1845
|
return;
|
|
@@ -1965,11 +1919,13 @@ class AccountWorker {
|
|
|
1965
1919
|
return;
|
|
1966
1920
|
}
|
|
1967
1921
|
|
|
1968
|
-
// Premium-only command detection — disable
|
|
1922
|
+
// Premium-only command detection — disable permanently
|
|
1969
1923
|
if (resultLower.includes('only available on premium') || resultLower.includes('premium') ||
|
|
1970
|
-
resultLower.includes('buy the ability to use this command')
|
|
1971
|
-
|
|
1972
|
-
|
|
1924
|
+
resultLower.includes('buy the ability to use this command') ||
|
|
1925
|
+
resultLower.includes('advancements upgrades')) {
|
|
1926
|
+
this.log('warn', `${cmdName} requires premium/advancement — DISABLED`);
|
|
1927
|
+
await this.setCooldown(cmdName, 2592000); // 30 days
|
|
1928
|
+
if (redis) try { await redis.set(`dkg:disabled:${this.account.id}:${cmdName}`, '1', 'EX', 2592000); } catch {}
|
|
1973
1929
|
return;
|
|
1974
1930
|
}
|
|
1975
1931
|
|
|
@@ -2076,9 +2032,12 @@ class AccountWorker {
|
|
|
2076
2032
|
}
|
|
2077
2033
|
}
|
|
2078
2034
|
|
|
2079
|
-
// Smart auto-deposit:
|
|
2080
|
-
|
|
2035
|
+
// Smart auto-deposit: max once per hour, only if deposit is enabled
|
|
2036
|
+
const depositEnabled = this.account.cmd_deposit !== false;
|
|
2037
|
+
const depositCooldownOk = !this._lastDepositAt || Date.now() - this._lastDepositAt >= 3600_000;
|
|
2038
|
+
if (depositEnabled && depositCooldownOk && earned > 0 && this.stats.balance > this._autoDepositThreshold) {
|
|
2081
2039
|
this.log('info', `Wallet ⏣ ${this.stats.balance.toLocaleString()} exceeds threshold — auto-depositing`);
|
|
2040
|
+
this._lastDepositAt = Date.now();
|
|
2082
2041
|
try {
|
|
2083
2042
|
await this.channel.send('pls dep max');
|
|
2084
2043
|
await this.waitForDankMemer(6000);
|
|
@@ -2172,10 +2131,10 @@ class AccountWorker {
|
|
|
2172
2131
|
{ key: 'cmd_work', cmd: 'work shift', cdKey: 'cd_work', defaultCd: 1800, priority: 3 },
|
|
2173
2132
|
// Time-gated (run ASAP when available)
|
|
2174
2133
|
{ key: 'cmd_daily', cmd: 'daily', cdKey: 'cd_daily', defaultCd: 86400, priority: 10 },
|
|
2175
|
-
|
|
2134
|
+
// weekly removed — premium only, not available for free users
|
|
2176
2135
|
{ key: 'cmd_monthly', cmd: 'monthly', cdKey: 'cd_monthly', defaultCd: 2592000,priority: 10 },
|
|
2177
2136
|
// Financial safety
|
|
2178
|
-
{ key: 'cmd_deposit', cmd: 'dep max', cdKey: 'cd_deposit', defaultCd:
|
|
2137
|
+
{ key: 'cmd_deposit', cmd: 'dep max', cdKey: 'cd_deposit', defaultCd: 3600, priority: 8 },
|
|
2179
2138
|
{ key: 'cmd_drops', cmd: 'drops', cdKey: 'cd_drops', defaultCd: 86400, priority: 2 },
|
|
2180
2139
|
// Alert is NOT scheduled — it's reactive (listener-based, see grindLoop)
|
|
2181
2140
|
].map(Object.freeze);
|
|
@@ -3171,16 +3130,16 @@ async function start(apiKey, apiUrl) {
|
|
|
3171
3130
|
let balDone = 0;
|
|
3172
3131
|
const balProgressInterval = setInterval(() => {
|
|
3173
3132
|
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3174
|
-
process.stdout.write(`\r ${rgb(34, 211, 238)}${spin}${c.reset} ${c.dim}Balance...${c.reset} ${c.bold}${rgb(52, 211, 153)}${balDone}${c.reset}${c.dim}/${c.reset}${c.white}${
|
|
3133
|
+
process.stdout.write(`\r ${rgb(34, 211, 238)}${spin}${c.reset} ${c.dim}Balance...${c.reset} ${c.bold}${rgb(52, 211, 153)}${balDone}${c.reset}${c.dim}/${c.reset}${c.white}${activeWorkers.length}${c.reset} `);
|
|
3175
3134
|
}, 80);
|
|
3176
3135
|
|
|
3177
|
-
// Run
|
|
3178
|
-
|
|
3136
|
+
// Run in parallel
|
|
3137
|
+
await Promise.all(activeWorkers.map(async w => {
|
|
3179
3138
|
try {
|
|
3180
3139
|
await w.checkBalance();
|
|
3140
|
+
balDone++;
|
|
3181
3141
|
} catch {}
|
|
3182
|
-
|
|
3183
|
-
}
|
|
3142
|
+
}));
|
|
3184
3143
|
|
|
3185
3144
|
clearInterval(balProgressInterval);
|
|
3186
3145
|
process.stdout.write(`\r${c.clearLine}`);
|
|
@@ -3213,6 +3172,9 @@ async function start(apiKey, apiUrl) {
|
|
|
3213
3172
|
if (dm.deaths > 0) dmDeaths += dm.deaths;
|
|
3214
3173
|
if (dm.levelUps > 0) dmLevelUps += dm.levelUps;
|
|
3215
3174
|
if (dm.lifesavers === 0) dmNoLs.push(w.username);
|
|
3175
|
+
// Store level and lifesaver for dashboard
|
|
3176
|
+
if (dm.currentLevel > 0) w._level = dm.currentLevel;
|
|
3177
|
+
if (dm.lifesavers >= 0) w._lifesavers = dm.lifesavers;
|
|
3216
3178
|
const parts = [];
|
|
3217
3179
|
if (dm.currentLevel > 0) parts.push(`Lv${dm.currentLevel}`);
|
|
3218
3180
|
if (dm.deaths > 0) parts.push(`${rgb(239, 68, 68)}${dm.deaths} deaths${c.reset}`);
|
|
@@ -3227,6 +3189,15 @@ async function start(apiKey, apiUrl) {
|
|
|
3227
3189
|
}
|
|
3228
3190
|
if (dmNoLs.length > 0) {
|
|
3229
3191
|
console.log(` ${rgb(239, 68, 68)}⚠${c.reset} ${c.bold}${c.red}DM confirms 0 lifesavers:${c.reset} ${dmNoLs.join(', ')}`);
|
|
3192
|
+
// Set Redis keys to block crime/search
|
|
3193
|
+
for (const w of activeWorkers) {
|
|
3194
|
+
if (dmNoLs.includes(w.username) && redis) {
|
|
3195
|
+
try {
|
|
3196
|
+
await redis.set(`dkg:lifesavers:${w.account.id}`, '0', 'EX', 86400);
|
|
3197
|
+
await redis.set(`raw:alert:no-lifesaver:${w.channel?.id}`, '1', 'EX', 86400);
|
|
3198
|
+
} catch {}
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3230
3201
|
}
|
|
3231
3202
|
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}DM check${c.reset} ${c.dim}${dmDeaths} deaths, ${dmLevelUps} level-ups found${c.reset}`);
|
|
3232
3203
|
console.log('');
|
package/lib/rawLogger.js
CHANGED
|
@@ -152,6 +152,9 @@ function detectCommand(d) {
|
|
|
152
152
|
if (cv2Text.includes('fishing') || cv2Text.includes('fisherfolk')) return 'fish';
|
|
153
153
|
if (cv2Text.includes('deposit') || cv2Text.includes('bank account')) return 'deposit';
|
|
154
154
|
if (cv2Text.includes('begging') || cv2Text.includes('imagine begging')) return 'beg';
|
|
155
|
+
if (cv2Text.includes('hunting') || cv2Text.includes('went hunting') || cv2Text.includes('hunting rifle')) return 'hunt';
|
|
156
|
+
if (cv2Text.includes('digging') || cv2Text.includes('found nothing while') || cv2Text.includes('you dig')) return 'dig';
|
|
157
|
+
if (cv2Text.includes('great work') || cv2Text.includes('for your shift') || cv2Text.includes('working as') || cv2Text.includes('work shift') || cv2Text.includes('what color was') || cv2Text.includes('remember words order') || cv2Text.includes('remember the colors') || cv2Text.includes('remember the emojis') || cv2Text.includes('what word was repeated') || cv2Text.includes('unscramble') || cv2Text.includes('remember the number') || cv2Text.includes('click the buttons in correct order') || cv2Text.includes('babysitter') || cv2Text.includes('click the matching')) return 'work';
|
|
155
158
|
if (cv2Text.includes('weekly')) return 'weekly';
|
|
156
159
|
if (cv2Text.includes('daily')) return 'daily';
|
|
157
160
|
if (cv2Text.includes('inventory')) return 'inventory';
|
|
@@ -174,11 +177,13 @@ function detectCommand(d) {
|
|
|
174
177
|
if (embedText.includes('you searched') || embedText.includes('searched the')) return 'search';
|
|
175
178
|
if (embedText.includes('you committed') || embedText.includes('went outside')) return 'crime';
|
|
176
179
|
// Hunt / dig
|
|
177
|
-
if (embedText.includes('hunting') || embedText.includes('came back with') || embedText.includes('hunting rifle') || embedText.includes('dragon\'s fireball') || embedText.includes('dodge the')) return 'hunt';
|
|
178
|
-
if (embedText.includes('you dig') || embedText.includes('found a') && embedText.includes('
|
|
179
|
-
// Work
|
|
180
|
-
if (embedText.includes('work') && (embedText.includes('shift') || embedText.includes('mini-game') || embedText.includes('color') || embedText.includes('what color'))) return 'work';
|
|
180
|
+
if (embedText.includes('hunting') || embedText.includes('came back with') || embedText.includes('hunting rifle') || embedText.includes('dragon\'s fireball') || embedText.includes('dodge the') || embedText.includes('went hunting') || embedText.includes('hunt') && (embedText.includes('caught') || embedText.includes('brought back') || embedText.includes('attacked') || embedText.includes('nothing') || embedText.includes('laughed') || embedText.includes('escaped') || embedText.includes('fell asleep'))) return 'hunt';
|
|
181
|
+
if (embedText.includes('digging') || embedText.includes('you dig') || embedText.includes('found a') && embedText.includes('dig') || embedText.includes('shovel') || embedText.includes('found nothing while') || embedText.includes('you found') && !embedText.includes('search')) return 'dig';
|
|
182
|
+
// Work — match both minigame prompt AND completion
|
|
183
|
+
if (embedText.includes('work') && (embedText.includes('shift') || embedText.includes('mini-game') || embedText.includes('color') || embedText.includes('what color') || embedText.includes('babysitter') || embedText.includes('great work') || embedText.includes('for your shift'))) return 'work';
|
|
181
184
|
if (embedText.includes('you were given') && embedText.includes('shift')) return 'work';
|
|
185
|
+
if (embedText.includes('working as') || embedText.includes('for your shift')) return 'work';
|
|
186
|
+
if (embedText.includes('remember words order') || embedText.includes('remember the colors') || embedText.includes('remember the emojis') || embedText.includes('what word was repeated') || embedText.includes('unscramble the word') || embedText.includes('remember the number') || embedText.includes('click the buttons in correct order') || embedText.includes('click the matching')) return 'work';
|
|
182
187
|
// Postmemes
|
|
183
188
|
if (embedText.includes('pick a meme') || embedText.includes('meme posting')) return 'postmemes';
|
|
184
189
|
// Stream
|