// ==UserScript==
// @name YouTube Enchantments
// @namespace Based on YouTube Auto-Liker by HatScripts and Youtube Auto Scroll Down
// @version 0.5
// @description Automatically likes videos of channels you're subscribed to, scrolls down on Youtube with a toggle button, and bypasses the AdBlock ban.
// @author JJJ
// @match https://www.youtube.com/*
// @exclude https://www.youtube.com/*/community
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-idle
// @noframes
// @license MIT
// ==/UserScript==
(() => {
'use strict';
// Selectors for various YouTube elements
const SELECTORS = {
PLAYER: '#movie_player',
SUBSCRIBE_BUTTON: '#subscribe-button > ytd-subscribe-button-renderer, ytd-reel-player-overlay-renderer #subscribe-button',
LIKE_BUTTON: '#menu .YtLikeButtonViewModelHost button, #segmented-like-button button, #like-button button',
DISLIKE_BUTTON: '#menu .YtDislikeButtonViewModelHost button, #segmented-dislike-button button, #dislike-button button'
};
const PLAYER_CONTAINER_SELECTOR = '#player-container-outer';
const PLAYABILITY_ERROR_SELECTOR = 'yt-playability-error-supported-renderers';
const IFRAME_ID = 'adblock-bypass-player';
// Variable to keep track of whether the settings dialog is open
let settingsDialogOpen = false;
// Set to store video IDs that have been auto-liked
const autoLikedVideoIds = new Set();
let isScrolling = false;
let scrollInterval;
let currentVideoId = '';
// Default settings for the script
const defaultSettings = {
autoLikeEnabled: true,
autoLikeLiveStreams: false,
likeIfNotSubscribed: false,
watchThreshold: 0,
checkFrequency: 5000,
debugMode: false
};
// Load settings from storage or use default settings
const settings = loadSettings();
// Function to load settings from storage or use default settings
function loadSettings() {
const savedSettings = GM_getValue('settings', null);
return savedSettings ? Object.assign({}, defaultSettings, savedSettings) : defaultSettings;
}
// Function to save settings to storage
function saveSettings() {
GM_setValue('settings', settings);
}
// Function to toggle a specific setting
function toggleSetting(settingName) {
if (settingName === 'watchThreshold') {
showWatchThresholdDropdown();
} else {
settings[settingName] = !settings[settingName];
saveSettings();
}
}
// Function called when the script is initialized
function onInit() {
const DEBUG = new Debugger(GM_info.script.name, settings.debugMode);
setInterval(wait, settings.checkFrequency, DEBUG);
createSettingsMenu()
document.addEventListener('keydown', handleKeyPress);
}
// Function to create the settings menu
function createSettingsMenu() {
GM_registerMenuCommand('YouTube Enchantments Settings', showSettingsDialog);
}
// Function to show the settings dialog
function showSettingsDialog() {
const settingsDialog = createSettingsDialog();
document.body.appendChild(settingsDialog);
}
// Function to create the settings dialog element
function createSettingsDialog() {
const dialog = document.createElement('div');
dialog.style.position = 'fixed';
dialog.style.top = '50%';
dialog.style.left = '50%';
dialog.style.transform = 'translate(-50%, -50%)';
dialog.style.backgroundColor = 'white';
dialog.style.padding = '20px';
dialog.style.border = '1px solid black';
dialog.style.zIndex = '9999';
const title = document.createElement('h2');
title.textContent = 'YouTube Enchantments Settings';
dialog.appendChild(title);
const settingsList = document.createElement('ul');
settingsList.style.listStyleType = 'none';
settingsList.style.padding = '0';
// Create a setting item for each setting
for (const setting in settings) {
const settingItem = createSettingItem(setting);
settingsList.appendChild(settingItem);
}
dialog.appendChild(settingsList);
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.addEventListener('click', (event) => {
event.stopPropagation();
closeSettingsDialog();
});
dialog.appendChild(closeButton);
dialog.addEventListener('click', (event) => {
if (event.target === dialog) {
closeSettingsDialog();
}
});
return dialog;
}
// Function to create a setting item element
function createSettingItem(settingName) {
const listItem = document.createElement('li');
listItem.style.marginBottom = '10px';
const label = document.createElement('label');
label.style.cursor = 'pointer';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = settings[settingName];
checkbox.addEventListener('change', () => {
toggleSetting(settingName);
});
const settingText = document.createElement('span');
settingText.textContent = settingName.replace(/([A-Z])/g, ' $1').trim();
label.appendChild(checkbox);
label.appendChild(settingText);
listItem.appendChild(label);
return listItem;
}
// Function to show the watch threshold dropdown
function showWatchThresholdDropdown() {
const dropdown = document.createElement('select');
const watchThresholdOptions = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
watchThresholdOptions.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option;
optionElement.textContent = `${option}%`;
if (option === settings.watchThreshold) {
optionElement.selected = true;
}
dropdown.appendChild(optionElement);
});
const dialogContainer = document.createElement('div');
dialogContainer.style.position = 'fixed';
dialogContainer.style.top = '50%';
dialogContainer.style.left = '50%';
dialogContainer.style.transform = 'translate(-50%, -50%)';
dialogContainer.style.backgroundColor = 'white';
dialogContainer.style.padding = '20px';
dialogContainer.style.border = '1px solid black';
dialogContainer.style.zIndex = '9999';
const title = document.createElement('h2');
title.textContent = 'Select Watch Threshold';
dialogContainer.appendChild(title);
dialogContainer.appendChild(dropdown);
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.addEventListener('click', () => {
document.body.removeChild(dialogContainer);
});
dialogContainer.appendChild(closeButton);
dropdown.addEventListener('change', () => {
settings.watchThreshold = parseInt(dropdown.value);
saveSettings();
document.body.removeChild(dialogContainer);
});
document.body.appendChild(dialogContainer);
}
// Clear the set of auto-liked video IDs when the page state changes
function clearAutoLikedVideoIds() {
autoLikedVideoIds.clear();
}
window.addEventListener('popstate', clearAutoLikedVideoIds);
// Get the current video ID
function getVideoId() {
const watchFlexyElem = document.querySelector('#page-manager > ytd-watch-flexy');
if (watchFlexyElem && watchFlexyElem.hasAttribute('video-id')) {
return watchFlexyElem.getAttribute('video-id');
} else {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('v');
}
}
// Check if the watch threshold has been reached
function watchThresholdReached(DEBUG) {
const player = document.querySelector(SELECTORS.PLAYER);
if (player) {
const watched = player.getCurrentTime() / player.getDuration();
const watchedTarget = settings.watchThreshold / 100;
if (watched < watchedTarget) {
DEBUG.info(`Waiting until watch threshold reached (${watched.toFixed(2)}/${watchedTarget})...`);
return false;
}
}
return true;
}
// Check if the user is subscribed to the channel
function isSubscribed(DEBUG) {
DEBUG.info('Checking whether subscribed...');
const subscribeButton = document.querySelector(SELECTORS.SUBSCRIBE_BUTTON);
if (!subscribeButton) {
DEBUG.warn('Couldn\'t find sub button');
return false;
}
const subscribed = subscribeButton.hasAttribute('subscribe-button-invisible') || subscribeButton.hasAttribute('subscribed');
DEBUG.info(subscribed ? 'We are subscribed' : 'We are not subscribed');
return subscribed;
}
// Function to check if video should be liked and perform liking
function wait(DEBUG) {
if (watchThresholdReached(DEBUG)) {
try {
if (settings.autoLikeEnabled && (settings.likeIfNotSubscribed || isSubscribed(DEBUG))) {
if (settings.autoLikeLiveStreams || window.getComputedStyle(document.querySelector('.ytp-live-badge')).display === 'none') {
like(DEBUG);
}
}
} catch (e) {
DEBUG.warn(`Failed to like video: ${e}. Will try again in ${settings.checkFrequency} ms...`);
}
}
}
// Check if a like or dislike button is pressed
function isButtonPressed(button) {
return button.classList.contains('style-default-active') || button.getAttribute('aria-pressed') === 'true';
}
// Function to like the current video
function like(DEBUG) {
DEBUG.info('Trying to like video...');
const likeButton = document.querySelector(SELECTORS.LIKE_BUTTON);
const dislikeButton = document.querySelector(SELECTORS.DISLIKE_BUTTON);
if (!likeButton) {
throw Error('Couldn\'t find like button');
}
if (!dislikeButton) {
throw Error('Couldn\'t find dislike button');
}
const videoId = getVideoId();
if (isButtonPressed(likeButton)) {
DEBUG.info('Like button has already been clicked');
autoLikedVideoIds.add(videoId);
} else if (isButtonPressed(dislikeButton)) {
DEBUG.info('Dislike button has already been clicked');
} else if (autoLikedVideoIds.has(videoId)) {
DEBUG.info('Video has already been auto-liked. User must have un-liked it, so we won\'t like it again');
} else {
DEBUG.info('Found like button. It\'s unclicked. Clicking it...');
likeButton.click();
if (isButtonPressed(likeButton)) {
autoLikedVideoIds.add(videoId);
DEBUG.info('Successfully liked video');
} else {
DEBUG.info('Failed to like video');
}
}
}
// Debugger class for logging messages
class Debugger {
constructor(name, enabled) {
this.debug = {};
if (!window.console) {
return () => { };
}
Object.getOwnPropertyNames(window.console).forEach(key => {
if (typeof window.console[key] === 'function') {
this.debug[key] = enabled ? window.console[key].bind(window.console, name + ': ') : () => { };
}
});
return this.debug;
}
}
// Function to toggle automatic scrolling
function toggleScrolling() {
if (isScrolling) {
clearInterval(scrollInterval);
isScrolling = false;
} else {
isScrolling = true;
scrollInterval = setInterval(scrollDown, 20);
}
}
// Function to perform scrolling
function scrollDown() {
var scrollAmount = 50;
window.scrollBy(0, scrollAmount);
}
// Event listener for keydown to toggle scrolling
document.addEventListener("keydown", function (event) {
if (event.key === "PageDown") {
toggleScrolling();
} else if (event.key === "PageUp") {
clearInterval(scrollInterval);
isScrolling = false;
}
});
function handleAdBlockError(DEBUG) {
DEBUG.info('Checking for AdBlock error...');
const playabilityError = document.querySelector(PLAYABILITY_ERROR_SELECTOR);
if (playabilityError) {
DEBUG.info('AdBlock error detected. Replacing player...');
const videoId = getVideoId();
if (videoId && videoId !== currentVideoId) {
currentVideoId = videoId;
replacePlayer(videoId, DEBUG);
}
}
}
function replacePlayer(videoId, DEBUG) {
DEBUG.info(`Replacing player with iframe for video ID: ${videoId}`);
const playerContainer = document.querySelector(PLAYER_CONTAINER_SELECTOR);
if (playerContainer) {
playerContainer.innerHTML = '';
const iframe = createIframe(videoId);
playerContainer.appendChild(iframe);
DEBUG.info('Player replaced successfully');
} else {
DEBUG.warn('Player container not found');
}
}
function createIframe(videoId) {
const iframe = document.createElement('iframe');
iframe.id = IFRAME_ID;
iframe.src = `https://www.youtube-nocookie.com/embed/${videoId}?autoplay=1`;
iframe.allow = 'autoplay; encrypted-media';
iframe.allowFullscreen = true;
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.border = 'none';
return iframe;
}
function observeDomChanges() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
const addedNodes = mutation.addedNodes;
for (let node of addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && node.matches(PLAYABILITY_ERROR_SELECTOR)) {
const DEBUG = new Debugger(GM_info.script.name, settings.debugMode);
handleAdBlockError(DEBUG);
break;
}
}
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
// Function to handle key press events
function handleKeyPress(event) {
if (event.key === 'F2') {
toggleSettingsDialog();
}
}
// Function to toggle the settings dialog
function toggleSettingsDialog() {
if (settingsDialogOpen) {
closeSettingsDialog();
} else {
openSettingsDialog();
}
}
// Function to close the settings dialog
function closeSettingsDialog() {
const settingsDialog = document.querySelector('.settings-dialog');
if (settingsDialog) {
settingsDialog.remove();
settingsDialogOpen = false;
}
}
// Function to open the settings dialog
function openSettingsDialog() {
const settingsDialog = createSettingsDialog();
settingsDialog.classList.add('settings-dialog');
document.body.appendChild(settingsDialog);
settingsDialogOpen = true;
}
// Initialize the script
onInit();
observeDomChanges();
}
)();