185 lines
6.7 KiB
JavaScript
Executable File
185 lines
6.7 KiB
JavaScript
Executable File
#!/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'
|
|
]
|
|
});
|
|
|
|
const page = await browser.newPage();
|
|
|
|
// Set a realistic user agent to avoid bot detection
|
|
await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
|
let playerCount = null;
|
|
let roomValidated = false;
|
|
|
|
// Monitor network requests to see if API call happens
|
|
page.on('response', async (response) => {
|
|
const url = response.url();
|
|
if (url.includes('ecast.jackboxgames.com/api/v2/rooms')) {
|
|
if (process.env.DEBUG) {
|
|
console.error('[NETWORK] Room API called:', url, 'Status:', response.status());
|
|
}
|
|
if (response.status() === 200) {
|
|
roomValidated = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Listen for console messages that contain the client/welcome WebSocket message
|
|
page.on('console', async msg => {
|
|
try {
|
|
const args = msg.args();
|
|
for (const arg of args) {
|
|
const val = await arg.jsonValue();
|
|
const str = typeof val === 'object' ? JSON.stringify(val) : String(val);
|
|
|
|
// Debug: log all console messages that might be relevant
|
|
if (process.env.DEBUG && (str.includes('welcome') || str.includes('here') || str.includes('opcode'))) {
|
|
console.error('[CONSOLE]', str.substring(0, 200));
|
|
}
|
|
|
|
// Look for the client/welcome message with player data
|
|
if (str.includes('client/welcome')) {
|
|
try {
|
|
let data;
|
|
if (typeof val === 'object') {
|
|
data = val;
|
|
} else {
|
|
// The string might be "recv <- {...}" so extract the JSON part
|
|
const jsonStart = str.indexOf('{');
|
|
if (jsonStart !== -1) {
|
|
const jsonStr = str.substring(jsonStart);
|
|
data = JSON.parse(jsonStr);
|
|
}
|
|
}
|
|
|
|
if (data && data.opcode === 'client/welcome' && data.result) {
|
|
// Look for the "here" object which contains all connected players
|
|
if (data.result.here) {
|
|
playerCount = Object.keys(data.result.here).length;
|
|
if (process.env.DEBUG) {
|
|
console.error('[SUCCESS] Found player count:', playerCount);
|
|
}
|
|
} else if (process.env.DEBUG) {
|
|
console.error('[DEBUG] client/welcome found but no "here" object. Keys:', Object.keys(data.result));
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (process.env.DEBUG) {
|
|
console.error('[PARSE ERROR]', e.message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Ignore errors
|
|
}
|
|
});
|
|
|
|
try {
|
|
if (process.env.DEBUG) console.error('[1] Navigating to jackbox.tv...');
|
|
await page.goto('https://jackbox.tv/', { waitUntil: 'networkidle2', timeout: 30000 });
|
|
|
|
// Wait for the room code input to be ready
|
|
if (process.env.DEBUG) console.error('[2] Waiting for form...');
|
|
await page.waitForSelector('input#roomcode', { timeout: 10000 });
|
|
|
|
// Type the room code using the input ID (more reliable)
|
|
// Use the element.type() method which properly triggers React events
|
|
if (process.env.DEBUG) console.error('[3] Typing room code:', roomCode);
|
|
const roomInput = await page.$('input#roomcode');
|
|
await roomInput.type(roomCode.toUpperCase(), { delay: 50 }); // Reduced delay from 100ms to 50ms
|
|
|
|
// Wait for room validation (the app info appears after validation)
|
|
if (process.env.DEBUG) {
|
|
console.error('[4] Waiting for room validation...');
|
|
const roomValue = await page.evaluate(() => document.querySelector('input#roomcode').value);
|
|
console.error('[4] Room code value:', roomValue);
|
|
}
|
|
|
|
// Actually wait for the validation to complete - the game name label appears
|
|
try {
|
|
await page.waitForFunction(() => {
|
|
const labels = Array.from(document.querySelectorAll('div, span, label'));
|
|
return labels.some(el => {
|
|
const text = el.textContent;
|
|
return text.includes('Trivia') || text.includes('Party') || text.includes('Quiplash') ||
|
|
text.includes('Fibbage') || text.includes('Drawful') || text.includes('Murder');
|
|
});
|
|
}, { timeout: 5000 });
|
|
if (process.env.DEBUG) console.error('[4.5] Room validated successfully!');
|
|
} catch (e) {
|
|
if (process.env.DEBUG) console.error('[4.5] Room validation timeout - continuing anyway...');
|
|
}
|
|
|
|
// Type the name using the input ID
|
|
// This will trigger the input event that enables the Play button
|
|
if (process.env.DEBUG) console.error('[5] Typing name...');
|
|
const nameInput = await page.$('input#username');
|
|
await nameInput.type('Observer', { delay: 30 }); // Reduced delay from 100ms to 30ms
|
|
|
|
// Wait a moment for the button to enable and click immediately
|
|
if (process.env.DEBUG) console.error('[6] Waiting for Play button...');
|
|
await page.waitForFunction(() => {
|
|
const buttons = Array.from(document.querySelectorAll('button'));
|
|
const playBtn = buttons.find(b => {
|
|
const text = b.textContent.toUpperCase();
|
|
return (text.includes('PLAY') || text.includes('RECONNECT')) && !b.disabled;
|
|
});
|
|
return playBtn !== undefined;
|
|
}, { timeout: 5000 }); // Reduced timeout from 10s to 5s
|
|
|
|
if (process.env.DEBUG) console.error('[7] Clicking Play...');
|
|
await page.evaluate(() => {
|
|
const buttons = Array.from(document.querySelectorAll('button'));
|
|
const playBtn = buttons.find(b => {
|
|
const text = b.textContent.toUpperCase();
|
|
return (text.includes('PLAY') || text.includes('RECONNECT')) && !b.disabled;
|
|
});
|
|
if (playBtn) {
|
|
playBtn.click();
|
|
} else {
|
|
throw new Error('Play button not found or still disabled');
|
|
}
|
|
});
|
|
|
|
// Wait for the WebSocket player count message (up to 5 seconds)
|
|
if (process.env.DEBUG) console.error('[8] Waiting for player count message...');
|
|
for (let i = 0; i < 10 && playerCount === null; i++) {
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
}
|
|
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
|
|
if (playerCount === null) {
|
|
throw new Error('Could not get player count from WebSocket');
|
|
}
|
|
|
|
return playerCount;
|
|
}
|
|
|
|
// Main
|
|
const roomCode = process.argv[2];
|
|
if (!roomCode) {
|
|
console.error('Usage: node jackbox-count-v3.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);
|
|
});
|
|
|