node-apiless-youtube-upload-nc
Version:
Upload videos to Youtube in Node.js without any Youtube API dependency by using Selenium.
203 lines (202 loc) • 9.38 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const selenium_webdriver_1 = require("selenium-webdriver");
const helpers_1 = require("../helpers");
var URL = require('url');
const GOOGLE_URL = `https://google.com`;
const YOUTUBE_STUDIO_URL = `https://studio.youtube.com`;
exports.default = async (url, hs, cookies, vid, headlessMode = true, onProgress = console.log) => {
if (!url)
throw new Error("url not set.");
if (!hs)
throw new Error("hs 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];
};
function getRandom(max, min) {
if (min !== undefined && max != min) {
return Math.floor(min + (max - min) * Math.random());
}
else {
return Math.floor(max * Math.random());
}
}
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 currentUrl = (await driver.getCurrentUrl());
if (!currentUrl.includes('studio.youtube.com/')) {
throw new Error(`Cookies are expired or not valid. (tried to upload, was redirected to ${currentUrl}`);
}
// Open url
if (!url.endsWith('/videos')) {
url += '/videos';
}
onProgress('Opening ' + url);
await driver.get(url);
await driver.sleep(1000);
var items = (await findElements('#items ytd-grid-video-renderer a[id=thumbnail]'));
var vids = [];
for (var i = 0; i < items.length; i++) {
var params = URL.parse(await items[i].getAttribute('href')).search.substr(1).split('&');
for (var j = 0; j < params.length; j++) {
var pair = params[j].split('=');
if (pair[0] == 'v') {
vids.push(pair[1]);
break;
}
}
}
if (vids.length) {
var sn = -1;
for (var i = 0; i < vids.length; i++) {
if (vids[i] == vid) {
sn = i;
break;
}
}
if (sn == 0) {
console.log(`latest video has been remarked!`);
return Promise.resolve(vid);
}
else {
vid = vids[0];
if (sn != -1) {
vid = vids[sn - 1];
}
if (Math.random() < 0.9) {
var url = 'https://www.youtube.com/watch?v=' + vid;
onProgress('Opening ' + url);
await driver.get(url);
await driver.sleep(getRandom(5, 2) * 1000);
var metaEl = await findElement('[id=primary-inner] [id=meta]');
var location = await metaEl.getRect();
onProgress('click like button..');
var likeCnt = getRandom(3, 1);
for (var i = 0; i < likeCnt; i++) {
await driver.executeScript('window.scrollTo(0, arguments[0])', location.y + location.height + 300 * i);
await driver.sleep(getRandom(5, 2) * 1000);
var likeBtns = await findElements('ytd-toggle-button-renderer[id=like-button]');
if (likeBtns.length) {
var likeBtn = likeBtns[getRandom(likeBtns.length, 0)];
await driver.executeScript('arguments[0].scrollIntoViewIfNeeded()', likeBtn);
if ((await likeBtn.getAttribute('class')).indexOf('style-default-active') == -1) {
try {
await likeBtn.click();
}
catch (e) {
console.error('error occur when click like button!');
console.error(e);
}
}
}
await driver.sleep(getRandom(2, 1) * 1000);
await driver.executeScript('window.scrollTo(0, 0)');
await driver.sleep(getRandom(2, 1) * 1000);
}
onProgress('add comment..');
await driver.executeScript('window.scrollTo(0, arguments[0])', location.y + location.height);
await driver.sleep(getRandom(5, 2) * 1000);
onProgress('remark..');
(await findElement('#simplebox-placeholder')).click();
await driver.sleep(1000);
var cts = await findElements('yt-formatted-string[id=content-text]');
if (cts.length) {
var ct = cts[getRandom(cts.length, 0)];
await driver.executeScript('arguments[0].scrollIntoViewIfNeeded()', ct);
try {
var curText = await ct.getText();
var rVal = Math.random();
if (rVal < 0.15) {
hs = curText + ' ' + hs;
}
else if (rVal < 0.3) {
hs = hs + ' ' + curText;
}
else if (rVal < 0.4) {
hs = curText;
}
}
catch (e) {
console.error('error occur when get text!');
console.error(e);
}
}
var inputElem = await findElement('[id=contenteditable-root]');
await driver.executeScript('arguments[0].scrollIntoViewIfNeeded()', inputElem);
await inputElem.sendKeys(hs);
await driver.sleep(getRandom(5, 2) * 1000);
(await findElement('#submit-button [id=button]')).click();
await driver.sleep(getRandom(5, 2) * 1000);
}
else {
console.log('ignore comments!');
}
return Promise.resolve(vid);
}
}
else {
console.log(`videos is empty!`);
return Promise.resolve('');
}
}
catch (e) {
return Promise.resolve('');
}
finally {
if (securityIgnoreInterval)
clearInterval(securityIgnoreInterval);
await driver.quit();
}
};