dankgrinder 5.11.0 → 5.12.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/stream.js +160 -84
- package/package.json +1 -1
package/lib/commands/stream.js
CHANGED
|
@@ -1,10 +1,89 @@
|
|
|
1
1
|
const {
|
|
2
2
|
LOG, c, getFullText, parseCoins, getAllButtons, getAllSelectMenus,
|
|
3
3
|
safeClickButton, logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, needsItem,
|
|
4
|
+
isCV2, ensureCV2, stripAnsi,
|
|
4
5
|
} = require('./utils');
|
|
5
6
|
const { buyItem } = require('./shop');
|
|
6
7
|
|
|
7
8
|
const STREAM_ITEMS = Object.freeze(['keyboard', 'mouse']);
|
|
9
|
+
const STREAM_ACTION_LABELS = Object.freeze(['run ad', 'read chat', 'collect donations']);
|
|
10
|
+
|
|
11
|
+
function normalizeLower(text) {
|
|
12
|
+
return String(text || '')
|
|
13
|
+
.normalize('NFKC')
|
|
14
|
+
.replace(/[’‘`´]/g, "'")
|
|
15
|
+
.replace(/\s+/g, ' ')
|
|
16
|
+
.trim()
|
|
17
|
+
.toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pickRandom(arr) {
|
|
21
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function hydrate(msg) {
|
|
25
|
+
if (isCV2(msg)) await ensureCV2(msg);
|
|
26
|
+
return msg;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function refetchMsg(channel, msgId) {
|
|
30
|
+
try { return await channel.messages.fetch(msgId); } catch { return null; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getGoLiveButton(msg) {
|
|
34
|
+
const buttons = getAllButtons(msg);
|
|
35
|
+
return buttons.find(b => !b.disabled && (b.label || '').toLowerCase().includes('go live')) || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getLiveActionButtons(msg) {
|
|
39
|
+
const buttons = getAllButtons(msg).filter(b => !b.disabled && b.label);
|
|
40
|
+
return buttons.filter(b => STREAM_ACTION_LABELS.some(lbl => (b.label || '').toLowerCase().includes(lbl)));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isLiveDashboard(msg, lowerText) {
|
|
44
|
+
if (lowerText.includes('your stream can last') || lowerText.includes('you can interact with your stream every')) return true;
|
|
45
|
+
const labels = getAllButtons(msg).map(b => (b.label || '').toLowerCase());
|
|
46
|
+
return labels.some(l => l.includes('end stream')) && labels.some(l => STREAM_ACTION_LABELS.some(a => l.includes(a)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isStreamManagerScreen(lowerText) {
|
|
50
|
+
return lowerText.includes('stream manager')
|
|
51
|
+
|| lowerText.includes('what game do you want to stream')
|
|
52
|
+
|| lowerText.includes('view setup')
|
|
53
|
+
|| lowerText.includes('go live');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function selectRandomStreamOption(msg) {
|
|
57
|
+
const menus = getAllSelectMenus(msg);
|
|
58
|
+
if (menus.length === 0) return false;
|
|
59
|
+
|
|
60
|
+
const menu = menus[0];
|
|
61
|
+
const options = (menu.options || []).filter(o => !o.default);
|
|
62
|
+
if (options.length === 0) return false;
|
|
63
|
+
|
|
64
|
+
const pick = pickRandom(options);
|
|
65
|
+
LOG.info(`[stream] Selecting option: "${pick.label}"`);
|
|
66
|
+
|
|
67
|
+
let menuRowIdx = -1;
|
|
68
|
+
for (let i = 0; i < (msg.components || []).length; i++) {
|
|
69
|
+
for (const comp of (msg.components[i]?.components || [])) {
|
|
70
|
+
if ((comp.type === 'STRING_SELECT' || comp.type === 3) &&
|
|
71
|
+
((comp.customId || comp.custom_id) === (menu.customId || menu.custom_id))) {
|
|
72
|
+
menuRowIdx = i;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (menuRowIdx >= 0) break;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await msg.selectMenu(menuRowIdx >= 0 ? menuRowIdx : (menu.customId || menu.custom_id), [pick.value]);
|
|
81
|
+
return true;
|
|
82
|
+
} catch (e) {
|
|
83
|
+
LOG.error(`[stream] Select option failed: ${e.message}`);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
8
87
|
|
|
9
88
|
async function runStream({ channel, waitForDankMemer, client }) {
|
|
10
89
|
LOG.cmd(`${c.white}${c.bold}pls stream${c.reset}`);
|
|
@@ -22,16 +101,21 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
22
101
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
23
102
|
}
|
|
24
103
|
|
|
104
|
+
await hydrate(response);
|
|
25
105
|
logMsg(response, 'stream');
|
|
26
106
|
let text = getFullText(response);
|
|
27
|
-
let lower = text
|
|
107
|
+
let lower = normalizeLower(stripAnsi(text));
|
|
28
108
|
|
|
29
109
|
// Missing items — buy keyboard + mouse
|
|
30
|
-
|
|
110
|
+
const missing = needsItem(lower);
|
|
111
|
+
if (missing === 'keyboard' || missing === 'mouse' ||
|
|
112
|
+
lower.includes('missing items') || lower.includes('need following items') ||
|
|
113
|
+
lower.includes('need the following items') ||
|
|
31
114
|
lower.includes('need a keyboard') || lower.includes('need a mouse')) {
|
|
32
115
|
const itemsToBuy = [];
|
|
33
116
|
if (lower.includes('keyboard')) itemsToBuy.push('keyboard');
|
|
34
117
|
if (lower.includes('mouse')) itemsToBuy.push('mouse');
|
|
118
|
+
if ((missing === 'keyboard' || missing === 'mouse') && !itemsToBuy.includes(missing)) itemsToBuy.push(missing);
|
|
35
119
|
if (itemsToBuy.length === 0) itemsToBuy.push(...STREAM_ITEMS);
|
|
36
120
|
|
|
37
121
|
for (const item of itemsToBuy) {
|
|
@@ -44,108 +128,100 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
44
128
|
await channel.send('pls stream');
|
|
45
129
|
response = await waitForDankMemer(12000);
|
|
46
130
|
if (!response) return { result: 'no response after buy', coins: 0 };
|
|
131
|
+
await hydrate(response);
|
|
47
132
|
logMsg(response, 'stream-retry');
|
|
48
133
|
text = getFullText(response);
|
|
49
|
-
lower = text
|
|
134
|
+
lower = normalizeLower(stripAnsi(text));
|
|
50
135
|
}
|
|
51
136
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
137
|
+
// Setup flow: manager -> option select (game/platform) -> Go Live -> live dashboard
|
|
138
|
+
for (let step = 0; step < 6; step++) {
|
|
139
|
+
text = getFullText(response);
|
|
140
|
+
lower = normalizeLower(stripAnsi(text));
|
|
141
|
+
|
|
142
|
+
if (isLiveDashboard(response, lower)) break;
|
|
143
|
+
if (!isStreamManagerScreen(lower)) break;
|
|
144
|
+
|
|
145
|
+
const selected = await selectRandomStreamOption(response);
|
|
146
|
+
if (selected) {
|
|
147
|
+
await humanDelay(150, 350);
|
|
148
|
+
const updatedAfterSelect = (await waitForDankMemer(5000)) || (await refetchMsg(channel, response.id));
|
|
149
|
+
if (updatedAfterSelect) {
|
|
150
|
+
response = updatedAfterSelect;
|
|
151
|
+
await hydrate(response);
|
|
152
|
+
logMsg(response, `stream-selected-${step}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
70
155
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
156
|
+
const goLiveBtn = getGoLiveButton(response);
|
|
157
|
+
if (!goLiveBtn) {
|
|
158
|
+
await sleep(300);
|
|
159
|
+
const fresh = await refetchMsg(channel, response.id);
|
|
160
|
+
if (fresh) {
|
|
161
|
+
response = fresh;
|
|
162
|
+
await hydrate(response);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
LOG.info('[stream] Clicking "Go Live"');
|
|
169
|
+
await humanDelay(100, 250);
|
|
170
|
+
try {
|
|
171
|
+
const liveResult = await safeClickButton(response, goLiveBtn);
|
|
172
|
+
const next = liveResult || (await waitForDankMemer(7000)) || (await refetchMsg(channel, response.id));
|
|
173
|
+
if (next) {
|
|
174
|
+
response = next;
|
|
175
|
+
await hydrate(response);
|
|
176
|
+
logMsg(response, `stream-go-live-${step}`);
|
|
177
|
+
continue;
|
|
83
178
|
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
LOG.error(`[stream] Go Live click failed: ${e.message}`);
|
|
84
181
|
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
85
184
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
185
|
+
// Live phase: click exactly one random interaction button every run.
|
|
186
|
+
// Scheduler uses nextCooldownSec=600, so this executes every 10 minutes.
|
|
187
|
+
text = getFullText(response);
|
|
188
|
+
lower = normalizeLower(stripAnsi(text));
|
|
189
|
+
|
|
190
|
+
if (isLiveDashboard(response, lower)) {
|
|
191
|
+
const actions = getLiveActionButtons(response);
|
|
192
|
+
if (actions.length > 0) {
|
|
193
|
+
const action = pickRandom(actions);
|
|
194
|
+
LOG.info(`[stream] Live action: "${action.label}"`);
|
|
195
|
+
await humanDelay(120, 320);
|
|
92
196
|
try {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
197
|
+
const clicked = await safeClickButton(response, action);
|
|
198
|
+
const updated = clicked || (await waitForDankMemer(6000)) || (await refetchMsg(channel, response.id));
|
|
199
|
+
if (updated) {
|
|
200
|
+
response = updated;
|
|
201
|
+
await hydrate(response);
|
|
202
|
+
logMsg(response, 'stream-action');
|
|
97
203
|
text = getFullText(response);
|
|
98
|
-
lower = text
|
|
204
|
+
lower = normalizeLower(stripAnsi(text));
|
|
99
205
|
}
|
|
100
206
|
} catch (e) {
|
|
101
|
-
LOG.error(`[stream]
|
|
207
|
+
LOG.error(`[stream] Action click failed: ${e.message}`);
|
|
102
208
|
}
|
|
103
209
|
} else {
|
|
104
|
-
|
|
105
|
-
if (anyBtn) {
|
|
106
|
-
try { await safeClickButton(response, anyBtn); } catch {}
|
|
107
|
-
}
|
|
210
|
+
LOG.info('[stream] Live dashboard found but no action buttons available yet');
|
|
108
211
|
}
|
|
109
212
|
}
|
|
110
213
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
logMsg(followUp, `stream-round-${round}`);
|
|
118
|
-
const fText = getFullText(followUp);
|
|
119
|
-
const fLower = fText.toLowerCase();
|
|
120
|
-
const fCoins = parseCoins(fText);
|
|
121
|
-
|
|
122
|
-
if (fLower.includes('stream ended') || fLower.includes('stream is over') || fLower.includes('you earned')) {
|
|
123
|
-
if (fCoins > 0) {
|
|
124
|
-
LOG.coin(`[stream] ${c.green}+⏣ ${fCoins.toLocaleString()}${c.reset}`);
|
|
125
|
-
return { result: `stream → +⏣ ${fCoins.toLocaleString()}`, coins: fCoins, nextCooldownSec: 600 };
|
|
126
|
-
}
|
|
127
|
-
return { result: 'stream ended', coins: 0, nextCooldownSec: 600 };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const btns = getAllButtons(followUp);
|
|
131
|
-
const actionBtn = btns.find(b => !b.disabled && b.label &&
|
|
132
|
-
!b.label.toLowerCase().includes('end') && !b.label.toLowerCase().includes('stop') && !b.label.toLowerCase().includes('back'));
|
|
133
|
-
if (actionBtn) {
|
|
134
|
-
LOG.info(`[stream] Clicking "${actionBtn.label}"`);
|
|
135
|
-
await humanDelay(100, 300);
|
|
136
|
-
try { await safeClickButton(followUp, actionBtn); } catch {}
|
|
137
|
-
} else if (btns.length > 0) {
|
|
138
|
-
const first = btns.find(b => !b.disabled);
|
|
139
|
-
if (first) { try { await safeClickButton(followUp, first); } catch {} }
|
|
140
|
-
} else {
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
214
|
+
const ended = lower.includes('stream ended') || lower.includes('stream is over') || lower.includes('you earned');
|
|
215
|
+
const coins = parseCoins(text);
|
|
216
|
+
if (coins > 0) {
|
|
217
|
+
LOG.coin(`[stream] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
218
|
+
return { result: `stream → +⏣ ${coins.toLocaleString()}`, coins, nextCooldownSec: 600 };
|
|
143
219
|
}
|
|
144
220
|
|
|
145
|
-
|
|
146
|
-
if (coins > 0) return { result: `stream → +⏣ ${coins.toLocaleString()}`, coins, nextCooldownSec: 600 };
|
|
221
|
+
if (ended) return { result: 'stream ended', coins: 0, nextCooldownSec: 600 };
|
|
147
222
|
|
|
148
|
-
|
|
223
|
+
const short = normalizeLower(stripAnsi(text)).substring(0, 60);
|
|
224
|
+
return { result: short || 'streamed', coins: 0, nextCooldownSec: 600 };
|
|
149
225
|
}
|
|
150
226
|
|
|
151
227
|
module.exports = { runStream };
|