initial actual player count implementation

This commit is contained in:
cottongin
2025-11-03 13:57:26 -05:00
parent 2a75237e90
commit 140988d01d
14 changed files with 3428 additions and 0 deletions

184
scripts/jackbox-count-v2.js Normal file
View File

@@ -0,0 +1,184 @@
#!/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);
});