dankgrinder 8.109.0 → 8.110.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 +27 -9
- package/lib/grinder.js +80 -0
- package/lib/rawLogger.js +23 -0
- package/package.json +1 -1
|
@@ -108,14 +108,31 @@ const SAFE_KEYWORDS = Object.freeze(['flee', 'run', 'hide', 'avoid', 'ignore', '
|
|
|
108
108
|
const RISKY_KEYWORDS = Object.freeze(['reach', 'grab', 'fight', 'attack', 'steal', 'open', 'touch', 'eat', 'drink']);
|
|
109
109
|
const ADVENTURE_PREFERRED_TYPES = Object.freeze(['space', 'out west']);
|
|
110
110
|
|
|
111
|
-
function pickSafeChoice(choices) {
|
|
111
|
+
function pickSafeChoice(choices, adventureAnswers) {
|
|
112
112
|
if (choices.length === 0) return null;
|
|
113
113
|
if (choices.length === 1) return choices[0];
|
|
114
114
|
|
|
115
|
-
// Check labels for safe/risky keywords
|
|
116
115
|
const labels = choices.map(b => (b.label || '').toLowerCase());
|
|
117
116
|
|
|
118
|
-
//
|
|
117
|
+
// ── 1) Config answer maps (dmg pattern — highest priority) ──
|
|
118
|
+
if (adventureAnswers) {
|
|
119
|
+
for (const [question, answers] of Object.entries(adventureAnswers)) {
|
|
120
|
+
const matched = labels.find(l => l.includes(question));
|
|
121
|
+
if (matched && Array.isArray(answers)) {
|
|
122
|
+
for (const answer of answers) {
|
|
123
|
+
const found = choices.find(c =>
|
|
124
|
+
(c.label || '').toLowerCase().includes(answer.toLowerCase())
|
|
125
|
+
);
|
|
126
|
+
if (found) {
|
|
127
|
+
LOG.info(`[adventure] Config answer: "${found.label}" matched question "${question}"`);
|
|
128
|
+
return found;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── 2) Hardcoded safe keywords ──
|
|
119
136
|
for (let i = 0; i < labels.length; i++) {
|
|
120
137
|
for (let k = 0; k < SAFE_KEYWORDS.length; k++) {
|
|
121
138
|
const kw = SAFE_KEYWORDS[k];
|
|
@@ -126,7 +143,7 @@ function pickSafeChoice(choices) {
|
|
|
126
143
|
}
|
|
127
144
|
}
|
|
128
145
|
|
|
129
|
-
// Avoid risky keywords
|
|
146
|
+
// ── 3) Avoid risky keywords ──
|
|
130
147
|
const nonRisky = choices.filter((b, i) => {
|
|
131
148
|
return !RISKY_KEYWORDS.some(kw => labels[i].includes(kw));
|
|
132
149
|
});
|
|
@@ -143,7 +160,7 @@ function pickSafeChoice(choices) {
|
|
|
143
160
|
}
|
|
144
161
|
|
|
145
162
|
// ── Play through all adventure rounds ────────────────────────────
|
|
146
|
-
async function playAdventureRounds(channel, msg) {
|
|
163
|
+
async function playAdventureRounds(channel, msg, adventureAnswers) {
|
|
147
164
|
let current = msg;
|
|
148
165
|
let interactions = 0;
|
|
149
166
|
const MAX_INTERACTIONS = 30;
|
|
@@ -171,7 +188,7 @@ async function playAdventureRounds(channel, msg) {
|
|
|
171
188
|
// ── If there are choices, pick one first ─────────────────
|
|
172
189
|
if (choices.length > 0) {
|
|
173
190
|
LOG.info(`[adventure] ${choices.length} choices: [${choices.map(b => `"${b.label}"`).join(', ')}]`);
|
|
174
|
-
const choice = pickSafeChoice(choices);
|
|
191
|
+
const choice = pickSafeChoice(choices, adventureAnswers);
|
|
175
192
|
if (choice) {
|
|
176
193
|
LOG.info(`[adventure] → Choosing: "${choice.label}"`);
|
|
177
194
|
await sleep(50);
|
|
@@ -258,9 +275,10 @@ async function playAdventureRounds(channel, msg) {
|
|
|
258
275
|
* @param {object} opts.channel - Discord channel
|
|
259
276
|
* @param {function} opts.waitForDankMemer - Waits for Dank Memer response
|
|
260
277
|
* @param {object} [opts.client] - Discord client (for modal handling in shop)
|
|
278
|
+
* @param {object} [opts.adventureAnswers] - Per-type answer map from AccountWorker.ADVENTURE_ANSWERS
|
|
261
279
|
* @returns {Promise<{result: string, coins: number, nextCooldownSec: number|null}>}
|
|
262
280
|
*/
|
|
263
|
-
async function runAdventure({ channel, waitForDankMemer, client }) {
|
|
281
|
+
async function runAdventure({ channel, waitForDankMemer, client, adventureAnswers }) {
|
|
264
282
|
LOG.cmd(`${c.white}${c.bold}pls adventure${c.reset}`);
|
|
265
283
|
|
|
266
284
|
// Step 1: Send the command
|
|
@@ -334,7 +352,7 @@ async function runAdventure({ channel, waitForDankMemer, client }) {
|
|
|
334
352
|
if (hasNextBtn && menus.length === 0) {
|
|
335
353
|
// Already mid-adventure from a previous run — jump straight to rounds
|
|
336
354
|
LOG.info('[adventure] Resuming mid-adventure...');
|
|
337
|
-
const { text: finalText, coins, interactions, rewards, finalMsg } = await playAdventureRounds(channel, response);
|
|
355
|
+
const { text: finalText, coins, interactions, rewards, finalMsg } = await playAdventureRounds(channel, response, adventureAnswers);
|
|
338
356
|
return buildResult(finalText, coins, interactions, rewards, finalMsg);
|
|
339
357
|
}
|
|
340
358
|
|
|
@@ -434,7 +452,7 @@ async function runAdventure({ channel, waitForDankMemer, client }) {
|
|
|
434
452
|
}
|
|
435
453
|
|
|
436
454
|
// ── Play through all adventure rounds ──────────────────────
|
|
437
|
-
const { text: finalText, coins, interactions, rewards, finalMsg } = await playAdventureRounds(channel, response);
|
|
455
|
+
const { text: finalText, coins, interactions, rewards, finalMsg } = await playAdventureRounds(channel, response, adventureAnswers);
|
|
438
456
|
return buildResult(finalText, coins, interactions, rewards, finalMsg);
|
|
439
457
|
}
|
|
440
458
|
|
package/lib/grinder.js
CHANGED
|
@@ -598,6 +598,37 @@ class MinHeap {
|
|
|
598
598
|
|
|
599
599
|
get size() { return this.heap.length; }
|
|
600
600
|
|
|
601
|
+
// ── Message handler registration (dmg pattern) ──────────────
|
|
602
|
+
// Allows plugins/commands to register handlers for gateway events.
|
|
603
|
+
// handlers: { messageCreate: Map<name, fn>, messageUpdate: Map<name, fn> }
|
|
604
|
+
// ── Dispatch events to registered handlers (dmg pattern) ──
|
|
605
|
+
_dispatchToHandlers(event, msg) {
|
|
606
|
+
const handlers = this._handlers[event];
|
|
607
|
+
if (!handlers || handlers.size === 0) return;
|
|
608
|
+
for (const [name, fn] of handlers) {
|
|
609
|
+
try {
|
|
610
|
+
const result = fn(msg);
|
|
611
|
+
if (result && typeof result.then === 'function') {
|
|
612
|
+
result.catch(e => this.log('error', `[handler:${name}] ${e.message}`));
|
|
613
|
+
}
|
|
614
|
+
} catch (e) {
|
|
615
|
+
this.log('error', `[handler:${name}] ${e.message}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
_registerHandler(event, name, fn) {
|
|
621
|
+
if (this._handlers[event]) {
|
|
622
|
+
this._handlers[event].set(name, fn);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
_unregisterHandler(event, name) {
|
|
627
|
+
if (this._handlers[event]) {
|
|
628
|
+
this._handlers[event].delete(name);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
601
632
|
_bubbleUp(i) {
|
|
602
633
|
while (i > 0) {
|
|
603
634
|
const parent = (i - 1) >> 1;
|
|
@@ -677,6 +708,14 @@ class AccountWorker {
|
|
|
677
708
|
this._commandRunning = false; // prevents grinding commands from overlapping with quest commands
|
|
678
709
|
this._verifyLevelUnlock = null; // holds level to verify after quest completion
|
|
679
710
|
this.commandQueue = null;
|
|
711
|
+
|
|
712
|
+
// ── Message handler routing (dmg pattern) ─────────────────
|
|
713
|
+
// Map of event → handler functions. Plugins register here.
|
|
714
|
+
// Usage: _handlers.messageCreate.get('adventure')?.(msg)
|
|
715
|
+
this._handlers = {
|
|
716
|
+
messageCreate: new Map(),
|
|
717
|
+
messageUpdate: new Map(),
|
|
718
|
+
};
|
|
680
719
|
this.lastHealthCheck = Date.now();
|
|
681
720
|
this.doneToday = new Map();
|
|
682
721
|
|
|
@@ -762,6 +801,8 @@ class AccountWorker {
|
|
|
762
801
|
}
|
|
763
802
|
function handler(msg) {
|
|
764
803
|
if (msg.author.id === DANK_MEMER_ID && msg.channel.id === self.channel.id) {
|
|
804
|
+
// ── Dispatch to registered handlers (dmg pattern) ────
|
|
805
|
+
self._dispatchToHandlers('messageCreate', msg);
|
|
765
806
|
const hasComponentPayload = Array.isArray(msg.components)
|
|
766
807
|
&& msg.components.some(c => c && (c.components || c.content || c.type || c.label || c.customId));
|
|
767
808
|
const hasContent = (msg.content && msg.content.length > 0)
|
|
@@ -791,6 +832,8 @@ class AccountWorker {
|
|
|
791
832
|
// Reject edits to messages created well before our command was sent
|
|
792
833
|
const msgTs = newMsg.createdTimestamp || newMsg.createdAt?.getTime?.() || 0;
|
|
793
834
|
if (msgTs > 0 && msgTs < sentAt - 1500) return;
|
|
835
|
+
// ── Dispatch to registered handlers ─────────────────
|
|
836
|
+
self._dispatchToHandlers('messageUpdate', newMsg);
|
|
794
837
|
cleanup();
|
|
795
838
|
resolve(newMsg);
|
|
796
839
|
}
|
|
@@ -1356,6 +1399,7 @@ class AccountWorker {
|
|
|
1356
1399
|
client: this.client,
|
|
1357
1400
|
safeAnswers: cmdName === 'search' ? safeParseJSON(this.account.search_answers, []) :
|
|
1358
1401
|
cmdName === 'crime' ? safeParseJSON(this.account.crime_answers, []) : [],
|
|
1402
|
+
adventureAnswers: AccountWorker.ADVENTURE_ANSWERS,
|
|
1359
1403
|
betAmount: this._questBetOverride || (['blackjack'].includes(cmdName) ? bjBet : gambBet),
|
|
1360
1404
|
accountId: this.account.id,
|
|
1361
1405
|
redis,
|
|
@@ -1821,6 +1865,42 @@ class AccountWorker {
|
|
|
1821
1865
|
].map(Object.freeze);
|
|
1822
1866
|
|
|
1823
1867
|
// ── Level Quest System — blocks grinding until quests complete ──
|
|
1868
|
+
// ── Adventure answer maps (per type) ──────────────────────────
|
|
1869
|
+
// Pattern: question text → answer choice to click
|
|
1870
|
+
// Supports: space, out west, brazil, vacation, halloween, museum
|
|
1871
|
+
static ADVENTURE_ANSWERS = {
|
|
1872
|
+
space: {
|
|
1873
|
+
'what do you want to do': ['search for debris', 'scavenge wreckage'],
|
|
1874
|
+
'how do you respond': ['stay calm', 'analyze situation'],
|
|
1875
|
+
'what do you choose': ['try to communicate', 'observe first'],
|
|
1876
|
+
},
|
|
1877
|
+
west: {
|
|
1878
|
+
'what do you do': ['draw first', 'hide behind cover'],
|
|
1879
|
+
'how do you respond': ['draw', 'shoot'],
|
|
1880
|
+
'sheriff wants': ['accept bounty', 'decline'],
|
|
1881
|
+
},
|
|
1882
|
+
brazil: {
|
|
1883
|
+
'what do you want to do': ['search for treasure', 'ask locals'],
|
|
1884
|
+
'how do you respond': ['negotiate', 'accept offer'],
|
|
1885
|
+
'locals offer': ['take the deal', 'refuse'],
|
|
1886
|
+
},
|
|
1887
|
+
vacation: {
|
|
1888
|
+
'stranger offers': ['accept', 'decline'],
|
|
1889
|
+
'what do you do': ['explore', 'rest'],
|
|
1890
|
+
'something suspicious': ['investigate', 'ignore'],
|
|
1891
|
+
},
|
|
1892
|
+
halloween: {
|
|
1893
|
+
'knock at door': ['open it', 'hide'],
|
|
1894
|
+
'what do you do': ['investigate', 'run away'],
|
|
1895
|
+
'ghost appears': ['speak to it', 'hide'],
|
|
1896
|
+
},
|
|
1897
|
+
museum: {
|
|
1898
|
+
'what do you do': ['touch exhibit', 'keep distance'],
|
|
1899
|
+
'alarm sounds': ['hide', 'run'],
|
|
1900
|
+
'guard asks': ['tell truth', 'lie'],
|
|
1901
|
+
},
|
|
1902
|
+
};
|
|
1903
|
+
|
|
1824
1904
|
static LEVEL_QUESTS = {
|
|
1825
1905
|
1: [
|
|
1826
1906
|
{ cmd: 'beg', times: 2 },
|
package/lib/rawLogger.js
CHANGED
|
@@ -457,6 +457,20 @@ async function store(d, event) {
|
|
|
457
457
|
pipe.ltrim(accKey, 0, 4999);
|
|
458
458
|
pipe.expire(accKey, LOG_TTL);
|
|
459
459
|
}
|
|
460
|
+
// Track ephemeral in memory for quick access before they vanish
|
|
461
|
+
if (parsed.isEphemeral || (d.flags & 32832)) {
|
|
462
|
+
let chMap = ephemeralByChannel.get(d.channel_id);
|
|
463
|
+
if (!chMap) {
|
|
464
|
+
chMap = new Map();
|
|
465
|
+
ephemeralByChannel.set(d.channel_id, chMap);
|
|
466
|
+
}
|
|
467
|
+
chMap.set(d.id, parsed);
|
|
468
|
+
// Cap per-channel ephemeral buffer at 50
|
|
469
|
+
if (chMap.size > 50) {
|
|
470
|
+
const firstKey = chMap.keys().next().value;
|
|
471
|
+
chMap.delete(firstKey);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
460
474
|
// Ephemeral log
|
|
461
475
|
if (parsed.isEphemeral || (d.flags & 32832)) {
|
|
462
476
|
pipe.lpush('raw:ephemeral:log', `${d.id}:${parsed._capturedAt}:${parsed.command}:${d.channel_id}`);
|
|
@@ -494,8 +508,17 @@ function onDmEvent(fn) { dmListeners.push(fn); }
|
|
|
494
508
|
|
|
495
509
|
// ── Ephemeral message callbacks ──
|
|
496
510
|
const ephemeralListeners = [];
|
|
511
|
+
const ephemeralByChannel = new Map(); // channelId → Map<msgId, parsed msg>
|
|
512
|
+
|
|
497
513
|
function onNextEphemeral(fn) { ephemeralListeners.push(fn); }
|
|
498
514
|
|
|
515
|
+
// Track ephemeral messages per channel — useful for reading state before vanish
|
|
516
|
+
function getEphemeralMsgs(channelId, count = 20) {
|
|
517
|
+
const map = ephemeralByChannel.get(channelId);
|
|
518
|
+
if (!map) return [];
|
|
519
|
+
return [...map.values()].slice(-count);
|
|
520
|
+
}
|
|
521
|
+
|
|
499
522
|
function _notifyEphemeral(parsed) {
|
|
500
523
|
if (!ephemeralListeners.length) return;
|
|
501
524
|
const listeners = [...ephemeralListeners];
|