dankgrinder 3.0.0 → 4.1.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/README.md +25 -16
- package/bin/dankgrinder.js +18 -28
- package/lib/commands/adventure.js +502 -0
- package/lib/commands/beg.js +45 -0
- package/lib/commands/blackjack.js +85 -0
- package/lib/commands/crime.js +94 -0
- package/lib/commands/deposit.js +46 -0
- package/lib/commands/dig.js +82 -0
- package/lib/commands/fish.js +615 -0
- package/lib/commands/fishVision.js +141 -0
- package/lib/commands/gamble.js +96 -0
- package/lib/commands/generic.js +181 -0
- package/lib/commands/highlow.js +112 -0
- package/lib/commands/hunt.js +85 -0
- package/lib/commands/index.js +59 -0
- package/lib/commands/postmemes.js +148 -0
- package/lib/commands/profile.js +99 -0
- package/lib/commands/scratch.js +83 -0
- package/lib/commands/search.js +102 -0
- package/lib/commands/shop.js +262 -0
- package/lib/commands/trivia.js +146 -0
- package/lib/commands/utils.js +287 -0
- package/lib/commands/work.js +400 -0
- package/lib/grinder.js +916 -358
- package/package.json +5 -3
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trivia command handler.
|
|
3
|
+
* Send "pls trivia", look up answer in trivia DB, click correct answer.
|
|
4
|
+
* If no match found, pick random (25% chance).
|
|
5
|
+
*
|
|
6
|
+
* Trivia DB: Redis-backed persistent store with in-memory fallback.
|
|
7
|
+
* Key format: "trivia:<first 80 chars of question, lowercased, trimmed>"
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
12
|
+
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
13
|
+
} = require('./utils');
|
|
14
|
+
|
|
15
|
+
const triviaDB = {};
|
|
16
|
+
|
|
17
|
+
function makeQuestionKey(question) {
|
|
18
|
+
return question.toLowerCase().trim().substring(0, 80);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function lookupAnswer(question, buttons, redis) {
|
|
22
|
+
const qKey = makeQuestionKey(question);
|
|
23
|
+
let answer = null;
|
|
24
|
+
|
|
25
|
+
if (redis) {
|
|
26
|
+
try {
|
|
27
|
+
answer = await redis.get('trivia:' + qKey);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
LOG.debug(`[trivia] Redis lookup failed: ${e.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!answer) {
|
|
34
|
+
answer = triviaDB[qKey];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (answer) {
|
|
38
|
+
const btn = buttons.find(b => (b.label || '').toLowerCase() === answer.toLowerCase());
|
|
39
|
+
if (btn && !btn.disabled) {
|
|
40
|
+
LOG.debug(`[trivia] DB hit: "${qKey.substring(0, 40)}..." → "${answer}"`);
|
|
41
|
+
return btn;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const [storedKey, storedAnswer] of Object.entries(triviaDB)) {
|
|
46
|
+
if (qKey.includes(storedKey) || storedKey.includes(qKey)) {
|
|
47
|
+
const btn = buttons.find(b => (b.label || '').toLowerCase() === storedAnswer.toLowerCase());
|
|
48
|
+
if (btn && !btn.disabled) {
|
|
49
|
+
LOG.debug(`[trivia] DB partial hit: "${storedKey.substring(0, 40)}..." → "${storedAnswer}"`);
|
|
50
|
+
return btn;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function learnFromResult(question, correctAnswer, redis) {
|
|
59
|
+
if (!question || !correctAnswer) return;
|
|
60
|
+
const key = makeQuestionKey(question);
|
|
61
|
+
|
|
62
|
+
triviaDB[key] = correctAnswer;
|
|
63
|
+
|
|
64
|
+
if (redis) {
|
|
65
|
+
try {
|
|
66
|
+
await redis.set('trivia:' + key, correctAnswer);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
LOG.debug(`[trivia] Redis write failed: ${e.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
LOG.debug(`[trivia] Learned: "${key.substring(0, 40)}..." → "${correctAnswer}"`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function runTrivia({ channel, waitForDankMemer, redis }) {
|
|
76
|
+
LOG.cmd(`${c.white}${c.bold}pls trivia${c.reset}`);
|
|
77
|
+
|
|
78
|
+
await channel.send('pls trivia');
|
|
79
|
+
const response = await waitForDankMemer(10000);
|
|
80
|
+
|
|
81
|
+
if (!response) {
|
|
82
|
+
LOG.warn('[trivia] No response');
|
|
83
|
+
return { result: 'no response', coins: 0 };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isHoldTight(response)) {
|
|
87
|
+
const reason = getHoldTightReason(response);
|
|
88
|
+
LOG.warn(`[trivia] Hold Tight${reason ? ` (reason: /${reason})` : ''} — waiting 30s`);
|
|
89
|
+
await sleep(30000);
|
|
90
|
+
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
logMsg(response, 'trivia');
|
|
94
|
+
const buttons = getAllButtons(response);
|
|
95
|
+
|
|
96
|
+
if (buttons.length === 0) {
|
|
97
|
+
const text = getFullText(response);
|
|
98
|
+
return { result: text.substring(0, 60), coins: 0 };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const clickable = buttons.filter(b => !b.disabled);
|
|
102
|
+
if (clickable.length === 0) return { result: 'no clickable buttons', coins: 0 };
|
|
103
|
+
|
|
104
|
+
const question = (response.embeds?.[0]?.description || '').replace(/\*\*/g, '').replace(/\*.*?\*/g, '').trim();
|
|
105
|
+
|
|
106
|
+
let btn = await lookupAnswer(question, clickable, redis || null);
|
|
107
|
+
const fromDB = !!btn;
|
|
108
|
+
if (!btn) {
|
|
109
|
+
btn = clickable[Math.floor(Math.random() * clickable.length)];
|
|
110
|
+
}
|
|
111
|
+
LOG.info(`[trivia] ${fromDB ? '(DB)' : '(random)'} Picking: "${btn.label}"`);
|
|
112
|
+
|
|
113
|
+
await humanDelay();
|
|
114
|
+
try {
|
|
115
|
+
const followUp = await safeClickButton(response, btn);
|
|
116
|
+
if (followUp) {
|
|
117
|
+
logMsg(followUp, 'trivia-result');
|
|
118
|
+
const text = getFullText(followUp);
|
|
119
|
+
const coins = parseCoins(text);
|
|
120
|
+
|
|
121
|
+
const lower = text.toLowerCase();
|
|
122
|
+
const correctMatch = text.match(/correct answer was \*\*(.+?)\*\*/i);
|
|
123
|
+
if (correctMatch) {
|
|
124
|
+
await learnFromResult(question, correctMatch[1], redis || null);
|
|
125
|
+
} else if (lower.includes('correct') || lower.includes('nice') || lower.includes('right')) {
|
|
126
|
+
await learnFromResult(question, btn.label, redis || null);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (coins > 0) {
|
|
130
|
+
LOG.coin(`[trivia] ${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
131
|
+
return { result: `trivia → ${btn.label} → +⏣ ${coins.toLocaleString()}`, coins };
|
|
132
|
+
}
|
|
133
|
+
if (lower.includes('correct answer was') || lower.includes('nitwit') || lower.includes('wrong') || lower.includes('incorrect')) {
|
|
134
|
+
return { result: `trivia → ${btn.label} → ${c.red}wrong${c.reset}`, coins: 0 };
|
|
135
|
+
}
|
|
136
|
+
if (lower.includes('correct') || lower.includes('nice') || lower.includes('right')) {
|
|
137
|
+
return { result: `trivia → ${btn.label} → ${c.green}correct${c.reset}`, coins: 0 };
|
|
138
|
+
}
|
|
139
|
+
return { result: `trivia → ${btn.label}`, coins: 0 };
|
|
140
|
+
}
|
|
141
|
+
} catch (e) { LOG.error(`[trivia] Click error: ${e.message}`); }
|
|
142
|
+
|
|
143
|
+
return { result: 'trivia done', coins: 0 };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = { runTrivia, triviaDB };
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for all Dank Memer command handlers.
|
|
3
|
+
* Every command file imports from here instead of duplicating helpers.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DANK_MEMER_ID = '270904126974590976';
|
|
7
|
+
|
|
8
|
+
// ── Terminal Colors ──────────────────────────────────────────
|
|
9
|
+
const c = {
|
|
10
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
|
|
11
|
+
green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
12
|
+
magenta: '\x1b[35m', white: '\x1b[37m', blue: '\x1b[34m',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// ── Logging ──────────────────────────────────────────────────
|
|
16
|
+
function log(label, msg) {
|
|
17
|
+
const time = new Date().toLocaleTimeString('en-US', { hour12: true, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
18
|
+
console.log(` ${c.dim}${time}${c.reset} ${label} ${msg}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const LOG = {
|
|
22
|
+
info: (msg) => log(`${c.cyan}│${c.reset}`, msg),
|
|
23
|
+
success: (msg) => log(`${c.green}✓${c.reset}`, msg),
|
|
24
|
+
error: (msg) => log(`${c.red}✗${c.reset}`, msg),
|
|
25
|
+
warn: (msg) => log(`${c.yellow}!${c.reset}`, msg),
|
|
26
|
+
cmd: (msg) => log(`${c.magenta}▸${c.reset}`, msg),
|
|
27
|
+
coin: (msg) => log(`${c.yellow}$${c.reset}`, msg),
|
|
28
|
+
buy: (msg) => log(`${c.blue}♦${c.reset}`, msg),
|
|
29
|
+
debug: (msg) => log(`${c.dim}⊙${c.reset}`, msg),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// ── Sleep ────────────────────────────────────────────────────
|
|
33
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
34
|
+
|
|
35
|
+
function humanDelay(min = 100, max = 350) {
|
|
36
|
+
return new Promise(r => setTimeout(r, min + Math.random() * (max - min)));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Message Parsing ──────────────────────────────────────────
|
|
40
|
+
function getFullText(msg) {
|
|
41
|
+
if (!msg) return '';
|
|
42
|
+
let text = msg.content || '';
|
|
43
|
+
for (const embed of msg.embeds || []) {
|
|
44
|
+
if (embed.title) text += ' ' + embed.title;
|
|
45
|
+
if (embed.description) text += ' ' + embed.description;
|
|
46
|
+
for (const f of embed.fields || []) text += ' ' + (f.name || '') + ' ' + (f.value || '');
|
|
47
|
+
if (embed.footer?.text) text += ' ' + embed.footer.text;
|
|
48
|
+
}
|
|
49
|
+
// CV2 components text — recursively extract from containers, sections, text displays
|
|
50
|
+
function extractText(components) {
|
|
51
|
+
for (const comp of components || []) {
|
|
52
|
+
if (comp.content) text += ' ' + comp.content;
|
|
53
|
+
if (comp.components) extractText(comp.components);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
extractText(msg.components);
|
|
57
|
+
return text;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseCoins(text) {
|
|
61
|
+
if (!text) return 0;
|
|
62
|
+
// Match ⏣ prefixed coins
|
|
63
|
+
const matches = text.match(/⏣\s*([\d,]+)/g);
|
|
64
|
+
if (!matches) return 0;
|
|
65
|
+
let best = 0;
|
|
66
|
+
for (const m of matches) {
|
|
67
|
+
const numStr = m.replace(/[^\d]/g, '');
|
|
68
|
+
if (numStr) {
|
|
69
|
+
const val = parseInt(numStr);
|
|
70
|
+
if (val > 0 && val < 10000000) best = Math.max(best, val);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return best;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse wallet balance from a balance response message.
|
|
78
|
+
* Handles both embed-based and CV2 TEXT_DISPLAY component formats.
|
|
79
|
+
* CV2 format: "<:Coin:1105833876032606350> 243,322\n<:Bank:...> 20,000 / 20,000"
|
|
80
|
+
*/
|
|
81
|
+
function parseBalance(msg) {
|
|
82
|
+
if (!msg) return 0;
|
|
83
|
+
const text = getFullText(msg);
|
|
84
|
+
|
|
85
|
+
// Try standard embed wallet pattern
|
|
86
|
+
const walletMatch = text.match(/wallet[:\s]*[⏣💰]?\s*([\d,]+)/i);
|
|
87
|
+
if (walletMatch) return parseInt(walletMatch[1].replace(/,/g, ''));
|
|
88
|
+
|
|
89
|
+
// CV2 pattern: <:Coin:ID> NUMBER (first number after Coin emoji = wallet)
|
|
90
|
+
const coinEmojiMatch = text.match(/<:Coin:\d+>\s*([\d,]+)/);
|
|
91
|
+
if (coinEmojiMatch) return parseInt(coinEmojiMatch[1].replace(/,/g, ''));
|
|
92
|
+
|
|
93
|
+
// Fallback: ⏣ prefixed
|
|
94
|
+
const coins = parseCoins(text);
|
|
95
|
+
if (coins > 0) return coins;
|
|
96
|
+
|
|
97
|
+
// Last resort: first large number not in "X / Y" bank pattern
|
|
98
|
+
const cleaned = text.replace(/[\d,]+\s*\/\s*[\d,]+/g, '');
|
|
99
|
+
const numMatch = cleaned.match(/([\d,]{2,})/);
|
|
100
|
+
if (numMatch) return parseInt(numMatch[1].replace(/,/g, ''));
|
|
101
|
+
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Component Helpers ────────────────────────────────────────
|
|
106
|
+
// Recursively collect components from CV2 containers, sections, action rows,
|
|
107
|
+
// and SECTION accessory buttons (e.g. "Fish Again" in fishing result screen)
|
|
108
|
+
function flattenComponents(components) {
|
|
109
|
+
const flat = [];
|
|
110
|
+
for (const item of components || []) {
|
|
111
|
+
if (item.type === 2 || item.type === 'BUTTON' ||
|
|
112
|
+
item.type === 3 || item.type === 'SELECT_MENU' || item.type === 'STRING_SELECT') {
|
|
113
|
+
flat.push(item);
|
|
114
|
+
}
|
|
115
|
+
// Recurse into containers, action rows, sections, etc.
|
|
116
|
+
if (item.components) flat.push(...flattenComponents(item.components));
|
|
117
|
+
// SECTION accessory can be a button (e.g. "Fish Again" in Dank Memer fishing)
|
|
118
|
+
if (item.data?.accessory) {
|
|
119
|
+
const acc = item.data.accessory;
|
|
120
|
+
if (acc.type === 2 || acc.type === 'BUTTON') {
|
|
121
|
+
// Build a proper button-like object from the raw accessory data
|
|
122
|
+
flat.push({
|
|
123
|
+
type: 'BUTTON',
|
|
124
|
+
label: acc.label,
|
|
125
|
+
customId: acc.custom_id,
|
|
126
|
+
style: acc.style,
|
|
127
|
+
disabled: acc.disabled || false,
|
|
128
|
+
emoji: acc.emoji,
|
|
129
|
+
data: acc,
|
|
130
|
+
// Keep reference for safeClickButton
|
|
131
|
+
_accessoryOf: item,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return flat;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getAllButtons(msg) {
|
|
140
|
+
const all = flattenComponents(msg.components);
|
|
141
|
+
return all.filter(c => c.type === 2 || c.type === 'BUTTON');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getAllSelectMenus(msg) {
|
|
145
|
+
const all = flattenComponents(msg.components);
|
|
146
|
+
return all.filter(c => c.type === 3 || c.type === 'SELECT_MENU' || c.type === 'STRING_SELECT');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function findButton(msg, labelPart) {
|
|
150
|
+
return getAllButtons(msg).find(b => b.label && b.label.toLowerCase().includes(labelPart.toLowerCase())) || null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function findSelectMenuOption(msg, label) {
|
|
154
|
+
const menus = getAllSelectMenus(msg);
|
|
155
|
+
for (const comp of menus) {
|
|
156
|
+
const opt = (comp.options || []).find(o => o.label?.toLowerCase().includes(label.toLowerCase()));
|
|
157
|
+
if (opt) return { menuCustomId: comp.customId, option: opt, component: comp };
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Safe button click — tries .click() first, falls back to msg.clickButton()
|
|
163
|
+
async function safeClickButton(msg, button) {
|
|
164
|
+
if (typeof button.click === 'function') {
|
|
165
|
+
return button.click();
|
|
166
|
+
}
|
|
167
|
+
if (button.customId && typeof msg.clickButton === 'function') {
|
|
168
|
+
return msg.clickButton(button.customId);
|
|
169
|
+
}
|
|
170
|
+
throw new Error('No click method available on button');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── Hold Tight Detection ─────────────────────────────────────
|
|
174
|
+
function isHoldTight(msg) {
|
|
175
|
+
if (!msg) return false;
|
|
176
|
+
const text = getFullText(msg).toLowerCase();
|
|
177
|
+
return text.includes('hold tight') || text.includes('unable to interact') || text.includes('ongoing command');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Parse Hold Tight reason from message.
|
|
182
|
+
* Format: "Reason: /postmemes" or "Reason: /adventure"
|
|
183
|
+
* Returns the command name (e.g. 'postmemes', 'adventure') or null.
|
|
184
|
+
*/
|
|
185
|
+
function getHoldTightReason(msg) {
|
|
186
|
+
if (!msg) return null;
|
|
187
|
+
const text = getFullText(msg);
|
|
188
|
+
const match = text.match(/Reason:\s*\/(\w+)/i);
|
|
189
|
+
return match ? match[1].toLowerCase() : null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Debug Logger ─────────────────────────────────────────────
|
|
193
|
+
function logMsg(msg, label) {
|
|
194
|
+
if (!msg) { LOG.debug(`[${label}] No message`); return; }
|
|
195
|
+
// Content
|
|
196
|
+
if (msg.content) LOG.debug(`[${label}] content: "${msg.content.substring(0, 200).replace(/\n/g, ' ')}"`);
|
|
197
|
+
// Embeds
|
|
198
|
+
for (const e of msg.embeds || []) {
|
|
199
|
+
if (e.title) LOG.debug(`[${label}] title: "${e.title}"`);
|
|
200
|
+
if (e.description) LOG.debug(`[${label}] desc: "${e.description.substring(0, 200).replace(/\n/g, ' ')}"`);
|
|
201
|
+
for (const f of e.fields || []) LOG.debug(`[${label}] field: "${f.name}" = "${(f.value || '').substring(0, 150)}"`);
|
|
202
|
+
if (e.footer?.text) LOG.debug(`[${label}] footer: "${e.footer.text}"`);
|
|
203
|
+
if (e.image?.url) LOG.debug(`[${label}] image: ${e.image.url.substring(0, 80)}`);
|
|
204
|
+
}
|
|
205
|
+
// Components (buttons, selects, CV2 text)
|
|
206
|
+
for (const row of msg.components || []) {
|
|
207
|
+
if (row.type === 'TEXT_DISPLAY' && row.content)
|
|
208
|
+
LOG.debug(`[${label}] cv2-text: "${row.content.substring(0, 200).replace(/\n/g, ' ')}"`);
|
|
209
|
+
if (row.type === 'CONTAINER' || row.type === 'SECTION') {
|
|
210
|
+
for (const comp of row.components || []) {
|
|
211
|
+
if (comp.type === 'TEXT_DISPLAY' && comp.content)
|
|
212
|
+
LOG.debug(`[${label}] cv2-section: "${comp.content.substring(0, 200).replace(/\n/g, ' ')}"`);
|
|
213
|
+
if (comp.components) {
|
|
214
|
+
for (const sub of comp.components) {
|
|
215
|
+
if (sub.type === 'TEXT_DISPLAY' && sub.content)
|
|
216
|
+
LOG.debug(`[${label}] cv2-nested: "${sub.content.substring(0, 200).replace(/\n/g, ' ')}"`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (const comp of row.components || []) {
|
|
222
|
+
if (comp.type === 'BUTTON' || comp.type === 2)
|
|
223
|
+
LOG.debug(`[${label}] btn: "${comp.label}" emoji=${comp.emoji?.name || '-'} disabled=${comp.disabled} style=${comp.style} id=${(comp.customId || '').substring(0, 40)}`);
|
|
224
|
+
if (comp.type === 'STRING_SELECT' || comp.type === 3)
|
|
225
|
+
LOG.debug(`[${label}] select: ${comp.customId} [${comp.options?.map(o => `${o.label}${o.default ? '*' : ''}`).join(', ')}]`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── Dump full message raw (for debugging) ────────────────────
|
|
231
|
+
function dumpMessage(msg, label) {
|
|
232
|
+
console.log(`\n═══ [${label}] ═══`);
|
|
233
|
+
console.log(` author: ${msg.author?.tag} (${msg.author?.id})`);
|
|
234
|
+
console.log(` content: "${msg.content || ''}"`);
|
|
235
|
+
console.log(` embeds (${msg.embeds?.length || 0}):`);
|
|
236
|
+
for (const e of msg.embeds || []) {
|
|
237
|
+
console.log(JSON.stringify({
|
|
238
|
+
title: e.title, description: e.description,
|
|
239
|
+
fields: e.fields?.map(f => ({ name: f.name, value: f.value })),
|
|
240
|
+
footer: e.footer?.text, color: e.color,
|
|
241
|
+
}, null, 2));
|
|
242
|
+
}
|
|
243
|
+
console.log(` components (${msg.components?.length || 0}):`);
|
|
244
|
+
for (const row of msg.components || []) {
|
|
245
|
+
for (const comp of row.components || []) {
|
|
246
|
+
console.log(` type=${comp.type} label="${comp.label}" customId="${comp.customId}" disabled=${comp.disabled} style=${comp.style}`);
|
|
247
|
+
if (comp.options) console.log(` options: ${JSON.stringify(comp.options.map(o => ({ label: o.label, value: o.value, default: o.default })))}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Item Detection ───────────────────────────────────────────
|
|
253
|
+
function needsItem(text) {
|
|
254
|
+
const lower = text.toLowerCase();
|
|
255
|
+
if (lower.includes("don't have a shovel") || (lower.includes('need') && lower.includes('shovel')) || lower.includes('you need a shovel'))
|
|
256
|
+
return 'shovel';
|
|
257
|
+
if (lower.includes("don't have a fishing") || lower.includes('need a fishing') || lower.includes('you need a fishing pole'))
|
|
258
|
+
return 'fishing pole';
|
|
259
|
+
if (lower.includes("don't have a hunting rifle") || lower.includes('need a hunting rifle') || lower.includes('you need a rifle'))
|
|
260
|
+
return 'hunting rifle';
|
|
261
|
+
if (lower.includes("don't have") && lower.includes('ticket'))
|
|
262
|
+
return 'adventure ticket';
|
|
263
|
+
if (lower.includes('need') && lower.includes('ticket'))
|
|
264
|
+
return 'adventure ticket';
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
module.exports = {
|
|
269
|
+
DANK_MEMER_ID,
|
|
270
|
+
c,
|
|
271
|
+
LOG,
|
|
272
|
+
sleep,
|
|
273
|
+
humanDelay,
|
|
274
|
+
getFullText,
|
|
275
|
+
parseCoins,
|
|
276
|
+
parseBalance,
|
|
277
|
+
getAllButtons,
|
|
278
|
+
getAllSelectMenus,
|
|
279
|
+
findButton,
|
|
280
|
+
findSelectMenuOption,
|
|
281
|
+
safeClickButton,
|
|
282
|
+
isHoldTight,
|
|
283
|
+
getHoldTightReason,
|
|
284
|
+
logMsg,
|
|
285
|
+
dumpMessage,
|
|
286
|
+
needsItem,
|
|
287
|
+
};
|