Niedzwiedzw / youtube-publish-drafts

Publish all your draft videos without clicking by using javascript
MIT License
268 stars 121 forks source link

Publish Draft to Unlisted #28

Open mrajayjose opened 1 year ago

mrajayjose commented 1 year ago

Hi,

I have been trying to move from 'draft" to "unlisted" state for several videos in the content pages. To do this I have changed the config in the script to const VISIBILITY = ‘Unlisted’; // 'Public' / 'Private' / 'Unlisted'. When I paste this in the console, I see an error "Uncaught SyntaxError: Invalid or unexpected token". Please can you advice what needs to be done to fix this.

Thanks a mill!

Below is the script I've used:

(() => { // ----------------------------------------------------------------- // CONFIG (you're safe to edit this) // ----------------------------------------------------------------- // ~ GLOBAL CONFIG // ----------------------------------------------------------------- const MODE = ‘edit_drafts'; // 'publish_drafts' / 'sort_playlist'; const DEBUG_MODE = true; // true / false, enable for more context // ----------------------------------------------------------------- // ~ PUBLISH CONFIG // ----------------------------------------------------------------- const MADE_FOR_KIDS = false; // true / false; const VISIBILITY = ‘Unlisted’ // 'Public' / 'Private' / 'Unlisted' // ----------------------------------------------------------------- // ~ SORT PLAYLIST CONFIG // ----------------------------------------------------------------- const SORTING_KEY = (one, other) => { return one.name.localeCompare(other.name, undefined, {numeric: true, sensitivity: 'base'}); }; // END OF CONFIG (not safe to edit stuff below) // -----------------------------------------------------------------

// Art by Joan G. Stark
// .'"'.        ___,,,___        .'``.
// : (\  `."'"```         ```"'"-'  /) ;
//  :  \                         `./  .'
//   `.                            :.'
//     /        _         _        \
//    |         0}       {0         |
//    |         /         \         |
//    |        /           \        |
//    |       /             \       |
//     \     |      .-.      |     /
//      `.   | . . /   \ . . |   .'
//        `-._\.'.(     ).'./_.-'
//            `\'  `._.'  '/'
//              `. --'-- .'
//                `-...-'

// ----------------------------------
// COMMON  STUFF
// ---------------------------------
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');
}

// ----------------------------------
// PUBLISH STUFF
// ----------------------------------
const VISIBILITY_PUBLISH_ORDER = {
    'Private': 0,
    'Unlisted': 1,
    'Public': 2,
};

// SELECTORS
// ---------
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);
    }
}

// ----------------------------------
// SORTING STUFF
// ----------------------------------
const SORTING_MENU_BUTTON_SELECTOR = 'button';
const SORTING_ITEM_MENU_SELECTOR = 'tp-yt-paper-listbox#items';
const SORTING_ITEM_MENU_ITEM_SELECTOR = 'ytd-menu-service-item-renderer';
const MOVE_TO_TOP_INDEX = 4;
const MOVE_TO_BOTTOM_INDEX = 5;

class SortingDialog {
    constructor(raw) {
        this.raw = raw;
    }

    async anyMenuItem() {
        const item =  await waitForElement(SORTING_ITEM_MENU_ITEM_SELECTOR, this.raw);
        if (item === null) {
            throw new Error("could not locate any menu item");
        }
        return item;
    }

    menuItems() {
        return [...this.raw.querySelectorAll(SORTING_ITEM_MENU_ITEM_SELECTOR)];
    }

    async moveToTop() {
        click(this.menuItems()[MOVE_TO_TOP_INDEX]);
    }

    async moveToBottom() {
        click(this.menuItems()[MOVE_TO_BOTTOM_INDEX]);
    }
}
class PlaylistVideo {
    constructor(raw) {
        this.raw = raw;
    }
    get name() {
        return this.raw.querySelector('#video-title').textContent;
    }
    async dialog() {
        return this.raw.querySelector(SORTING_MENU_BUTTON_SELECTOR);
    }

    async openDialog() {
        click(await this.dialog());
        const dialog = new SortingDialog(await waitForElement(SORTING_ITEM_MENU_SELECTOR));
        await dialog.anyMenuItem();
        return dialog;
    }

}
async function playlistVideos() {
    return [...document.querySelectorAll('ytd-playlist-video-renderer')]
        .map((el) => new PlaylistVideo(el));
}
async function sortPlaylist() {
    debugLog('sorting playlist');
    const videos = await playlistVideos();
    debugLog(`found ${videos.length} videos`);
    videos.sort(SORTING_KEY);
    const videoNames = videos.map((v) => v.name);

    let index = 1;
    for (let name of videoNames) {
        debugLog({index, name});
        const video = videos.find((v) => v.name === name);
        const dialog = await video.openDialog();
        await dialog.moveToBottom();
        await sleep(1000);
        index += 1;
    }

}

// ----------------------------------
// ENTRY POINT
// ----------------------------------
({
    'publish_drafts': publishDrafts,
    'sort_playlist': sortPlaylist,
})[MODE]();

})();

ajsnyde commented 9 months ago

Same