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
JavaScript
;
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();
}
};