UNPKG

node-apiless-youtube-upload-nc

Version:

Upload videos to Youtube in Node.js without any Youtube API dependency by using Selenium.

218 lines (192 loc) 9.22 kB
import {until, By, WebElement} from 'selenium-webdriver' import {Cookies, makeWebDriver} from '../helpers' var URL = require('url'); const GOOGLE_URL = `https://google.com`; const YOUTUBE_STUDIO_URL = `https://studio.youtube.com`; export default async (url : string, hs : string, cookies : Cookies, vid : string, headlessMode = true, onProgress = console.log):Promise<string> => { if(!url) throw new Error("url not set.") if(!hs) throw new Error("hs not set.") if (!cookies || !cookies.length) throw new Error("Can't check video copyright: cookies not set.") const driver = await makeWebDriver({headless: headlessMode, fullsize: true}) const enterEmojiString = async (webElement : WebElement, string : string) => { // sendKeys(string) doesn't support emojis (causes a crash) // youtube custom input elements don't have "value" property (but webEl.clear() still works) // clipboard hack works but not in headless mode // also editing innerHTML causes racing conditions with the underlying javascript mechanism // solution is to use an obsolete method document.execCommand('insreText') await driver.sleep(500) webElement.click() await driver.sleep(250) webElement.clear() await driver.sleep(250) await driver.executeScript(` arguments[0].focus(); document.execCommand('insertText', false, arguments[1]); `, webElement, string) } const ensureNoSecurityWarning = async () => { await driver.executeScript("if (document.querySelector('ytcp-auth-confirmation-dialog')) document.querySelector('ytcp-auth-confirmation-dialog').remove()") } const findElements = async (cssSelector : string) => { var webEls = await driver.findElements(By.css(cssSelector)) if (webEls[0]) await driver.executeScript('arguments[0].scrollIntoViewIfNeeded()', webEls[0]) return webEls } const findElement = async (cssSelector : string) => { var els = await findElements(cssSelector) if (els.length === 0) throw new Error(`Element was not found with selector '${cssSelector}'`) return els[0] } const tryFindElement = async (cssSelector : string) => { var els = await findElements(cssSelector) if (els.length == 0) return false return els[0] } function getRandom(max, min){ if(min!==undefined&&max!=min){ return Math.floor(min + (max -min) *Math.random()); } else{ return Math.floor(max*Math.random()); } } var securityIgnoreInterval = null try { onProgress('Settings cookies..') // Load google page to set up cookies await driver.get(GOOGLE_URL) // Add cookies for (let cookie of cookies) await driver.manage().addCookie(cookie) onProgress('Opening Youtube Studio..') // Open Youtube Studio page await driver.get(YOUTUBE_STUDIO_URL); // Wait for stuff to fully load await driver.sleep(1000) securityIgnoreInterval = setInterval(() => { ensureNoSecurityWarning() }, 500) // Check if url is still studio.youtube.com and not accounts.google.com (which is the case if cookies are not valid / are expired) var currentUrl = (await driver.getCurrentUrl()) if (!currentUrl.includes('studio.youtube.com/')) { throw new Error(`Cookies are expired or not valid. (tried to upload, was redirected to ${currentUrl}`) } // Open url if(!url.endsWith('/videos')){ url += '/videos'; } onProgress('Opening ' + url) await driver.get(url); await driver.sleep(1000); var items = (await findElements('#items ytd-grid-video-renderer a[id=thumbnail]')); var vids = []; for(var i = 0; i < items.length; i++){ var params = URL.parse(await items[i].getAttribute('href')).search.substr(1).split('&'); for(var j = 0; j < params.length; j++){ var pair = params[j].split('='); if(pair[0]=='v'){ vids.push(pair[1]); break; } } } if(vids.length){ var sn = -1; for(var i = 0; i < vids.length; i++){ if(vids[i]==vid){ sn = i; break; } } if(sn==0){ console.log(`latest video has been remarked!`); return Promise.resolve(vid); } else { vid = vids[0]; if (sn != -1) { vid = vids[sn - 1]; } if(Math.random()<0.9){ var url = 'https://www.youtube.com/watch?v=' + vid; onProgress('Opening ' + url); await driver.get(url); await driver.sleep(getRandom(5, 2)*1000); var metaEl = await findElement('[id=primary-inner] [id=meta]'); var location = await metaEl.getRect(); onProgress('click like button..'); var likeCnt = getRandom(3, 1); for(var i = 0; i < likeCnt; i++){ await driver.executeScript('window.scrollTo(0, arguments[0])', location.y+location.height+300*i); await driver.sleep(getRandom(5, 2)*1000); var likeBtns = await findElements('ytd-toggle-button-renderer[id=like-button]'); if(likeBtns.length){ var likeBtn = likeBtns[getRandom(likeBtns.length, 0)]; await driver.executeScript('arguments[0].scrollIntoViewIfNeeded()', likeBtn); if((await likeBtn.getAttribute('class')).indexOf('style-default-active')==-1){ try{ await likeBtn.click(); } catch(e){ console.error('error occur when click like button!'); console.error(e); } } } await driver.sleep(getRandom(2, 1)*1000); await driver.executeScript('window.scrollTo(0, 0)'); await driver.sleep(getRandom(2, 1)*1000); } onProgress('add comment..'); await driver.executeScript('window.scrollTo(0, arguments[0])', location.y+location.height); await driver.sleep(getRandom(5, 2) * 1000); onProgress('remark..'); (await findElement('#simplebox-placeholder')).click() await driver.sleep(1000); var cts = await findElements('yt-formatted-string[id=content-text]'); if(cts.length){ var ct = cts[getRandom(cts.length, 0)]; await driver.executeScript('arguments[0].scrollIntoViewIfNeeded()', ct); try{ var curText = await ct.getText(); var rVal = Math.random(); if(rVal<0.15){ hs = curText + ' ' + hs; } else if(rVal<0.3){ hs = hs + ' ' + curText; } else if(rVal<0.4){ hs = curText; } } catch(e){ console.error('error occur when get text!'); console.error(e); } } var inputElem = await findElement('[id=contenteditable-root]'); await driver.executeScript('arguments[0].scrollIntoViewIfNeeded()', inputElem); await inputElem.sendKeys(hs); await driver.sleep(getRandom(5, 2)*1000); (await findElement('#submit-button [id=button]')).click(); await driver.sleep(getRandom(5, 2)*1000); } else{ console.log('ignore comments!'); } return Promise.resolve(vid); } } else{ console.log(`videos is empty!`); return Promise.resolve(''); } } catch(e){ return Promise.resolve(''); } finally { if (securityIgnoreInterval) clearInterval(securityIgnoreInterval) await driver.quit(); } }