Skip to content

Instantly share code, notes, and snippets.

@jossef
Created January 28, 2023 21:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jossef/0911c48d012817d61e27ef313fb9e2ae to your computer and use it in GitHub Desktop.
Save jossef/0911c48d012817d61e27ef313fb9e2ae to your computer and use it in GitHub Desktop.
a fork of niedzwiedzw/youtube-publish-drafts
(() => {
const MODE = 'publish_drafts';
const DEBUG_MODE = true;
const MADE_FOR_KIDS = false;
const VISIBILITY = 'Unlisted';
const SORTING_KEY = (one, other) => {
return one.name.localeCompare(other.name, undefined, {numeric: true, sensitivity: 'base'});
};
const TIMEOUT_STEP_MS = 20;
const DEFAULT_ELEMENT_TIMEOUT_MS = 10000;
function debugLog(...args) {
if (!DEBUG_MODE) {
return;
}
console.debug(...args);
}
const sleep = (ms) => new Promise((resolve, _) => setTimeout(resolve, ms));
async function waitForElement(selector, baseEl, timeoutMs) {
if (timeoutMs === undefined) {
timeoutMs = DEFAULT_ELEMENT_TIMEOUT_MS;
}
if (baseEl === undefined) {
baseEl = document;
}
let timeout = timeoutMs;
while (timeout > 0) {
let element = baseEl.querySelector(selector);
if (element !== null) {
return element;
}
await sleep(TIMEOUT_STEP_MS);
timeout -= TIMEOUT_STEP_MS;
}
debugLog(`could not find ${selector} inside`, baseEl);
return null;
}
function click(element) {
const event = document.createEvent('MouseEvents');
event.initMouseEvent('mousedown', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
element.click();
debugLog(element, 'clicked');
}
const VISIBILITY_PUBLISH_ORDER = {
'Private': 0,
'Unlisted': 1,
'Public': 2,
};
const VIDEO_ROW_SELECTOR = 'ytcp-video-row';
const DRAFT_MODAL_SELECTOR = '.style-scope.ytcp-uploads-dialog';
const DRAFT_BUTTON_SELECTOR = '.edit-draft-button';
const MADE_FOR_KIDS_SELECTOR = '#made-for-kids-group';
const RADIO_BUTTON_SELECTOR = 'tp-yt-paper-radio-button';
const VISIBILITY_STEPPER_SELECTOR = '#step-badge-3';
const VISIBILITY_PAPER_BUTTONS_SELECTOR = 'tp-yt-paper-radio-group';
const SAVE_BUTTON_SELECTOR = '#done-button';
const SUCCESS_ELEMENT_SELECTOR = 'ytcp-video-thumbnail-with-info';
const DIALOG_SELECTOR = 'ytcp-dialog.ytcp-video-share-dialog > tp-yt-paper-dialog:nth-child(1)';
const DIALOG_CLOSE_BUTTON_SELECTOR = 'tp-yt-iron-icon';
class SuccessDialog {
constructor(raw) {
this.raw = raw;
}
async closeDialogButton() {
return await waitForElement(DIALOG_CLOSE_BUTTON_SELECTOR, this.raw);
}
async close() {
click(await this.closeDialogButton());
await sleep(50);
debugLog('closed');
}
}
class VisibilityModal {
constructor(raw) {
this.raw = raw;
}
async radioButtonGroup() {
return await waitForElement(VISIBILITY_PAPER_BUTTONS_SELECTOR, this.raw);
}
async visibilityRadioButton() {
const group = await this.radioButtonGroup();
const value = VISIBILITY_PUBLISH_ORDER[VISIBILITY];
return [...group.querySelectorAll(RADIO_BUTTON_SELECTOR)][value];
}
async setVisibility() {
click(await this.visibilityRadioButton());
debugLog(`visibility set to ${VISIBILITY}`);
await sleep(50);
}
async saveButton() {
return await waitForElement(SAVE_BUTTON_SELECTOR, this.raw);
}
async isSaved() {
await waitForElement(SUCCESS_ELEMENT_SELECTOR, document);
}
async dialog() {
return await waitForElement(DIALOG_SELECTOR);
}
async save() {
click(await this.saveButton());
await this.isSaved();
debugLog('saved');
const dialogElement = await this.dialog();
const success = new SuccessDialog(dialogElement);
return success;
}
}
class DraftModal {
constructor(raw) {
this.raw = raw;
}
async madeForKidsToggle() {
return await waitForElement(MADE_FOR_KIDS_SELECTOR, this.raw);
}
async madeForKidsPaperButton() {
const nthChild = MADE_FOR_KIDS ? 1 : 2;
return await waitForElement(`${RADIO_BUTTON_SELECTOR}:nth-child(${nthChild})`, this.raw);
}
async selectMadeForKids() {
click(await this.madeForKidsPaperButton());
await sleep(50);
debugLog(`"Made for kids" set as ${MADE_FOR_KIDS}`);
}
async visibilityStepper() {
return await waitForElement(VISIBILITY_STEPPER_SELECTOR, this.raw);
}
async goToVisibility() {
debugLog('going to Visibility');
await sleep(50);
click(await this.visibilityStepper());
const visibility = new VisibilityModal(this.raw);
await sleep(50);
await waitForElement(VISIBILITY_PAPER_BUTTONS_SELECTOR, visibility.raw);
return visibility;
}
}
class VideoRow {
constructor(raw) {
this.raw = raw;
}
get editDraftButton() {
return waitForElement(DRAFT_BUTTON_SELECTOR, this.raw, 20);
}
async openDraft() {
debugLog('focusing draft button');
click(await this.editDraftButton);
return new DraftModal(await waitForElement(DRAFT_MODAL_SELECTOR));
}
}
function allVideos() {
return [...document.querySelectorAll(VIDEO_ROW_SELECTOR)].map((el) => new VideoRow(el));
}
async function editableVideos() {
let editable = [];
for (let video of allVideos()) {
if ((await video.editDraftButton) !== null) {
editable = [...editable, video];
}
}
return editable;
}
async function publishDrafts() {
const videos = await editableVideos();
debugLog(`found ${videos.length} videos`);
debugLog('starting in 1000ms');
await sleep(1000);
for (let video of videos) {
const draft = await video.openDraft();
debugLog({
draft
});
await draft.selectMadeForKids();
const visibility = await draft.goToVisibility();
await visibility.setVisibility();
const dialog = await visibility.save();
await dialog.close();
await sleep(100);
}
}
publishDrafts();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment