UNPKG

node-apiless-youtube-upload-nc

Version:

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

183 lines (182 loc) 9.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const selenium_webdriver_1 = require("selenium-webdriver"); const helpers_1 = require("../helpers"); const GOOGLE_URL = `https://google.com`; const YOUTUBE_STUDIO_URL = `https://studio.youtube.com`; exports.default = async (vid, cookies, headlessMode = true, onProgress = console.log) => { 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 (0, helpers_1.makeWebDriver)({ headless: headlessMode, fullsize: true }); const enterEmojiString = async (webElement, 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) => { var webEls = await driver.findElements(selenium_webdriver_1.By.css(cssSelector)); if (webEls[0]) await driver.executeScript('arguments[0].scrollIntoViewIfNeeded()', webEls[0]); return webEls; }; const findElement = async (cssSelector) => { 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) => { 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(selenium_webdriver_1.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(selenium_webdriver_1.By.css('[id=thumbnail-anchor]')); if (!anchors.length) { anchors = await videoRows[i].findElements(selenium_webdriver_1.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(selenium_webdriver_1.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(selenium_webdriver_1.By.css('[role=cell]')))[2].findElements(selenium_webdriver_1.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(selenium_webdriver_1.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(selenium_webdriver_1.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(); } };