Last active
January 22, 2026 23:56
-
-
Save tycoi2005/966e31019a5755788edae0c4515033fc to your computer and use it in GitHub Desktop.
youtube auto save draft :3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // YouTube Studio Draft Video Auto-Publisher - IMPROVED VERSION | |
| (async function() { | |
| 'use strict'; | |
| // Configuration | |
| const CONFIG = { | |
| PLAYLIST_NAME: 'misc', | |
| WAIT_TIME: 2000, | |
| MAX_RETRIES: 5, | |
| SAVE_WAIT_TIME: 4000, | |
| CONTENT_LOAD_TIMEOUT: 15000 // 15 seconds max wait for content to load | |
| }; | |
| // Utility: Wait function | |
| const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)); | |
| // Utility: Wait for element to appear with timeout | |
| const waitForElement = (selector, timeout = 10000) => { | |
| return new Promise((resolve, reject) => { | |
| const existing = document.querySelector(selector); | |
| if (existing) return resolve(existing); | |
| const observer = new MutationObserver(() => { | |
| const element = document.querySelector(selector); | |
| if (element) { | |
| observer.disconnect(); | |
| resolve(element); | |
| } | |
| }); | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| setTimeout(() => { | |
| observer.disconnect(); | |
| reject(new Error(`Timeout waiting for ${selector}`)); | |
| }, timeout); | |
| }); | |
| }; | |
| // Wait for dialog content to fully load | |
| const waitForDialogContent = async () => { | |
| console.log('β³ Waiting for dialog content to load...'); | |
| try { | |
| // Wait for the main content area to have actual content | |
| await waitForElement('ytcp-video-metadata-editor', CONFIG.CONTENT_LOAD_TIMEOUT); | |
| // Additional wait for dynamic content | |
| await wait(2000); | |
| // Verify critical elements are present | |
| const hasTitle = document.querySelector('ytcp-video-title'); | |
| const hasPlaylist = document.querySelector('ytcp-video-metadata-playlists'); | |
| const hasAudience = document.querySelector('ytkc-made-for-kids-select'); | |
| if (!hasTitle || !hasPlaylist || !hasAudience) { | |
| console.warn('β οΈ Some elements missing, waiting longer...'); | |
| await wait(3000); | |
| } | |
| console.log('β Dialog content loaded'); | |
| return true; | |
| } catch (error) { | |
| console.error('β Dialog content failed to load:', error); | |
| return false; | |
| } | |
| }; | |
| // Process single video in edit dialog | |
| const processCurrentVideo = async () => { | |
| try { | |
| console.log('πΉ Processing video in edit mode...'); | |
| // Wait for content to load | |
| const contentLoaded = await waitForDialogContent(); | |
| if (!contentLoaded) { | |
| console.error('β Content failed to load - skipping this video'); | |
| return false; | |
| } | |
| // Step 1: Select playlist "misc" | |
| console.log('Step 1: Setting playlist to "misc"...'); | |
| const playlistTrigger = document.querySelector('ytcp-video-metadata-playlists ytcp-dropdown-trigger'); | |
| if (playlistTrigger) { | |
| playlistTrigger.click(); | |
| await wait(1500); | |
| // Find "misc" checkbox | |
| const checkboxLabels = Array.from(document.querySelectorAll('.checkbox-label .label-text')); | |
| let miscCheckbox = null; | |
| for (let label of checkboxLabels) { | |
| if (label.textContent.trim().toLowerCase() === CONFIG.PLAYLIST_NAME.toLowerCase()) { | |
| const checkboxLi = label.closest('li'); | |
| if (checkboxLi) { | |
| miscCheckbox = checkboxLi.querySelector('ytcp-checkbox-lit #checkbox'); | |
| break; | |
| } | |
| } | |
| } | |
| if (miscCheckbox) { | |
| const isChecked = miscCheckbox.getAttribute('aria-checked') === 'true'; | |
| if (!isChecked) { | |
| console.log(' β Found "misc" playlist, checking...'); | |
| miscCheckbox.click(); | |
| await wait(800); | |
| } else { | |
| console.log(' β "misc" already selected'); | |
| } | |
| playlistTrigger.click(); // Close dropdown | |
| await wait(500); | |
| } else { | |
| console.warn(' β οΈ "misc" playlist not found'); | |
| playlistTrigger.click(); // Close dropdown | |
| await wait(500); | |
| } | |
| } else { | |
| console.warn(' β οΈ Playlist dropdown not found'); | |
| } | |
| // Step 2: Set audience | |
| console.log('Step 2: Setting audience to "Not made for kids"...'); | |
| const notForKidsRadio = document.querySelector('tp-yt-paper-radio-button[name="VIDEO_MADE_FOR_KIDS_NOT_MFK"]'); | |
| if (notForKidsRadio) { | |
| if (!notForKidsRadio.hasAttribute('checked')) { | |
| notForKidsRadio.click(); | |
| console.log(' β Set to "Not made for kids"'); | |
| } else { | |
| console.log(' β Already "Not made for kids"'); | |
| } | |
| await wait(1000); | |
| } else { | |
| console.warn(' β οΈ Audience setting not found'); | |
| } | |
| // Navigate through steps | |
| console.log('Step 3-5: Navigating to Visibility page...'); | |
| // Click Next 3 times to reach Visibility | |
| for (let i = 1; i <= 3; i++) { | |
| await wait(1000); | |
| const nextButton = document.querySelector('#next-button:not([hidden])'); | |
| if (nextButton) { | |
| nextButton.click(); | |
| console.log(` β Clicked Next (${i}/3)`); | |
| await wait(CONFIG.WAIT_TIME); | |
| } else { | |
| console.warn(` β οΈ Next button not found at step ${i}`); | |
| break; | |
| } | |
| } | |
| // Step 6: Set visibility to Private | |
| console.log('Step 6: Setting visibility to "Private"...'); | |
| await wait(2000); | |
| // Try multiple methods to find Private radio button | |
| let privateRadio = null; | |
| let attempts = 0; | |
| const maxAttempts = 3; | |
| while (!privateRadio && attempts < maxAttempts) { | |
| attempts++; | |
| console.log(` Attempt ${attempts}/${maxAttempts} to find Private radio...`); | |
| // Method 1: By name attribute | |
| privateRadio = document.querySelector('tp-yt-paper-radio-button[name="PRIVATE"]'); | |
| // Method 2: By name with different format | |
| if (!privateRadio) { | |
| privateRadio = document.querySelector('tp-yt-paper-radio-button[name="VIDEO_VISIBILITY_PRIVATE"]'); | |
| } | |
| // Method 3: By text content | |
| if (!privateRadio) { | |
| const allRadios = document.querySelectorAll('tp-yt-paper-radio-button'); | |
| for (let radio of allRadios) { | |
| const text = radio.textContent.toLowerCase(); | |
| if (text.includes('private') && !text.includes('publish')) { | |
| privateRadio = radio; | |
| break; | |
| } | |
| } | |
| } | |
| if (!privateRadio && attempts < maxAttempts) { | |
| console.log(' β³ Radio not found, waiting...'); | |
| await wait(2000); | |
| } | |
| } | |
| if (privateRadio) { | |
| privateRadio.click(); | |
| console.log(' β Set to "Private"'); | |
| await wait(1000); | |
| } else { | |
| console.error(' β Private radio button not found after all attempts!'); | |
| console.log(' π Available radio buttons:'); | |
| const allRadios = document.querySelectorAll('tp-yt-paper-radio-button'); | |
| allRadios.forEach((radio, idx) => { | |
| console.log(` ${idx + 1}. name="${radio.getAttribute('name')}" text="${radio.textContent.trim().substring(0, 50)}"`); | |
| }); | |
| // Show error UI and return false | |
| showErrorUI('Private visibility option not found. Please check manually.'); | |
| return false; | |
| } | |
| // Step 7: Save | |
| console.log('Step 7: Saving video...'); | |
| const saveButton = document.querySelector('#done-button:not([hidden])'); | |
| if (saveButton) { | |
| saveButton.click(); | |
| console.log(' β Clicked Save'); | |
| await wait(CONFIG.SAVE_WAIT_TIME); | |
| } else { | |
| console.warn(' β οΈ Save button not found'); | |
| } | |
| console.log('β Video processed successfully!'); | |
| return true; | |
| } catch (error) { | |
| console.error('β Error processing video:', error); | |
| showErrorUI('An error occurred: ' + error.message); | |
| return false; | |
| } | |
| }; | |
| // Show error UI | |
| const showErrorUI = (message) => { | |
| const errorSection = document.querySelector('ytcp-error-section'); | |
| if (errorSection) { | |
| errorSection.removeAttribute('hidden'); | |
| const errorMessage = errorSection.querySelector('#error-message'); | |
| if (errorMessage) { | |
| errorMessage.textContent = message; | |
| } | |
| } | |
| // Also show as alert | |
| alert('β οΈ Automation Error\n\n' + message + '\n\nThe automation will stop. Please check the video manually.'); | |
| }; | |
| // Main function: Process all draft videos | |
| const processAllDrafts = async () => { | |
| console.log('π Starting YouTube Studio Draft Video Automation...'); | |
| console.log('βοΈ Configuration:', CONFIG); | |
| console.log('ββββββββββββββββββββββββββββββββββββββββ\n'); | |
| let processedCount = 0; | |
| let errorCount = 0; | |
| let consecutiveErrors = 0; | |
| const MAX_CONSECUTIVE_ERRORS = 3; | |
| while (true) { | |
| const editDraftButtons = Array.from(document.querySelectorAll('.edit-draft-button')) | |
| .filter(btn => !btn.hidden && btn.offsetParent !== null); | |
| if (editDraftButtons.length === 0) { | |
| console.log('\nπ All done! No more draft videos found.'); | |
| console.log(`π Summary: ${processedCount} processed, ${errorCount} errors`); | |
| break; | |
| } | |
| console.log(`\nπ Found ${editDraftButtons.length} draft video(s) remaining`); | |
| console.log(`πΉ Processing draft #${processedCount + 1}...`); | |
| console.log('ββββββββββββββββββββββββββββββββββββββββ'); | |
| // Click first draft button | |
| editDraftButtons[0].click(); | |
| console.log('β Opened edit dialog'); | |
| await wait(CONFIG.WAIT_TIME); | |
| // Process video | |
| const success = await processCurrentVideo(); | |
| if (success) { | |
| processedCount++; | |
| consecutiveErrors = 0; | |
| } else { | |
| errorCount++; | |
| consecutiveErrors++; | |
| if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { | |
| console.error(`\nβ ${MAX_CONSECUTIVE_ERRORS} consecutive errors. Stopping automation.`); | |
| showErrorUI(`Too many consecutive errors (${MAX_CONSECUTIVE_ERRORS}). Automation stopped.`); | |
| break; | |
| } | |
| } | |
| // Wait and close dialog if still open | |
| await wait(CONFIG.SAVE_WAIT_TIME); | |
| const closeButton = document.querySelector('#close-button'); | |
| if (closeButton && closeButton.offsetParent !== null) { | |
| console.log('β οΈ Closing dialog manually...'); | |
| closeButton.click(); | |
| await wait(CONFIG.WAIT_TIME); | |
| } | |
| console.log('ββββββββββββββββββββββββββββββββββββββββ'); | |
| await wait(1500); | |
| } | |
| console.log('\n⨠Automation complete!'); | |
| }; | |
| // Start automation | |
| const editDraftButtons = document.querySelectorAll('.edit-draft-button'); | |
| if (editDraftButtons.length > 0) { | |
| console.log('π Detected video list page'); | |
| const confirmed = confirm( | |
| `Found ${editDraftButtons.length} draft video(s).\n\n` + | |
| `This will automatically:\n` + | |
| `1. Set playlist to "misc"\n` + | |
| `2. Set audience to "Not made for kids"\n` + | |
| `3. Set visibility to "Private"\n` + | |
| `4. Save each video\n\n` + | |
| `The script will stop if it encounters 3 consecutive errors.\n\n` + | |
| `Continue?` | |
| ); | |
| if (confirmed) { | |
| await processAllDrafts(); | |
| } else { | |
| console.log('β Cancelled by user'); | |
| } | |
| } else if (document.querySelector('ytcp-uploads-dialog')) { | |
| console.log('π Processing single video'); | |
| await processCurrentVideo(); | |
| } else { | |
| console.error('β Please run on YouTube Studio Content page'); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment