UNPKG

node-apiless-youtube-upload-nc

Version:

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

197 lines (174 loc) 9.43 kB
import {until, By, WebElement} from 'selenium-webdriver' import fs from 'fs' import {Cookies, makeWebDriver} from '../helpers' const GOOGLE_URL = `https://google.com`; const YOUTUBE_STUDIO_URL = `https://studio.youtube.com`; export default async (vid : string, cookies : Cookies, headlessMode = true, onProgress = console.log):Promise<string> => { if(!vid) throw new Error("vid 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] } 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 url = (await driver.getCurrentUrl()) if (!url.includes('studio.youtube.com/')) { throw new Error(`Cookies are expired or not valid. (tried to upload, was redirected to ${url}`) } onProgress('find video...'); //hack:hide tp-yt-iron-overlay-backdrop var backdrops = await driver.findElements(By.css("tp-yt-iron-overlay-backdrop")); if(backdrops){ for(var i = 0; i < backdrops.length; i++){ await driver.executeScript('arguments[0].style.height="0px";', backdrops[i]); } } await (await findElement('[id=menu-paper-icon-item-1]')).click() await driver.sleep(10*1000); var videoRow = null; await driver.executeScript('arguments[0].click()', await findElement('#chip-bar .text-input')); await driver.sleep(1000); await (await findElement(`[test-id='{"filterName":"VISIBILITY"}']`)).click(); await driver.sleep(1000); await (await findElement('[test-id=PRIVATE]')).click(); await driver.sleep(1000); await (await findElement('[test-id=DRAFT]')).click(); await driver.sleep(1000); await (await findElement('#apply-button')).click(); await driver.sleep(10*1000); var videoRows = (await findElements('#main [id=row-container]')); for(var i = 0; i < videoRows.length; i++){ var anchors = await videoRows[i].findElements(By.css('[id=thumbnail-anchor]')); if(!anchors.length){ anchors = await videoRows[i].findElements(By.css('[id=anchor-watch-on-yt]')); } if(anchors.length&&(await anchors[0].getAttribute('href')).indexOf(vid)!=-1){ await driver.executeScript('arguments[0].scrollIntoViewIfNeeded()', videoRows[i]); videoRow = videoRows[i]; break; } } var ret = ''; if(videoRow){ var restrictElem = (await videoRow.findElements(By.css('.restrictions-list .ytcp-video-row')))[0]; var copyrightTip = (await restrictElem.getAttribute('innerHTML')).replace(/<!--[\s\S]*-->/g, ''); onProgress('限制:'+copyrightTip); function checkText(val, vals){ for(var v of vals){ if(val.indexOf(v)!=-1){ return true; } } return false; } if(checkText(copyrightTip, ['无', '無', '没有', 'None'])){ onProgress('set video status public...'); var statusBtn = (await (await videoRow.findElements(By.css('[role=cell]')))[2].findElements(By.css('.icon-text-edit-triangle-wrap')))[0]; await driver.executeScript(`var evObj = document.createEvent('MouseEvents');evObj.initEvent('mouseover', true, false);arguments[0].dispatchEvent(evObj)`, statusBtn) await driver.sleep(1000); await driver.executeScript('arguments[0].click()', statusBtn); await driver.sleep(1000); (await findElements('#dialog [name=PUBLIC]'))[0].click(); await driver.sleep(1000); (await findElements('#dialog [id=save-button]'))[0].click(); ret = JSON.stringify({status:1}); } else if(checkText(copyrightTip, ['版权主张', '版權聲明', 'Copyright claim'])){ onProgress('get copyinfo detail...'); await driver.executeScript(`var evObj = document.createEvent('MouseEvents');evObj.initEvent('mouseover', true, false);arguments[0].dispatchEvent(evObj)`, restrictElem); await driver.sleep(1000); await driver.executeScript(`var evObj = document.createEvent('MouseEvents');evObj.initEvent('mouseenter', true, false);arguments[0].dispatchEvent(evObj)`, restrictElem); await driver.sleep(1000); (await findElement('#tooltip ytcp-button')).click(); await driver.sleep(10*1000); var rows = (await findElements('#dialog ytcr-claim-row')); var copyinfo = []; for(var i = 0; i < rows.length; i++){ await rows[i].click(); await driver.sleep(1000); copyinfo.push(await (await rows[i].findElements(By.css('button.ytcr-claim-list-cell-match-segments')))[0].getText()); } if(copyinfo.length){ onProgress('delete video...'); (await findElements('#dialog .ytcr-copyright-section ytcp-icon-button.close-button'))[0].click(); await driver.sleep(1000); await driver.executeScript(`var evObj = document.createEvent('MouseEvents');evObj.initEvent('mouseover', true, false);arguments[0].dispatchEvent(evObj)`, videoRow); await driver.sleep(1000); await (await videoRow.findElements(By.css('ytcp-icon-button.open-menu-button')))[0].click(); await driver.sleep(1000); (await findElements('#dialog [test-id=delete]'))[0].click(); await driver.sleep(1000); (await findElements('#delete-dialog [id=checkbox-container]'))[0].click(); await driver.sleep(1000); (await findElements('#delete-dialog [id=confirm-button]'))[0].click(); ret = JSON.stringify({state:1, copyinfo:JSON.stringify(copyinfo), cropstate:0}); } else{ throw new Error('not find copyright info!'); } } else{ onProgress('nothing to do...'); ret = JSON.stringify({status:2}); } } else{ throw new Error('not find a video!'); } await driver.sleep(2*1000); return Promise.resolve(ret); } catch(e){ return Promise.resolve(e); } finally { if (securityIgnoreInterval) clearInterval(securityIgnoreInterval) await driver.quit(); } }