dankgrinder 4.9.5 → 4.9.8

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.
@@ -194,15 +194,33 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
194
194
 
195
195
  while (page < total) {
196
196
  const buttons = getAllButtons(response);
197
- const nextBtn = buttons.find(b => {
198
- if (b.disabled) return false;
199
- const id = (b.customId || '').toLowerCase();
200
- const label = (b.label || '').toLowerCase();
201
- const emoji = (b.emoji?.name || '').toLowerCase();
202
- return id.includes('next') || id.includes('right') || label.includes('next')
203
- || label === '▶' || label === '→' || emoji.includes('right') || emoji === '▶';
197
+ const enabled = buttons.filter(b => !b.disabled);
198
+ const expectedNextPage = page; // page is 1-based, paginator target indexes are usually 0-based
199
+
200
+ const parseTargetPage = (customId) => {
201
+ if (!customId) return null;
202
+ const m = String(customId).match(/:(\d+)(?:\.\d+)?$/);
203
+ return m ? parseInt(m[1], 10) : null;
204
+ };
205
+
206
+ let nextBtn = enabled.find((b) => {
207
+ const id = String(b.customId || '');
208
+ if (!/paginator-inventory-list/i.test(id)) return false;
209
+ if (!/setpage/i.test(id)) return false;
210
+ return parseTargetPage(id) === expectedNextPage;
204
211
  });
205
212
 
213
+ if (!nextBtn) {
214
+ nextBtn = enabled.find((b) => {
215
+ const id = (b.customId || '').toLowerCase();
216
+ const label = (b.label || '').toLowerCase();
217
+ const emoji = (b.emoji?.name || '').toLowerCase();
218
+ return (id.includes('paginator-inventory') && (id.includes('setpage') || id.includes('next')))
219
+ || label.includes('next') || label === '▶' || label === '→'
220
+ || emoji.includes('arrowright') || emoji.includes('doubleright') || emoji === '▶';
221
+ });
222
+ }
223
+
206
224
  if (!nextBtn) {
207
225
  LOG.debug(`[inv] No next button on page ${page}`);
208
226
  break;
@@ -131,10 +131,10 @@ async function runTrivia({ channel, waitForDankMemer, redis }) {
131
131
  return { result: `trivia → ${btn.label} → +⏣ ${coins.toLocaleString()}`, coins };
132
132
  }
133
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 };
134
+ return { result: `trivia → ${btn.label} → wrong`, coins: 0 };
135
135
  }
136
136
  if (lower.includes('correct') || lower.includes('nice') || lower.includes('right')) {
137
- return { result: `trivia → ${btn.label} → ${c.green}correct${c.reset}`, coins: 0 };
137
+ return { result: `trivia → ${btn.label} → correct`, coins: 0 };
138
138
  }
139
139
  return { result: `trivia → ${btn.label}`, coins: 0 };
140
140
  }
package/lib/grinder.js CHANGED
@@ -300,10 +300,12 @@ async function fetchConfig(retries = 3, delayMs = 1500) {
300
300
 
301
301
  async function sendLog(accountName, command, response, status) {
302
302
  try {
303
+ const safeCommand = stripAnsi(String(command || '')).replace(/\s+/g, ' ').trim();
304
+ const safeResponse = stripAnsi(String(response || '')).replace(/\s+/g, ' ').trim();
303
305
  await fetch(`${API_URL}/api/grinder/log`, {
304
306
  method: 'POST',
305
307
  headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
306
- body: JSON.stringify({ account_name: accountName, command, response, status }),
308
+ body: JSON.stringify({ account_name: accountName, command: safeCommand, response: safeResponse, status }),
307
309
  });
308
310
  } catch { /* silent */ }
309
311
  }
@@ -321,10 +323,13 @@ async function reportEarnings(accountId, accountName, earned, spent, command) {
321
323
 
322
324
  async function reportCommandFeed(accountId, accountName, data) {
323
325
  try {
326
+ const normalized = { ...data };
327
+ if (typeof normalized.command === 'string') normalized.command = stripAnsi(normalized.command).replace(/\s+/g, ' ').trim();
328
+ if (typeof normalized.result === 'string') normalized.result = stripAnsi(normalized.result).replace(/\s+/g, ' ').trim();
324
329
  await fetch(`${API_URL}/api/grinder/command-feed`, {
325
330
  method: 'POST',
326
331
  headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
327
- body: JSON.stringify({ account_id: accountId, account_name: accountName, ...data }),
332
+ body: JSON.stringify({ account_id: accountId, account_name: accountName, ...normalized }),
328
333
  });
329
334
  } catch { /* silent */ }
330
335
  }
@@ -548,7 +553,7 @@ class AccountWorker {
548
553
  }
549
554
 
550
555
  setStatus(text) {
551
- this.lastStatus = text;
556
+ this.lastStatus = stripAnsi(String(text || '')).replace(/\s+/g, ' ').trim();
552
557
  if (dashboardStarted) scheduleRender();
553
558
  }
554
559
 
@@ -827,6 +832,39 @@ class AccountWorker {
827
832
 
828
833
  async checkBalance() {
829
834
  const prefix = this.account.use_slash ? '/' : 'pls';
835
+ const sentAt = Date.now();
836
+
837
+ const looksLikeBalance = (t) => {
838
+ if (!t) return false;
839
+ const lower = t.toLowerCase();
840
+ return lower.includes('balance') || lower.includes('balances') || lower.includes('global rank')
841
+ || lower.includes('wallet') || /<a?:coin:\d+>/.test(t) || /<a?:bank:\d+>/.test(t);
842
+ };
843
+
844
+ const readBalanceText = async (msg, forceCV2 = false) => {
845
+ if (!msg) return '';
846
+ if (isCV2(msg)) await ensureCV2(msg, forceCV2);
847
+ return stripAnsi(getFullText(msg)).replace(/\s+/g, ' ').trim();
848
+ };
849
+
850
+ const findRecentBalanceMessage = async () => {
851
+ if (!this.channel?.messages?.fetch) return null;
852
+ for (let attempt = 0; attempt < 4; attempt++) {
853
+ try {
854
+ const recent = await this.channel.messages.fetch({ limit: 8 });
855
+ const candidates = [...recent.values()].filter((m) =>
856
+ m?.author?.id === DANK_MEMER_ID && (m.createdTimestamp || 0) >= sentAt - 2500
857
+ );
858
+ for (const m of candidates) {
859
+ const t = await readBalanceText(m, true);
860
+ if (looksLikeBalance(t)) return m;
861
+ }
862
+ } catch {}
863
+ await new Promise((r) => setTimeout(r, 700));
864
+ }
865
+ return null;
866
+ };
867
+
830
868
  await this.channel.send(`${prefix} bal`);
831
869
  let response = await this.waitForDankMemer(10000);
832
870
 
@@ -837,15 +875,35 @@ class AccountWorker {
837
875
  }
838
876
 
839
877
  if (response) {
840
- if (isCV2(response)) await ensureCV2(response);
841
- let text = stripAnsi(getFullText(response)).replace(/\s+/g, ' ').trim();
878
+ let text = await readBalanceText(response);
842
879
 
843
880
  // Dank Memer sometimes sends empty first payload then edits in the full card.
844
- if (!text && response.id) {
881
+ if ((!text || !looksLikeBalance(text)) && response.id) {
845
882
  const edited = await this.waitForMessageUpdate(response.id, 5000);
846
883
  if (edited) {
847
- if (isCV2(edited)) await ensureCV2(edited);
848
- text = stripAnsi(getFullText(edited)).replace(/\s+/g, ' ').trim();
884
+ text = await readBalanceText(edited, true);
885
+ response = edited;
886
+ }
887
+ }
888
+
889
+ // If we received a stale/irrelevant update, fetch same message fresh.
890
+ if ((!text || !looksLikeBalance(text)) && response.id && this.channel?.messages?.fetch) {
891
+ const fetched = await Promise.resolve(this.channel.messages.fetch(response.id)).catch(() => null);
892
+ if (fetched) {
893
+ const fetchedText = await readBalanceText(fetched, true);
894
+ if (fetchedText) {
895
+ text = fetchedText;
896
+ response = fetched;
897
+ }
898
+ }
899
+ }
900
+
901
+ // Final fallback: scan latest Dank messages right after command send.
902
+ if (!text || !looksLikeBalance(text)) {
903
+ const recentBalance = await findRecentBalanceMessage();
904
+ if (recentBalance) {
905
+ text = await readBalanceText(recentBalance, true);
906
+ response = recentBalance;
849
907
  }
850
908
  }
851
909
 
@@ -854,6 +912,11 @@ class AccountWorker {
854
912
  return;
855
913
  }
856
914
 
915
+ if (!looksLikeBalance(text)) {
916
+ this.log('warn', `Balance response did not look like balance card: "${text.substring(0, 140)}"`);
917
+ return;
918
+ }
919
+
857
920
  let wallet = 0;
858
921
  let bank = 0;
859
922
  let matched = '';
@@ -898,9 +961,9 @@ class AccountWorker {
898
961
  if (this.stats.balance > 0 || this.stats.bankBalance > 0) return;
899
962
  }
900
963
 
901
- this.stats.balance = wallet;
902
- this.stats.bankBalance = bank;
903
- 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}`);
964
+ this.stats.balance = wallet;
965
+ this.stats.bankBalance = bank;
966
+ 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}`);
904
967
 
905
968
  // Store in Redis for persistence
906
969
  if (redis) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "4.9.5",
3
+ "version": "4.9.8",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"