Skip to content

Instantly share code, notes, and snippets.

@tycoi2005
Last active January 22, 2026 23:56
Show Gist options
  • Select an option

  • Save tycoi2005/966e31019a5755788edae0c4515033fc to your computer and use it in GitHub Desktop.

Select an option

Save tycoi2005/966e31019a5755788edae0c4515033fc to your computer and use it in GitHub Desktop.
youtube auto save draft :3
// 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