Skip to content

Instantly share code, notes, and snippets.

@Cozy19
Forked from szkrd/youtube-listen.js
Created April 21, 2022 18:07
Show Gist options
  • Save Cozy19/40502f9b34e670b1662c556ec575610b to your computer and use it in GitHub Desktop.
Save Cozy19/40502f9b34e670b1662c556ec575610b to your computer and use it in GitHub Desktop.
Listen to a youtube stream in puppeteer (headless chrome/chromium). By default plays Chillcow's channel, works on linux and windows, clicks on all the popup crap.
// first: `npm init -- yes && npm i chalk puppeteer-core`
// then: `node . --help`
const puppeteer = require('puppeteer-core');
const chalk = require('chalk');
// ---
const ifHasParam = (s = '') => process.argv.includes(s);
const paramValueOf = (s = '') => (process.argv.find(x => x.startsWith(`${s}=`)) || '').split('=')[1];
const DEFAULT_VIDEO_ID = '5qap5aO4i9A'; // ChillCow; his other channel is "DWcJFNfaw9c"
const DEBUG = ifHasParam('--debug');
const SHOW_HEAD = ifHasParam('--head');
const MAX_TRIES = parseInt(paramValueOf('--iteration'), 10) || 20;
const VIDEO_ID = paramValueOf('--vid');
const URL = `https://www.youtube.com/watch?v=${VIDEO_ID || DEFAULT_VIDEO_ID}`;
const ALSA_DEVICE = paramValueOf('--alsa');
let BROWSER_PATH = paramValueOf('--browser');;
if (!BROWSER_PATH && process.platform === 'linux') BROWSER_PATH = '/usr/bin/chromium';
if (!BROWSER_PATH && process.platform === 'win32') BROWSER_PATH = 'C:/Program Files/Google/Chrome/Application/chrome.exe';
// ---
if (ifHasParam('--help')) {
console.info('Params (all optional):\n--help\n--debug\n--head\n--vid=YOUTUBE_vID' +
'\n--iteration=N\n--browser=/usr/bin/chromium\n--alsa=ALSA_DEVICE_ID');
process.exit();
}
let pup = { browser: null, page: null };
const selectors = {
pageBody: 'body',
gdprButton: 'button[aria-label^="Agree"]',
introAgreeButton: '#introAgreeButton',
playButton: 'button[aria-label="Play"]',
noThanksButton: '#button[aria-label="No thanks"]',
dismissBullshitButton: '#dismiss-button paper-button',
consentIframe: 'iframe[src*="consent"]',
consentForm: 'form[action*="consent"]',
skipAdButton: 'button[class*="skip-button"]'
};
const print = (...args) => console.info(chalk.cyan(...args));
const sleep = (t = 0) => new Promise((resolve) => { setTimeout(resolve, t); });
async function waitForSelector(sel = '', timeout = 2000) {
if (DEBUG) print(`Waiting for ${chalk.blue(sel)}`);
if (selectors[sel]) sel = selectors[sel];
let success = true;
try { success = await pup.page.waitForSelector(sel, { timeout }); } catch (err) { success = false; }
return success;
}
const _clicked = {};
async function click(sel = '', times = 1) {
if (DEBUG) print(`Clicking "${chalk.blue(sel)}"`);
if (selectors[sel]) sel = selectors[sel];
if (_clicked[sel] >= times) return;
let success = true;
try { await pup.page.click(sel); } catch (err) { success = false; }
if (success) _clicked[sel] = (_clicked[sel] || 0) + 1;
return success;
}
async function acceptConsent() {
let hasConsentFrame = await waitForSelector('consentIframe');
if (!hasConsentFrame) return;
const frame = pup.page.frames().find((frame, i) => frame.url().includes('consent'));
if (frame) {
let success = true;
try { await frame.waitForSelector(selectors.consentForm, { timeout: 2000 }); } catch (err) { success = false; }
if (success) {
if (DEBUG) print('Submitting consent in iframe')
await frame.$eval(selectors.consentForm, form => form.submit());
}
}
}
async function quit(code = 0, message = '') {
if (message) print(message);
await pup.browser.close();
process.exit(code);
}
async function handleExit() {
await quit(0, 'Closing chrome, good bye!');
}
// ---
(async () => {
process.on('SIGINT', handleExit);
print(`Let's try to play ${chalk.white(URL)}`);
const headless = !SHOW_HEAD;
const args = [
'--autoplay-policy=no-user-gesture-required',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding',
'--disable-crash-reporter',
// youtube will block playback for headless browsers
// for headless detection see: https://intoli.com/blog/making-chrome-headless-undetectable/
'--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0"',
'--use-gl=egl', // linux headless
// linux sound: use `aplay -L` then `speaker-test -Dplug:front -c2` to determine the proper device
// and also, please fuck pulseaudio and all the devices it rode in on, yes, even in 2021
ALSA_DEVICE ? `--alsa-output-device=${ALSA_DEVICE}` : '',
headless ? '--headless' : '' // headless by itself in pup params is NOT enough of course
].filter(x => x);
if (DEBUG) print(`Args:\n${args.join('\n')}`);
const ignoreDefaultArgs = '--mute-audio';
const browser = pup.browser = await puppeteer.launch({ headless, executablePath: BROWSER_PATH, ignoreDefaultArgs, args });
const page = pup.page = await browser.newPage();
await page.goto(URL);
for (let i = 0; i < MAX_TRIES; i++) { // when in doubt, use brute force
print(`Iteration ${chalk.white(i + 1)}/${chalk.white(MAX_TRIES)}...`);
if (DEBUG) page.screenshot({ path: `screen-${i}.png` });
await sleep(3000);
const hasBody = await waitForSelector('pageBody');
if (!hasBody) { print('No body?!'); continue; }
await acceptConsent();
await click('playButton');
await click('gdprButton');
await click('dismissBullshitButton');
await click('noThanksButton');
await click('skipAdButton');
await click('introAgreeButton');
}
print('Done. Press ctrl+c to exit.');
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment