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