dankgrinder 8.107.0 → 8.109.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/index.js +2 -1
- package/lib/grinder.js +315 -164
- package/package.json +1 -1
package/lib/commands/index.js
CHANGED
|
@@ -16,7 +16,7 @@ const { runPostMemes } = require('./postmemes');
|
|
|
16
16
|
const { runScratch } = require('./scratch');
|
|
17
17
|
const { runBlackjack } = require('./blackjack');
|
|
18
18
|
const { runTrivia, triviaDB } = require('./trivia');
|
|
19
|
-
const { runWorkShift } = require('./work');
|
|
19
|
+
const { runWorkShift, autoApplyForJob } = require('./work');
|
|
20
20
|
const { runCointoss, runRoulette, runSlots, runSnakeeyes } = require('./gamble');
|
|
21
21
|
const { runDeposit } = require('./deposit');
|
|
22
22
|
const { runGeneric, runAlert } = require('./generic');
|
|
@@ -44,6 +44,7 @@ module.exports = {
|
|
|
44
44
|
runBlackjack,
|
|
45
45
|
runTrivia,
|
|
46
46
|
runWorkShift,
|
|
47
|
+
autoApplyForJob,
|
|
47
48
|
runCointoss,
|
|
48
49
|
runRoulette,
|
|
49
50
|
runSlots,
|
package/lib/grinder.js
CHANGED
|
@@ -670,9 +670,10 @@ class AccountWorker {
|
|
|
670
670
|
this._levelQuestActive = false;
|
|
671
671
|
this._levelQuestQueue = [];
|
|
672
672
|
this._levelQuestDone = new Set();
|
|
673
|
-
|
|
673
|
+
this._levelQuestProgressCache = new Map();
|
|
674
|
+
this._levelQuestRunMap = new Map(); // level -> { startedAt, updatedAt, entries: Map<cmd, state> }
|
|
674
675
|
this._questBetOverride = null;
|
|
675
|
-
|
|
676
|
+
this._lastCommandMeta = null;
|
|
676
677
|
this._commandRunning = false; // prevents grinding commands from overlapping with quest commands
|
|
677
678
|
this._verifyLevelUnlock = null; // holds level to verify after quest completion
|
|
678
679
|
this.commandQueue = null;
|
|
@@ -843,148 +844,13 @@ class AccountWorker {
|
|
|
843
844
|
}
|
|
844
845
|
|
|
845
846
|
async buyItem(itemName, quantity = 1) {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
854
|
-
let response = await this.waitForDankMemer(10000);
|
|
855
|
-
if (!response) {
|
|
856
|
-
this.log('warn', 'No response to shop view command.');
|
|
857
|
-
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
858
|
-
return false;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
const responseText = getFullText(response).toLowerCase();
|
|
862
|
-
const hasShopComponents = (response.components || []).some(row =>
|
|
863
|
-
(row.components || []).some(comp => comp.type === 3 || (comp.type === 2 && comp.label && comp.label.toLowerCase().includes('buy')))
|
|
864
|
-
);
|
|
865
|
-
|
|
866
|
-
if (!hasShopComponents && (responseText.includes('lucky') || responseText.includes('event') || responseText.includes('for the rest of the day'))) {
|
|
867
|
-
this.log('warn', 'Got event response instead of shop. Retrying...');
|
|
868
|
-
await humanDelay(3000, 5000);
|
|
869
|
-
continue;
|
|
870
|
-
}
|
|
871
|
-
if (!hasShopComponents && responseText.includes('shop')) {
|
|
872
|
-
const shopUI = await this.waitForDankMemer(8000);
|
|
873
|
-
if (shopUI) response = shopUI;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
// Navigate to Coin Shop
|
|
877
|
-
let coinShopMenuId = null;
|
|
878
|
-
let coinShopOption = null;
|
|
879
|
-
for (const row of response.components || []) {
|
|
880
|
-
for (const comp of row.components || []) {
|
|
881
|
-
if (comp.type === 3) {
|
|
882
|
-
const opt = (comp.options || []).find(o => o.label && o.label.includes('Coin Shop'));
|
|
883
|
-
if (opt) { coinShopMenuId = comp.customId; coinShopOption = opt; }
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
if (coinShopMenuId && coinShopOption) {
|
|
889
|
-
this.log('buy', 'Navigating to Coin Shop...');
|
|
890
|
-
try {
|
|
891
|
-
await response.selectMenu(coinShopMenuId, [coinShopOption.value]);
|
|
892
|
-
const updatedMsg = await this.waitForDankMemer(8000);
|
|
893
|
-
if (updatedMsg) response = updatedMsg;
|
|
894
|
-
} catch (e) {
|
|
895
|
-
this.log('error', `Failed to open Coin Shop: ${e.message}`);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
await humanDelay(300, 600);
|
|
899
|
-
|
|
900
|
-
// Find Buy button — match by full name or partial name
|
|
901
|
-
let buyBtn = null;
|
|
902
|
-
const searchNames = [
|
|
903
|
-
itemName.toLowerCase(),
|
|
904
|
-
itemName.toLowerCase().replace('hunting ', '').replace('fishing ', ''),
|
|
905
|
-
itemName.toLowerCase().split(' ')[0],
|
|
906
|
-
];
|
|
907
|
-
for (const row of response.components || []) {
|
|
908
|
-
for (const comp of row.components || []) {
|
|
909
|
-
if (comp.type !== 2 || !comp.label) continue;
|
|
910
|
-
const label = comp.label.toLowerCase();
|
|
911
|
-
if (searchNames.some(s => label.includes(s) || s.includes(label))) {
|
|
912
|
-
buyBtn = comp; break;
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
if (buyBtn) break;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
if (!buyBtn) {
|
|
919
|
-
this.log('warn', `Could not find Buy button for ${itemName} (attempt ${attempt})`);
|
|
920
|
-
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
921
|
-
return false;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
this.log('buy', `Clicking Buy ${itemName}...`);
|
|
925
|
-
try { await safeClickButton(response, buyBtn); } catch (e) {
|
|
926
|
-
this.log('error', `Buy click failed: ${e.message}`);
|
|
927
|
-
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
928
|
-
return false;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
// Handle Modal
|
|
932
|
-
const modal = await new Promise((resolve) => {
|
|
933
|
-
const timer = setTimeout(() => resolve(null), 8000);
|
|
934
|
-
const handler = (m) => {
|
|
935
|
-
clearTimeout(timer);
|
|
936
|
-
this.client.removeListener('interactionModalCreate', handler);
|
|
937
|
-
resolve(m);
|
|
938
|
-
};
|
|
939
|
-
this.client.on('interactionModalCreate', handler);
|
|
940
|
-
});
|
|
941
|
-
|
|
942
|
-
if (modal) {
|
|
943
|
-
this.log('buy', `Submitting quantity ${c.bold}${quantity}${c.reset} in modal...`);
|
|
944
|
-
try {
|
|
945
|
-
const quantityInputId = modal.components[0].components[0].customId;
|
|
946
|
-
await fetch('https://discord.com/api/v9/interactions', {
|
|
947
|
-
method: 'POST',
|
|
948
|
-
headers: { 'Authorization': this.client.token, 'Content-Type': 'application/json' },
|
|
949
|
-
body: JSON.stringify({
|
|
950
|
-
type: 5, application_id: modal.applicationId,
|
|
951
|
-
channel_id: this.channel.id, guild_id: this.channel.guild?.id,
|
|
952
|
-
data: {
|
|
953
|
-
id: modal.id, custom_id: modal.customId,
|
|
954
|
-
components: [{ type: 1, components: [{ type: 4, custom_id: quantityInputId, value: String(quantity) }] }]
|
|
955
|
-
},
|
|
956
|
-
session_id: this.client.sessionId || "dummy_session",
|
|
957
|
-
nonce: Date.now().toString()
|
|
958
|
-
})
|
|
959
|
-
});
|
|
960
|
-
} catch (e) {
|
|
961
|
-
this.log('error', `Modal submit failed: ${e.message}`);
|
|
962
|
-
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
963
|
-
return false;
|
|
964
|
-
}
|
|
965
|
-
} else {
|
|
966
|
-
this.log('warn', 'No modal appeared after clicking buy.');
|
|
967
|
-
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
968
|
-
return false;
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
const confirmMsg = await this.waitForDankMemer(8000);
|
|
972
|
-
if (confirmMsg) {
|
|
973
|
-
const text = getFullText(confirmMsg).toLowerCase();
|
|
974
|
-
if (text.includes('bought') || text.includes('purchased') || text.includes('success')) {
|
|
975
|
-
this.log('success', `Bought ${c.bold}${quantity}x ${itemName}${c.reset}!`);
|
|
976
|
-
return true;
|
|
977
|
-
}
|
|
978
|
-
if (text.includes('not enough') || text.includes("can't afford") || text.includes('insufficient')) {
|
|
979
|
-
this.log('warn', `Not enough coins to buy ${itemName}.`);
|
|
980
|
-
return false;
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
this.log('success', `Submitted purchase for ${quantity}x ${itemName}.`);
|
|
984
|
-
return true;
|
|
985
|
-
}
|
|
986
|
-
this.log('error', `Failed to buy ${itemName} after ${MAX_RETRIES} attempts.`);
|
|
987
|
-
return false;
|
|
847
|
+
return !!(await commands.buyItem({
|
|
848
|
+
channel: this.channel,
|
|
849
|
+
waitForDankMemer: (timeout) => this.waitForDankMemer(timeout),
|
|
850
|
+
client: this.client,
|
|
851
|
+
itemName,
|
|
852
|
+
quantity,
|
|
853
|
+
}));
|
|
988
854
|
}
|
|
989
855
|
|
|
990
856
|
// ── Check Balance ───────────────────────────────────────────
|
|
@@ -1387,7 +1253,7 @@ class AccountWorker {
|
|
|
1387
1253
|
// Each modular command handler sends the command, waits for response,
|
|
1388
1254
|
// handles Hold Tight / cooldowns / item-buying internally.
|
|
1389
1255
|
async runCommand(cmdName, prefix) {
|
|
1390
|
-
this._lastCommandMeta = { cmdName, nonPlay: false, spent: 0, earned: 0, holdTightReason: null, newMinBet: null, levelLocked: null };
|
|
1256
|
+
this._lastCommandMeta = { cmdName, nonPlay: false, spent: 0, earned: 0, holdTightReason: null, newMinBet: null, levelLocked: null, nextRetrySec: 0 };
|
|
1391
1257
|
let cmdString;
|
|
1392
1258
|
const bjBet = Math.max(5000, this.account.bet_amount || 5000);
|
|
1393
1259
|
const gambBet = Math.max(10000, this.account.bet_amount || 10000);
|
|
@@ -1502,7 +1368,7 @@ class AccountWorker {
|
|
|
1502
1368
|
case 'search': cmdResult = await commands.runSearch(cmdOpts); break;
|
|
1503
1369
|
case 'crime': cmdResult = await commands.runCrime(cmdOpts); break;
|
|
1504
1370
|
case 'hl': cmdResult = await commands.runHighLow(cmdOpts); break;
|
|
1505
|
-
|
|
1371
|
+
case 'farm': cmdResult = await commands.runFarm(cmdOpts); break;
|
|
1506
1372
|
case 'pm': cmdResult = await commands.runPostMemes(cmdOpts); break;
|
|
1507
1373
|
case 'hunt': cmdResult = await commands.runHunt(cmdOpts); break;
|
|
1508
1374
|
case 'dig': cmdResult = await commands.runDig(cmdOpts); break;
|
|
@@ -1512,6 +1378,20 @@ class AccountWorker {
|
|
|
1512
1378
|
case 'blackjack': cmdResult = await commands.runBlackjack(cmdOpts); break;
|
|
1513
1379
|
case 'trivia': cmdResult = await commands.runTrivia(cmdOpts); break;
|
|
1514
1380
|
case 'work shift': cmdResult = await commands.runWorkShift(cmdOpts); break;
|
|
1381
|
+
case 'work apply': {
|
|
1382
|
+
const ar = await commands.autoApplyForJob(cmdOpts);
|
|
1383
|
+
cmdResult = ar?.applied
|
|
1384
|
+
? { result: 'work apply completed', coins: 0, nextCooldownSec: 8 }
|
|
1385
|
+
: { result: `work apply failed${ar?.cooldownSec ? ` (${Math.ceil(ar.cooldownSec / 60)}m)` : ''}`, coins: 0, nonPlay: true, nextCooldownSec: ar?.cooldownSec || 60 };
|
|
1386
|
+
break;
|
|
1387
|
+
}
|
|
1388
|
+
case 'shop view': {
|
|
1389
|
+
const bought = await commands.buyItem({ ...cmdOpts, itemName: 'Shovel', quantity: 1 });
|
|
1390
|
+
cmdResult = bought
|
|
1391
|
+
? { result: 'shop buy item completed', coins: 0, nextCooldownSec: 10 }
|
|
1392
|
+
: { result: 'shop buy item failed', coins: 0, nonPlay: true, nextCooldownSec: 25 };
|
|
1393
|
+
break;
|
|
1394
|
+
}
|
|
1515
1395
|
case 'cointoss': cmdResult = await commands.runCointoss(cmdOpts); break;
|
|
1516
1396
|
case 'roulette': cmdResult = await commands.runRoulette(cmdOpts); break;
|
|
1517
1397
|
case 'slots': cmdResult = await commands.runSlots(cmdOpts); break;
|
|
@@ -1526,6 +1406,7 @@ class AccountWorker {
|
|
|
1526
1406
|
|
|
1527
1407
|
const result = cmdResult.result || 'done';
|
|
1528
1408
|
const resultLower = result.toLowerCase();
|
|
1409
|
+
this._lastCommandMeta.result = result;
|
|
1529
1410
|
|
|
1530
1411
|
// Rate limit detection — progressive backoff based on frequency
|
|
1531
1412
|
if (resultLower.includes('slow down') || resultLower.includes('rate limit') || resultLower.includes('too fast')) {
|
|
@@ -1535,6 +1416,7 @@ class AccountWorker {
|
|
|
1535
1416
|
const cooldownSec = Math.min(30 * Math.pow(2, Math.min(this._rateLimitHits - 1, 3)), 300);
|
|
1536
1417
|
this.log('warn', `Rate limited! ${cooldownSec}s cooldown (hit #${this._rateLimitHits})`);
|
|
1537
1418
|
this.globalCooldownUntil = Date.now() + cooldownSec * 1000;
|
|
1419
|
+
this._lastCommandMeta.nextRetrySec = cooldownSec;
|
|
1538
1420
|
await this.setCooldown(cmdName, cooldownSec);
|
|
1539
1421
|
// Reset rate limit count after 10 minutes of no hits
|
|
1540
1422
|
setTimeout(() => { if (this._rateLimitHits > 0) this._rateLimitHits = Math.max(0, this._rateLimitHits - 1); }, 600_000);
|
|
@@ -1545,6 +1427,8 @@ class AccountWorker {
|
|
|
1545
1427
|
if (resultLower.includes('cannot post another meme') || resultLower.includes('dead meme')) {
|
|
1546
1428
|
const minMatch = result.match(/(\d+)\s*minute/i);
|
|
1547
1429
|
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 + 30 : 150; // dead meme = N min + 30s buffer
|
|
1430
|
+
this._lastCommandMeta.nonPlay = true;
|
|
1431
|
+
this._lastCommandMeta.nextRetrySec = cdSec;
|
|
1548
1432
|
this.log('warn', `${cmdName} on cooldown: ${cdSec}s`);
|
|
1549
1433
|
await this.setCooldown(cmdName, cdSec);
|
|
1550
1434
|
return;
|
|
@@ -1565,6 +1449,28 @@ class AccountWorker {
|
|
|
1565
1449
|
this._mergeLevelQuestProgress(targetLv, progress);
|
|
1566
1450
|
}
|
|
1567
1451
|
this.log('warn', `Command /${cmdName} requires Level ${targetLv} quests`);
|
|
1452
|
+
} else {
|
|
1453
|
+
// Fallback: some responses are truncated/ephemeral and omit "Level X" in result text.
|
|
1454
|
+
// Run one profile check and infer lock level from raw profile text.
|
|
1455
|
+
try {
|
|
1456
|
+
const profile = await this.checkProfile(true);
|
|
1457
|
+
const profileText = String(profile?.rawText || '');
|
|
1458
|
+
if (profileText.toLowerCase().includes('not unlocked') || profileText.toLowerCase().includes('have not unlocked')) {
|
|
1459
|
+
const pm = profileText.match(/level\s*(\d+)/i);
|
|
1460
|
+
if (pm) {
|
|
1461
|
+
const targetLv = parseInt(pm[1], 10);
|
|
1462
|
+
if (Number.isFinite(targetLv) && targetLv > 0) {
|
|
1463
|
+
cmdResult.levelLocked = targetLv;
|
|
1464
|
+
const progress = this._parseLevelQuestProgress(targetLv, profileText);
|
|
1465
|
+
if (progress) {
|
|
1466
|
+
cmdResult.levelQuestProgress = progress;
|
|
1467
|
+
this._mergeLevelQuestProgress(targetLv, progress);
|
|
1468
|
+
}
|
|
1469
|
+
this.log('warn', `Command /${cmdName} requires Level ${targetLv} quests (profile fallback)`);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
} catch {}
|
|
1568
1474
|
}
|
|
1569
1475
|
}
|
|
1570
1476
|
|
|
@@ -1660,6 +1566,7 @@ class AccountWorker {
|
|
|
1660
1566
|
if (resultLower.includes('already got your daily') || resultLower.includes('already got your weekly') ||
|
|
1661
1567
|
resultLower.includes('already got your monthly') || resultLower.includes('already claimed') ||
|
|
1662
1568
|
resultLower.includes('try again <t:')) {
|
|
1569
|
+
this._lastCommandMeta.nonPlay = true;
|
|
1663
1570
|
this.log('info', `${cmdName} already claimed — waiting`);
|
|
1664
1571
|
const timeMatch = result.match(/<t:(\d+):R>/);
|
|
1665
1572
|
let waitSec;
|
|
@@ -1670,6 +1577,7 @@ class AccountWorker {
|
|
|
1670
1577
|
const defaultWaits = { daily: 86400, weekly: 604800, monthly: 2592000 };
|
|
1671
1578
|
waitSec = defaultWaits[cmdName] || 86400;
|
|
1672
1579
|
}
|
|
1580
|
+
this._lastCommandMeta.nextRetrySec = waitSec;
|
|
1673
1581
|
await this.setCooldown(cmdName, waitSec);
|
|
1674
1582
|
this.doneToday.set(cmdName, Date.now() + waitSec * 1000);
|
|
1675
1583
|
if (redis) try { await redis.set(`dkg:done:${this.account.id}:${cmdName}`, '1', 'EX', waitSec); } catch {}
|
|
@@ -1695,6 +1603,7 @@ class AccountWorker {
|
|
|
1695
1603
|
// Track net earnings (add wins, subtract losses)
|
|
1696
1604
|
this.stats.coins += (earned - spent);
|
|
1697
1605
|
if (cmdResult.nextCooldownSec) {
|
|
1606
|
+
this._lastCommandMeta.nextRetrySec = Math.max(this._lastCommandMeta.nextRetrySec || 0, cmdResult.nextCooldownSec);
|
|
1698
1607
|
await this.setCooldown(cmdName, cmdResult.nextCooldownSec);
|
|
1699
1608
|
this._lastCooldownOverride = cmdResult.nextCooldownSec;
|
|
1700
1609
|
// Learn: record this cooldown as the known value for future fallback use
|
|
@@ -1738,6 +1647,7 @@ class AccountWorker {
|
|
|
1738
1647
|
this._lastCommandMeta.nonPlay = true;
|
|
1739
1648
|
this._lastCommandMeta.holdTightReason = reason;
|
|
1740
1649
|
const holdSec = 35;
|
|
1650
|
+
this._lastCommandMeta.nextRetrySec = Math.max(this._lastCommandMeta.nextRetrySec || 0, holdSec);
|
|
1741
1651
|
this.log('warn', `Hold Tight: /${reason} — ${holdSec}s global cooldown`);
|
|
1742
1652
|
const reasonMap = { postmemes: 'pm', highlow: 'hl', blackjack: 'bj', 'work shift': 'work shift' };
|
|
1743
1653
|
const mappedCmd = reasonMap[reason] || reason;
|
|
@@ -1917,10 +1827,21 @@ class AccountWorker {
|
|
|
1917
1827
|
{ cmd: 'search', times: 2 },
|
|
1918
1828
|
{ cmd: 'tidy', times: 2 },
|
|
1919
1829
|
],
|
|
1830
|
+
2: [
|
|
1831
|
+
{ cmd: 'inventory', times: 1, progressMatch: 'inventory', progressSlash: 'inventory' },
|
|
1832
|
+
{ cmd: 'balance', times: 1, progressMatch: 'balance', progressSlash: 'balance' },
|
|
1833
|
+
{ cmd: 'hunt', times: 2, progressMatch: 'hunt', progressSlash: 'hunt' },
|
|
1834
|
+
{ cmd: 'dig', times: 2, progressMatch: 'dig', progressSlash: 'dig' },
|
|
1835
|
+
],
|
|
1920
1836
|
3: [
|
|
1921
|
-
{ cmd: 'work apply',
|
|
1922
|
-
{ cmd: 'work shift',
|
|
1923
|
-
{ cmd: 'shop sell
|
|
1837
|
+
{ cmd: 'work apply', times: 1, progressMatch: 'get a job', progressSlash: 'work apply' },
|
|
1838
|
+
{ cmd: 'work shift', times: 1, progressMatch: 'work a shift', progressSlash: 'work shift' },
|
|
1839
|
+
{ cmd: 'shop sell item shovel 1',times: 2, progressMatch: 'sell items', progressSlash: 'shop sell' },
|
|
1840
|
+
{ cmd: 'shop view', times: 1, progressMatch: 'buy an item', progressSlash: 'shop view' },
|
|
1841
|
+
],
|
|
1842
|
+
4: [
|
|
1843
|
+
// Keep this command-driven so the quest runner can continue progression.
|
|
1844
|
+
{ cmd: 'bal', times: 1, progressMatch: 'balance', progressSlash: 'balance' },
|
|
1924
1845
|
],
|
|
1925
1846
|
5: [
|
|
1926
1847
|
// "Lose 50,000 coins in /slots" etc — loseTarget means keep playing until cumulative losses hit 50k
|
|
@@ -1928,24 +1849,89 @@ class AccountWorker {
|
|
|
1928
1849
|
{ cmd: 'cointoss', loseTarget: 50000, bet: 10000, times: 50 },
|
|
1929
1850
|
{ cmd: 'snakeeyes', loseTarget: 50000, bet: 10000, times: 50 },
|
|
1930
1851
|
],
|
|
1852
|
+
6: [
|
|
1853
|
+
// Slash text in lock-card maps to regular pls command strings below.
|
|
1854
|
+
{ cmd: 'item new player pack', times: 1, progressMatch: 'new player pack', progressSlash: 'item' },
|
|
1855
|
+
{ cmd: 'use player 1', times: 1, progressMatch: 'new player pack', progressSlash: 'use' },
|
|
1856
|
+
{ cmd: 'use normie 1', times: 1, progressMatch: 'normie box', progressSlash: 'use' },
|
|
1857
|
+
],
|
|
1858
|
+
7: [
|
|
1859
|
+
{ cmd: 'title set newbie', times: 1, progressMatch: 'newbie title', progressSlash: 'title' },
|
|
1860
|
+
{ cmd: 'profile', times: 1, progressMatch: 'profile', progressSlash: 'profile' },
|
|
1861
|
+
{ cmd: 'daily', times: 1, progressMatch: 'daily', progressSlash: 'daily' },
|
|
1862
|
+
{ cmd: 'hl', times: 3, progressMatch: 'highlow', progressSlash: 'highlow' },
|
|
1863
|
+
],
|
|
1931
1864
|
};
|
|
1932
1865
|
|
|
1933
1866
|
_parseLevelQuestProgress(targetLevel, rawText) {
|
|
1934
|
-
if (targetLevel !== 5) return null;
|
|
1935
1867
|
const text = String(rawText || '');
|
|
1936
1868
|
const out = {};
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1869
|
+
|
|
1870
|
+
if (targetLevel === 5) {
|
|
1871
|
+
const re = /(\d[\d,]*)\s*\/\s*50,000[\s\S]{0,180}?<\/(slots|cointoss|snakeeyes):/gi;
|
|
1872
|
+
let m;
|
|
1873
|
+
while ((m = re.exec(text)) !== null) {
|
|
1874
|
+
const cur = parseInt(String(m[1] || '').replace(/,/g, ''), 10);
|
|
1875
|
+
const cmd = String(m[2] || '').toLowerCase();
|
|
1876
|
+
if (Number.isFinite(cur) && cmd) out[cmd] = Math.max(0, cur);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
if (targetLevel === 6) {
|
|
1881
|
+
// handled below by generic times-based parser
|
|
1943
1882
|
}
|
|
1883
|
+
|
|
1884
|
+
if (targetLevel !== 5) {
|
|
1885
|
+
const quests = AccountWorker.LEVEL_QUESTS[targetLevel] || [];
|
|
1886
|
+
const lines = text.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
1887
|
+
for (const q of quests) {
|
|
1888
|
+
if (q.loseTarget) continue;
|
|
1889
|
+
const cmd = String(q.cmd || '').toLowerCase();
|
|
1890
|
+
const cmdParts = cmd.split(/\s+/).filter(Boolean);
|
|
1891
|
+
if (cmdParts.length === 0) continue;
|
|
1892
|
+
|
|
1893
|
+
const slashHints = new Set();
|
|
1894
|
+
slashHints.add(cmdParts[0]);
|
|
1895
|
+
if (q.progressSlash) {
|
|
1896
|
+
for (const p of String(q.progressSlash).toLowerCase().split(/[\s,/|]+/).filter(Boolean)) {
|
|
1897
|
+
slashHints.add(p);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
if (cmd === 'hl') slashHints.add('highlow');
|
|
1901
|
+
if (cmd === 'highlow') slashHints.add('hl');
|
|
1902
|
+
|
|
1903
|
+
const matchPhrase = String(q.progressMatch || q.cmd || '').toLowerCase();
|
|
1904
|
+
const tail = matchPhrase
|
|
1905
|
+
.split(/\s+/)
|
|
1906
|
+
.filter(Boolean)
|
|
1907
|
+
.filter(t => !/^\d+$/.test(t))
|
|
1908
|
+
.filter(t => t !== cmdParts[0]);
|
|
1909
|
+
|
|
1910
|
+
for (const line of lines) {
|
|
1911
|
+
const lower = line.toLowerCase();
|
|
1912
|
+
const hasSlash = Array.from(slashHints).some(h => lower.includes(`/${h}`));
|
|
1913
|
+
if (!hasSlash) continue;
|
|
1914
|
+
|
|
1915
|
+
let ok = true;
|
|
1916
|
+
for (const token of tail) {
|
|
1917
|
+
if (!lower.includes(token)) { ok = false; break; }
|
|
1918
|
+
}
|
|
1919
|
+
if (!ok) continue;
|
|
1920
|
+
|
|
1921
|
+
const m = lower.match(/(\d[\d,]*)\s*\/\s*(\d[\d,]*)/);
|
|
1922
|
+
if (!m) continue;
|
|
1923
|
+
const cur = parseInt(String(m[1] || '').replace(/,/g, ''), 10);
|
|
1924
|
+
if (!Number.isFinite(cur)) continue;
|
|
1925
|
+
out[cmd] = Math.max(out[cmd] || 0, cur);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1944
1930
|
return Object.keys(out).length > 0 ? out : null;
|
|
1945
1931
|
}
|
|
1946
1932
|
|
|
1947
1933
|
_mergeLevelQuestProgress(targetLevel, progressHint) {
|
|
1948
|
-
if (!progressHint
|
|
1934
|
+
if (!progressHint) return;
|
|
1949
1935
|
const prev = this._levelQuestProgressCache.get(targetLevel) || {};
|
|
1950
1936
|
const merged = { ...prev };
|
|
1951
1937
|
for (const [cmd, val] of Object.entries(progressHint)) {
|
|
@@ -1963,13 +1949,31 @@ class AccountWorker {
|
|
|
1963
1949
|
|
|
1964
1950
|
this._mergeLevelQuestProgress(targetLevel, progressHint);
|
|
1965
1951
|
const cached = this._levelQuestProgressCache.get(targetLevel) || {};
|
|
1966
|
-
const
|
|
1952
|
+
const seededAll = quests
|
|
1967
1953
|
.map(q => {
|
|
1968
1954
|
const target = Number(q.loseTarget || 0);
|
|
1969
|
-
const
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1955
|
+
const seededLoss = target > 0 ? Math.min(target, Math.max(0, Number(cached[q.cmd] || 0))) : 0;
|
|
1956
|
+
const seededDone = target === 0 ? Math.max(0, Math.min(Number(q.times || 0), Number(cached[q.cmd] || 0))) : 0;
|
|
1957
|
+
const timesRemaining = target === 0 ? Math.max(0, Number(q.times || 0) - seededDone) : Number(q.times || 0);
|
|
1958
|
+
const defaultCdSec = this._getCommandDefaultCooldownSec(q.cmd);
|
|
1959
|
+
return {
|
|
1960
|
+
...q,
|
|
1961
|
+
level: targetLevel,
|
|
1962
|
+
_lostSoFar: seededLoss,
|
|
1963
|
+
_doneSoFar: seededDone,
|
|
1964
|
+
times: timesRemaining,
|
|
1965
|
+
_defaultCdSec: defaultCdSec,
|
|
1966
|
+
_nextRunAt: 0,
|
|
1967
|
+
};
|
|
1968
|
+
});
|
|
1969
|
+
|
|
1970
|
+
const seededQueue = seededAll
|
|
1971
|
+
.filter(q => {
|
|
1972
|
+
if (q.loseTarget) return q._lostSoFar < q.loseTarget;
|
|
1973
|
+
return q.times > 0;
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
this._initLevelQuestRunState(targetLevel, seededAll);
|
|
1973
1977
|
|
|
1974
1978
|
if (seededQueue.length === 0) {
|
|
1975
1979
|
this.log('info', `[QUEST] Level ${targetLevel} tasks already complete from progress cache — verifying unlock`);
|
|
@@ -1997,6 +2001,71 @@ class AccountWorker {
|
|
|
1997
2001
|
return true;
|
|
1998
2002
|
}
|
|
1999
2003
|
|
|
2004
|
+
_getCommandDefaultCooldownSec(cmdName) {
|
|
2005
|
+
const cmd = String(cmdName || '').toLowerCase().trim();
|
|
2006
|
+
if (!cmd) return 8;
|
|
2007
|
+
const map = AccountWorker.COMMAND_MAP || [];
|
|
2008
|
+
const exact = map.find(r => String(r.cmd || '').toLowerCase() === cmd);
|
|
2009
|
+
if (exact && Number.isFinite(exact.defaultCd) && exact.defaultCd > 0) return exact.defaultCd;
|
|
2010
|
+
if (cmd === 'highlow') {
|
|
2011
|
+
const hl = map.find(r => String(r.cmd || '').toLowerCase() === 'hl');
|
|
2012
|
+
if (hl?.defaultCd) return hl.defaultCd;
|
|
2013
|
+
}
|
|
2014
|
+
const head = cmd.split(/\s+/)[0];
|
|
2015
|
+
const byHead = map.find(r => String(r.cmd || '').toLowerCase() === head);
|
|
2016
|
+
if (byHead && Number.isFinite(byHead.defaultCd) && byHead.defaultCd > 0) return byHead.defaultCd;
|
|
2017
|
+
return 10;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
_initLevelQuestRunState(targetLevel, questRows) {
|
|
2021
|
+
const startedAt = Date.now();
|
|
2022
|
+
const state = {
|
|
2023
|
+
level: targetLevel,
|
|
2024
|
+
startedAt,
|
|
2025
|
+
updatedAt: startedAt,
|
|
2026
|
+
completedAt: 0,
|
|
2027
|
+
entries: new Map(),
|
|
2028
|
+
};
|
|
2029
|
+
|
|
2030
|
+
for (const q of (questRows || [])) {
|
|
2031
|
+
const cmd = String(q.cmd || '').toLowerCase();
|
|
2032
|
+
if (!cmd) continue;
|
|
2033
|
+
const required = q.loseTarget
|
|
2034
|
+
? Number(q.loseTarget || 0)
|
|
2035
|
+
: Math.max(0, Number(q._doneSoFar || 0) + Number(q.times || 0));
|
|
2036
|
+
const completed = q.loseTarget
|
|
2037
|
+
? Math.max(0, Number(q._lostSoFar || 0))
|
|
2038
|
+
: Math.max(0, Number(q._doneSoFar || 0));
|
|
2039
|
+
const remaining = q.loseTarget
|
|
2040
|
+
? Math.max(0, required - completed)
|
|
2041
|
+
: Math.max(0, Number(q.times || 0));
|
|
2042
|
+
|
|
2043
|
+
state.entries.set(cmd, {
|
|
2044
|
+
cmd,
|
|
2045
|
+
required,
|
|
2046
|
+
completed,
|
|
2047
|
+
remaining,
|
|
2048
|
+
attemptsLeft: Number(q.times || 0),
|
|
2049
|
+
runs: 0,
|
|
2050
|
+
success: 0,
|
|
2051
|
+
failed: 0,
|
|
2052
|
+
nonPlay: 0,
|
|
2053
|
+
lastResult: '',
|
|
2054
|
+
lastRunAt: 0,
|
|
2055
|
+
nextRunAt: Number(q._nextRunAt || 0),
|
|
2056
|
+
status: remaining <= 0 ? 'done' : 'pending',
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
this._levelQuestRunMap.set(targetLevel, state);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
_getLevelQuestRunEntry(level, cmdName) {
|
|
2064
|
+
const st = this._levelQuestRunMap.get(level);
|
|
2065
|
+
if (!st?.entries) return null;
|
|
2066
|
+
return st.entries.get(String(cmdName || '').toLowerCase()) || null;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2000
2069
|
async buildCommandQueue() {
|
|
2001
2070
|
const heap = new MinHeap();
|
|
2002
2071
|
const now = Date.now();
|
|
@@ -2259,6 +2328,11 @@ class AccountWorker {
|
|
|
2259
2328
|
this.log('success', `[QUEST] Level ${targetLv} verified ✓ — level unlocked!`);
|
|
2260
2329
|
this._level = currentLv;
|
|
2261
2330
|
this._levelQuestProgressCache.delete(targetLv);
|
|
2331
|
+
const st = this._levelQuestRunMap.get(targetLv);
|
|
2332
|
+
if (st) {
|
|
2333
|
+
st.updatedAt = Date.now();
|
|
2334
|
+
st.verifiedAt = Date.now();
|
|
2335
|
+
}
|
|
2262
2336
|
} else {
|
|
2263
2337
|
this.log('warn', `[QUEST] Level ${targetLv} NOT verified — still at ${currentLv}. Re-triggering quests.`);
|
|
2264
2338
|
// Re-trigger quests for this level
|
|
@@ -2279,7 +2353,22 @@ class AccountWorker {
|
|
|
2279
2353
|
|
|
2280
2354
|
// BLOCK: quest mode active — run quests only, no normal grinding
|
|
2281
2355
|
if (this._levelQuestActive && this._levelQuestQueue.length > 0) {
|
|
2356
|
+
const now = Date.now();
|
|
2357
|
+
let dueIdx = this._levelQuestQueue.findIndex(q => !q._nextRunAt || q._nextRunAt <= now);
|
|
2358
|
+
if (dueIdx < 0) {
|
|
2359
|
+
const soonest = Math.min(...this._levelQuestQueue.map(q => Number(q._nextRunAt || now + 5000)));
|
|
2360
|
+
const waitMs = Math.max(1200, Math.min(60000, soonest - now));
|
|
2361
|
+
this.setStatus(`[QUEST] cooldown wait ${Math.ceil(waitMs / 1000)}s`);
|
|
2362
|
+
this.tickTimeout = setTimeout(() => this.tick(), waitMs);
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
if (dueIdx > 0) {
|
|
2366
|
+
const [dueQuest] = this._levelQuestQueue.splice(dueIdx, 1);
|
|
2367
|
+
this._levelQuestQueue.unshift(dueQuest);
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2282
2370
|
const quest = this._levelQuestQueue[0];
|
|
2371
|
+
let nextQuestDelayMs = 1200;
|
|
2283
2372
|
if (quest.loseTarget) {
|
|
2284
2373
|
const remaining = Math.max(0, quest.loseTarget - (quest._lostSoFar || 0));
|
|
2285
2374
|
const floorBet = quest._minBet || quest.bet || 1;
|
|
@@ -2306,6 +2395,12 @@ class AccountWorker {
|
|
|
2306
2395
|
this.busy = true;
|
|
2307
2396
|
await this.runCommand(quest.cmd, prefix);
|
|
2308
2397
|
const questMeta = this._lastCommandMeta || {};
|
|
2398
|
+
const qEntry = this._getLevelQuestRunEntry(quest.level, quest.cmd);
|
|
2399
|
+
if (qEntry) {
|
|
2400
|
+
qEntry.runs++;
|
|
2401
|
+
qEntry.lastRunAt = Date.now();
|
|
2402
|
+
qEntry.lastResult = String(questMeta.result || '');
|
|
2403
|
+
}
|
|
2309
2404
|
this._commandRunning = false;
|
|
2310
2405
|
this.busy = false;
|
|
2311
2406
|
this._questBetOverride = null;
|
|
@@ -2328,6 +2423,10 @@ class AccountWorker {
|
|
|
2328
2423
|
if (!nonPlay) {
|
|
2329
2424
|
quest._lostSoFar = (quest._lostSoFar || 0) + lostThisRound;
|
|
2330
2425
|
this._mergeLevelQuestProgress(quest.level, { [quest.cmd]: quest._lostSoFar });
|
|
2426
|
+
if (qEntry) qEntry.success++;
|
|
2427
|
+
} else if (qEntry) {
|
|
2428
|
+
qEntry.nonPlay++;
|
|
2429
|
+
qEntry.failed++;
|
|
2331
2430
|
}
|
|
2332
2431
|
|
|
2333
2432
|
this.log('info', `[QUEST] ${quest.cmd} — lost ⏣${quest._lostSoFar.toLocaleString()} / ⏣${quest.loseTarget.toLocaleString()}`);
|
|
@@ -2346,10 +2445,57 @@ class AccountWorker {
|
|
|
2346
2445
|
questStepDone = true;
|
|
2347
2446
|
this.log('info', `[QUEST] ${quest.cmd} max attempts reached — moving on`);
|
|
2348
2447
|
}
|
|
2448
|
+
|
|
2449
|
+
if (qEntry) {
|
|
2450
|
+
qEntry.completed = Math.max(0, Number(quest._lostSoFar || 0));
|
|
2451
|
+
qEntry.remaining = Math.max(0, Number(quest.loseTarget || 0) - qEntry.completed);
|
|
2452
|
+
qEntry.attemptsLeft = Number(quest.times || 0);
|
|
2453
|
+
qEntry.status = questStepDone ? 'done' : (nonPlay ? 'cooldown' : 'progress');
|
|
2454
|
+
}
|
|
2349
2455
|
} else {
|
|
2350
2456
|
// Normal times-based quest
|
|
2351
|
-
|
|
2457
|
+
const nonPlay = !!questMeta.nonPlay;
|
|
2458
|
+
if (!nonPlay) {
|
|
2459
|
+
quest.times--;
|
|
2460
|
+
quest._doneSoFar = (quest._doneSoFar || 0) + 1;
|
|
2461
|
+
this._mergeLevelQuestProgress(quest.level, { [quest.cmd]: quest._doneSoFar });
|
|
2462
|
+
if (qEntry) qEntry.success++;
|
|
2463
|
+
} else {
|
|
2464
|
+
if (qEntry) {
|
|
2465
|
+
qEntry.nonPlay++;
|
|
2466
|
+
qEntry.failed++;
|
|
2467
|
+
}
|
|
2468
|
+
this.log('warn', `[QUEST] ${quest.cmd} non-play response — progress unchanged`);
|
|
2469
|
+
// Rotate to next quest so we don't spam one command during cooldown.
|
|
2470
|
+
if (this._levelQuestQueue.length > 1) {
|
|
2471
|
+
const rotated = this._levelQuestQueue.shift();
|
|
2472
|
+
this._levelQuestQueue.push(rotated);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2352
2475
|
if (quest.times <= 0) questStepDone = true;
|
|
2476
|
+
|
|
2477
|
+
if (qEntry) {
|
|
2478
|
+
qEntry.completed = Math.max(0, Number(quest._doneSoFar || 0));
|
|
2479
|
+
qEntry.remaining = Math.max(0, Number(quest.times || 0));
|
|
2480
|
+
qEntry.attemptsLeft = Number(quest.times || 0);
|
|
2481
|
+
qEntry.status = questStepDone ? 'done' : (nonPlay ? 'cooldown' : 'progress');
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
const retrySec = Math.max(
|
|
2486
|
+
1,
|
|
2487
|
+
Number(questMeta.nextRetrySec || 0),
|
|
2488
|
+
Number(quest._defaultCdSec || 0)
|
|
2489
|
+
);
|
|
2490
|
+
quest._nextRunAt = Date.now() + retrySec * 1000;
|
|
2491
|
+
nextQuestDelayMs = Math.max(nextQuestDelayMs, Math.min(60000, Math.floor(retrySec * 1000)));
|
|
2492
|
+
if (qEntry) qEntry.nextRunAt = Number(quest._nextRunAt || 0);
|
|
2493
|
+
|
|
2494
|
+
const runState = this._levelQuestRunMap.get(quest.level);
|
|
2495
|
+
if (runState) runState.updatedAt = Date.now();
|
|
2496
|
+
|
|
2497
|
+
if (questMeta.nextRetrySec && Number.isFinite(questMeta.nextRetrySec) && questMeta.nextRetrySec > 0) {
|
|
2498
|
+
nextQuestDelayMs = Math.max(nextQuestDelayMs, Math.min(60000, Math.floor(questMeta.nextRetrySec * 1000)));
|
|
2353
2499
|
}
|
|
2354
2500
|
|
|
2355
2501
|
if (questStepDone) this._levelQuestQueue.shift();
|
|
@@ -2358,6 +2504,11 @@ class AccountWorker {
|
|
|
2358
2504
|
this._levelQuestActive = false;
|
|
2359
2505
|
this._levelQuestDone.add(quest.level);
|
|
2360
2506
|
const justCompletedLevel = quest.level;
|
|
2507
|
+
const st = this._levelQuestRunMap.get(justCompletedLevel);
|
|
2508
|
+
if (st) {
|
|
2509
|
+
st.completedAt = Date.now();
|
|
2510
|
+
st.updatedAt = st.completedAt;
|
|
2511
|
+
}
|
|
2361
2512
|
this.log('success', `[QUEST] Level ${justCompletedLevel} quests DONE — verifying unlock...`);
|
|
2362
2513
|
// Null out commandQueue so buildCommandQueue picks only unlocked commands
|
|
2363
2514
|
this.commandQueue = null;
|
|
@@ -2367,7 +2518,7 @@ class AccountWorker {
|
|
|
2367
2518
|
this.tickTimeout = setTimeout(() => this.tick(), 3000);
|
|
2368
2519
|
return;
|
|
2369
2520
|
}
|
|
2370
|
-
this.tickTimeout = setTimeout(() => this.tick(),
|
|
2521
|
+
this.tickTimeout = setTimeout(() => this.tick(), nextQuestDelayMs);
|
|
2371
2522
|
return;
|
|
2372
2523
|
}
|
|
2373
2524
|
|