185 lines
6.5 KiB
JavaScript
185 lines
6.5 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
const puppeteer = require('puppeteer');
|
||
|
|
|
||
|
|
async function getPlayerCount(roomCode) {
|
||
|
|
const browser = await puppeteer.launch({
|
||
|
|
headless: 'new',
|
||
|
|
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-web-security']
|
||
|
|
});
|
||
|
|
|
||
|
|
const page = await browser.newPage();
|
||
|
|
let playerCount = null;
|
||
|
|
|
||
|
|
// Listen for console API calls (this catches console.log with formatting)
|
||
|
|
page.on('console', async msg => {
|
||
|
|
try {
|
||
|
|
// Get all args
|
||
|
|
for (const arg of msg.args()) {
|
||
|
|
const val = await arg.jsonValue();
|
||
|
|
const str = JSON.stringify(val);
|
||
|
|
|
||
|
|
if (process.env.DEBUG) console.error('[CONSOLE]', str.substring(0, 200));
|
||
|
|
|
||
|
|
// Check if this is the welcome message
|
||
|
|
if (str && str.includes('"opcode":"client/welcome"') && str.includes('"here"')) {
|
||
|
|
const data = JSON.parse(str);
|
||
|
|
if (data.result && data.result.here) {
|
||
|
|
playerCount = Object.keys(data.result.here).length;
|
||
|
|
if (process.env.DEBUG) console.error('[FOUND] Player count:', playerCount);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
// Ignore
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (process.env.DEBUG) console.error('[1] Loading page...');
|
||
|
|
await page.goto('https://jackbox.tv/', { waitUntil: 'networkidle2', timeout: 30000 });
|
||
|
|
|
||
|
|
// Clear storage and reload to avoid reconnect
|
||
|
|
if (process.env.DEBUG) console.error('[2] Clearing storage...');
|
||
|
|
await page.evaluate(() => {
|
||
|
|
localStorage.clear();
|
||
|
|
sessionStorage.clear();
|
||
|
|
});
|
||
|
|
await page.reload({ waitUntil: 'networkidle2' });
|
||
|
|
|
||
|
|
await page.waitForSelector('input[placeholder*="ENTER 4-LETTER CODE"]', { timeout: 10000 });
|
||
|
|
|
||
|
|
if (process.env.DEBUG) {
|
||
|
|
console.error('[2.5] Checking all inputs on page...');
|
||
|
|
const allInputs = await page.evaluate(() => {
|
||
|
|
const inputs = Array.from(document.querySelectorAll('input'));
|
||
|
|
return inputs.map(inp => ({
|
||
|
|
type: inp.type,
|
||
|
|
placeholder: inp.placeholder,
|
||
|
|
value: inp.value,
|
||
|
|
name: inp.name,
|
||
|
|
id: inp.id,
|
||
|
|
visible: inp.offsetParent !== null
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
console.error('[2.5] All inputs:', JSON.stringify(allInputs, null, 2));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (process.env.DEBUG) console.error('[3] Typing room code...');
|
||
|
|
|
||
|
|
// Type room code character by character (with delay to trigger validation)
|
||
|
|
await page.click('input[placeholder*="ENTER 4-LETTER CODE"]');
|
||
|
|
await page.type('input[placeholder*="ENTER 4-LETTER CODE"]', roomCode, { delay: 100 });
|
||
|
|
|
||
|
|
// Wait for room validation to complete (look for loader success message)
|
||
|
|
if (process.env.DEBUG) console.error('[3.5] Waiting for room validation...');
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
||
|
|
|
||
|
|
// Type name character by character - this will enable the Play button
|
||
|
|
if (process.env.DEBUG) console.error('[3.6] Typing name...');
|
||
|
|
await page.click('input[placeholder*="ENTER YOUR NAME"]');
|
||
|
|
await page.type('input[placeholder*="ENTER YOUR NAME"]', 'Observer', { delay: 100 });
|
||
|
|
|
||
|
|
// Wait a moment for button to fully enable
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||
|
|
|
||
|
|
if (process.env.DEBUG) {
|
||
|
|
const fieldValues = await page.evaluate(() => {
|
||
|
|
const roomInput = document.querySelector('input[placeholder*="ENTER 4-LETTER CODE"]');
|
||
|
|
const nameInput = document.querySelector('input[placeholder*="ENTER YOUR NAME"]');
|
||
|
|
return {
|
||
|
|
roomCode: roomInput ? roomInput.value : 'NOT FOUND',
|
||
|
|
name: nameInput ? nameInput.value : 'NOT FOUND'
|
||
|
|
};
|
||
|
|
});
|
||
|
|
console.error('[3.5] Field values:', fieldValues);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find the Play or RECONNECT button (case-insensitive, not disabled)
|
||
|
|
if (process.env.DEBUG) {
|
||
|
|
const buttonInfo = await page.evaluate(() => {
|
||
|
|
const buttons = Array.from(document.querySelectorAll('button'));
|
||
|
|
const allButtons = buttons.map(b => ({
|
||
|
|
text: b.textContent.trim(),
|
||
|
|
disabled: b.disabled,
|
||
|
|
visible: b.offsetParent !== null
|
||
|
|
}));
|
||
|
|
const actionBtn = buttons.find(b => {
|
||
|
|
const text = b.textContent.toUpperCase();
|
||
|
|
return (text.includes('PLAY') || text.includes('RECONNECT')) && !b.disabled;
|
||
|
|
});
|
||
|
|
return {
|
||
|
|
allButtons,
|
||
|
|
found: actionBtn ? actionBtn.textContent.trim() : 'NOT FOUND'
|
||
|
|
};
|
||
|
|
});
|
||
|
|
console.error('[4] All buttons:', JSON.stringify(buttonInfo.allButtons, null, 2));
|
||
|
|
console.error('[4] Target button:', buttonInfo.found);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (process.env.DEBUG) console.error('[5] Clicking Play/Reconnect (even if disabled)...');
|
||
|
|
await page.evaluate(() => {
|
||
|
|
const buttons = Array.from(document.querySelectorAll('button'));
|
||
|
|
const actionBtn = buttons.find(b => {
|
||
|
|
const text = b.textContent.toUpperCase();
|
||
|
|
return text.includes('PLAY') || text.includes('RECONNECT');
|
||
|
|
});
|
||
|
|
if (actionBtn) {
|
||
|
|
// Remove disabled attribute and click
|
||
|
|
actionBtn.disabled = false;
|
||
|
|
actionBtn.click();
|
||
|
|
} else {
|
||
|
|
throw new Error('Could not find PLAY or RECONNECT button');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Wait for navigation/lobby to load
|
||
|
|
if (process.env.DEBUG) console.error('[6] Waiting for lobby (5 seconds)...');
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||
|
|
|
||
|
|
// Check if we're in the lobby
|
||
|
|
const pageText = await page.evaluate(() => document.body.innerText);
|
||
|
|
const inLobby = pageText.includes('Sit back') || pageText.includes('relax');
|
||
|
|
|
||
|
|
if (process.env.DEBUG) {
|
||
|
|
console.error('[7] In lobby:', inLobby);
|
||
|
|
console.error('[7] Page text sample:', pageText.substring(0, 100));
|
||
|
|
console.error('[7] Page URL:', page.url());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wait for WebSocket message
|
||
|
|
if (process.env.DEBUG) console.error('[8] Waiting for player count...');
|
||
|
|
for (let i = 0; i < 20 && playerCount === null; i++) {
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||
|
|
if (process.env.DEBUG && i % 4 === 0) {
|
||
|
|
console.error(`[8.${i}] Still waiting...`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
await browser.close();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (playerCount === null) {
|
||
|
|
throw new Error('Could not get player count');
|
||
|
|
}
|
||
|
|
|
||
|
|
return playerCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
const roomCode = process.argv[2];
|
||
|
|
if (!roomCode) {
|
||
|
|
console.error('Usage: node jackbox-count-v2.js <ROOM_CODE>');
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
getPlayerCount(roomCode.toUpperCase())
|
||
|
|
.then(count => {
|
||
|
|
console.log(count);
|
||
|
|
process.exit(0);
|
||
|
|
})
|
||
|
|
.catch(err => {
|
||
|
|
console.error('Error:', err.message);
|
||
|
|
process.exit(1);
|
||
|
|
});
|
||
|
|
|