UNPKG

node-apiless-youtube-upload-nc

Version:

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

196 lines (195 loc) 9.14 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const selenium_webdriver_1 = require("selenium-webdriver"); const fs_1 = __importDefault(require("fs")); const helpers_1 = require("../helpers"); function getSubstr(str, narrow, len) { if (!narrow) { return str.substr(0, len); } else { var ret = ''; for (var i = 0; i < str.length; i++) { if (str.substr(0, i + 1).replace(/[\u4e00-\u9fa5]/g, '**').length <= len) { ret = str.substr(0, i + 1); } else { break; } } return ret; } } const validateVideoObj = (videoObj) => { if (!videoObj.videoPath) throw new Error("VideoObj: missing required property videoPath"); if (!videoObj.title) throw new Error("VideoObj: missing required property title"); var titleLen = videoObj.title.replace(/[\u4e00-\u9fa5]/g, '**').length; if (titleLen < 16) throw new Error("VideoObj: given title is shorter than min 16 characters"); if (titleLen > 40) { console.log("VideoObj: given title is longer than max 40 characters"); videoObj.title = getSubstr(videoObj.title, true, 40); } if (videoObj.description.length > 100) { console.log("VideoObj: given description is longer than max 100 characters"); videoObj.description = getSubstr(videoObj.description, false, 100); } if (!fs_1.default.existsSync(videoObj.videoPath)) throw new Error(`VideoObj: given videoPath doesn't exist on disk (${videoObj.videoPath})`); if (videoObj.thumbnailPath && !fs_1.default.existsSync(videoObj.thumbnailPath)) throw new Error(`VideoObj: given thumbnailPath doesn't exist on disk (${videoObj.thumbnailPath})`); }; exports.default = async (videoObj, cookies, headlessMode = true, onProgress = console.log) => { if (!cookies || !cookies.length) throw new Error("Can't upload video: cookies not set."); validateVideoObj(videoObj); // Fill default values videoObj = { description: '', category: '无', ...videoObj }; 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 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]; }; try { onProgress('Settings cookies..'); // Load haokan page to set up cookies await driver.get(helpers_1.URL.HAOKAN); // Add cookies for (let cookie of cookies) await driver.manage().addCookie(cookie); await driver.sleep(1000); //refresh await driver.get(helpers_1.URL.HAOKAN); await driver.sleep(1000); onProgress('redirect to upload page..'); await (await findElement('.icon-upload')).click(); var windows = await driver.getAllWindowHandles(); if (windows.length == 2) { await driver.switchTo().window(windows[1]); } // Wait for stuff to fully load await driver.sleep(1000 * 5); // Check whether user is still in login state (which is the case if cookies are not valid / are expired) if (!(await driver.getCurrentUrl()).includes(helpers_1.URL.HAOKAN_UPLOAD)) { throw new Error(`Cookies are expired or not valid.`); } // Wait for file input to appear await driver.wait(selenium_webdriver_1.until.elementsLocated(selenium_webdriver_1.By.css("input[type=file]")), 1000 * 10); onProgress('Initializing video..'); // Enter file path await (await findElement("input[type=file]")).sendKeys(videoObj.videoPath); // Wait for file metadata to upload await driver.wait(selenium_webdriver_1.until.elementsLocated(selenium_webdriver_1.By.css(".text-success")), 1000 * 60 * 5); var uploadVideoSucText = await findElement('.text-success'); if (uploadVideoSucText) { // Wait for random javascript garbage to load await driver.sleep(1000 * 2); onProgress('Initializing title and description..'); // Enter title and description var editBoxes = await findElements('.hk-input'); var titleBox = editBoxes[0]; var descriptionBox = editBoxes[1]; await enterEmojiString(titleBox, videoObj.title); await enterEmojiString(descriptionBox, videoObj.description); await driver.sleep(1000); onProgress('Entering custom thumbnail..'); async function onUploadThumbnail() { await (await findElement('.btn-publish')).click(); await driver.sleep(1000 * 10); if (!(await tryFindElement('.btn-publish'))) { await (await findElement('p.product')).click(); await driver.sleep(1000 * 5); if (await tryFindElement('.v-title') && (await (await findElement('.v-title')).getText()) == videoObj.title) { await (await findElement('.v-title')).click(); var windows = await driver.getAllWindowHandles(); if (windows.length == 3) { await driver.switchTo().window(windows[2]); await driver.sleep(1000 * 5); return JSON.stringify({ status: 1, videoUrl: await driver.getCurrentUrl() }); } else { console.error('Fail to publish because could not get url!'); return JSON.stringify({ status: 1, videoUrl: helpers_1.URL.HAOKAN_PRODUCT }); } } else { console.error('Fail to publish because could not open product page!'); return JSON.stringify({ status: 1, videoUrl: helpers_1.URL.HAOKAN_PRODUCT }); } } else { throw new Error(`Fail to publish because publish button doesn't disappear!`); } } // Enter custom thumbnail if (videoObj.thumbnailPath) { await (await findElement(".click-area")).click(); await driver.sleep(1000); await (await findElements(".modal-header-tab"))[1].click(); var thumbnailBox = await findElement('.image-uploader-input'); await thumbnailBox.sendKeys(videoObj.thumbnailPath); await driver.wait(selenium_webdriver_1.until.elementsLocated(selenium_webdriver_1.By.css(".btn-submit")), 1000 * 10); var thumbFinBtn = await findElement('.btn-submit'); if (thumbFinBtn) { thumbFinBtn.click(); await driver.sleep(1000 * 2); return await onUploadThumbnail(); } else { throw new Error('Fail to upload thumbnail!'); } } else { return await onUploadThumbnail(); } } else { throw new Error('Fail to upload video!'); } } catch (e) { return Promise.resolve(e); } finally { await driver.quit(); } };