node-apiless-youtube-upload-nc
Version:
Upload videos to Youtube in Node.js without any Youtube API dependency by using Selenium.
214 lines (181 loc) • 8.71 kB
text/typescript
import {until, By, WebElement} from 'selenium-webdriver'
import fs from 'fs'
import {Cookies, URL, makeWebDriver} from '../helpers'
import {VideoObj} from './uploadVideo'
function getSubstr(str:string, narrow:boolean, len:number){
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 : 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.existsSync(videoObj.videoPath)) throw new Error(`VideoObj: given videoPath doesn't exist on disk (${videoObj.videoPath})`)
if (videoObj.thumbnailPath && !fs.existsSync(videoObj.thumbnailPath)) throw new Error(`VideoObj: given thumbnailPath doesn't exist on disk (${videoObj.thumbnailPath})`)
}
export default async (videoObj : VideoObj, cookies : Cookies, headlessMode = true, onProgress = console.log):Promise<string> => {
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 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 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]
}
try {
onProgress('Settings cookies..')
// Load haokan page to set up cookies
await driver.get(URL.HAOKAN)
// Add cookies
for (let cookie of cookies) await driver.manage().addCookie(cookie)
await driver.sleep(1000)
//refresh
await driver.get(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(URL.HAOKAN_UPLOAD)) {
throw new Error(`Cookies are expired or not valid.`)
}
// Wait for file input to appear
await driver.wait(until.elementsLocated(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(until.elementsLocated(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: URL.HAOKAN_PRODUCT });
}
}
else {
console.error('Fail to publish because could not open product page!');
return JSON.stringify({ status: 1, videoUrl: 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(until.elementsLocated(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();
}
}