// ==UserScript==
// @name YouTube Enchantments
// @namespace http://tampermonkey.net/
// @version 0.8.1
// @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';
// Polyfill for Edge if necessary
if (!window.Blob || !window.URL || !window.Worker) {
console.warn('Browser compatibility features missing');
return;
}
// Inject the YouTube IFrame API script with error handling
function injectYouTubeAPI() {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://www.youtube.com/iframe_api';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
// Updated constants
const SELECTORS = {
PLAYER: '#movie_player',
SUBSCRIBE_BUTTON: '#subscribe-button > ytd-subscribe-button-renderer, ytd-reel-player-overlay-renderer #subscribe-button, tp-yt-paper-button[subscribed]',
LIKE_BUTTON: 'ytd-menu-renderer button[aria-pressed][aria-label*="like"], like-button-view-model button[aria-pressed]',
DISLIKE_BUTTON: 'ytd-menu-renderer button[aria-pressed][aria-label*="dislike"], dislike-button-view-model button[aria-pressed]',
PLAYER_CONTAINER: '#player-container-outer',
ERROR_SCREEN: '#error-screen',
PLAYABILITY_ERROR: '.yt-playability-error-supported-renderers',
LIVE_BADGE: '.ytp-live-badge'
};
const CONSTANTS = {
IFRAME_ID: 'adblock-bypass-player',
STORAGE_KEY: 'youtubeEnchantmentsSettings',
DELAY: 300, // Increased delay for Edge
MAX_TRIES: 150, // Increased max tries
DUPLICATE_CHECK_INTERVAL: 7000 // Increased interval
};
const defaultSettings = {
autoLikeEnabled: true,
autoLikeLiveStreams: false,
likeIfNotSubscribed: false,
watchThreshold: 0,
checkFrequency: 5000,
adBlockBypassEnabled: false
};
let settings = loadSettings();
const autoLikedVideoIds = new Set();
let isScrolling = false;
let scrollInterval;
let currentPageUrl = window.location.href;
let tries = 0;
const worker = createWorker();
const urlUtils = {
extractParams(url) {
try {
const params = new URL(url).searchParams;
return {
videoId: params.get('v'),
playlistId: params.get('list'),
index: params.get('index')
};
} catch (e) {
console.error('Failed to extract URL params:', e);
return {};
}
},
getTimestampFromUrl(url) {
try {
const timestamp = new URL(url).searchParams.get('t');
if (timestamp) {
const timeArray = timestamp.split(/h|m|s/).map(Number);
const timeInSeconds = timeArray.reduce((acc, time, index) =>
acc + time * Math.pow(60, 2 - index), 0);
return `&start=${timeInSeconds}`;
}
} catch (e) {
console.error('Failed to extract timestamp:', e);
}
return '';
}
};
let player;
// Updated PlayerManager
const playerManager = {
async initPlayer() {
try {
await injectYouTubeAPI();
const iframe = document.getElementById(CONSTANTS.IFRAME_ID);
if (iframe) {
player = new YT.Player(CONSTANTS.IFRAME_ID, {
events: {
'onReady': this.onPlayerReady.bind(this),
'onStateChange': this.onPlayerStateChange.bind(this),
'onError': (event) => {
console.error('Player error:', event.data);
}
}
});
}
} catch (error) {
console.error('Failed to initialize player:', error);
}
},
onPlayerReady(event) {
console.log('Player is ready');
},
onPlayerStateChange(event) {
if (event.data === YT.PlayerState.AD_STARTED) {
console.log('Ad is playing, allowing ad to complete.');
} else if (event.data === YT.PlayerState.ENDED || event.data === YT.PlayerState.PLAYING) {
console.log('Video is playing, ensuring it is tracked in history.');
}
},
createIframe(url) {
try {
const { videoId, playlistId, index } = urlUtils.extractParams(url);
if (!videoId) return null;
const iframe = document.createElement('iframe');
const commonArgs = 'autoplay=1&modestbranding=1&enablejsapi=1&origin=' + encodeURIComponent(window.location.origin);
const embedUrl = playlistId
? `https://www.youtube.com/embed/${videoId}?${commonArgs}&list=${playlistId}&index=${index}`
: `https://www.youtube.com/embed/${videoId}?${commonArgs}${urlUtils.getTimestampFromUrl(url)}`;
this.setIframeAttributes(iframe, embedUrl);
return iframe;
} catch (error) {
console.error('Failed to create iframe:', error);
return null;
}
},
setIframeAttributes(iframe, url) {
iframe.id = CONSTANTS.IFRAME_ID;
iframe.src = url;
iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share';
iframe.allowFullscreen = true;
iframe.style.cssText = 'height:100%; width:calc(100% - 240px); border:none; border-radius:12px; position:relative; left:240px;';
},
replacePlayer(url) {
const playerContainer = document.querySelector(SELECTORS.ERROR_SCREEN);
if (!playerContainer) return;
let iframe = document.getElementById(CONSTANTS.IFRAME_ID);
if (iframe) {
this.setIframeAttributes(iframe, url);
} else {
iframe = this.createIframe(url);
if (iframe) {
playerContainer.appendChild(iframe);
this.initPlayer();
}
}
// Ensure the iframe is on top of the player container
this.bringToFront(CONSTANTS.IFRAME_ID);
this.addScrollListener();
},
bringToFront(elementId) {
const element = document.getElementById(elementId);
if (element) {
const maxZIndex = Math.max(
...Array.from(document.querySelectorAll('*'))
.map(e => parseInt(window.getComputedStyle(e).zIndex) || 0)
);
element.style.zIndex = maxZIndex + 1;
}
},
removeDuplicates() {
const iframes = document.querySelectorAll(`#${CONSTANTS.IFRAME_ID}`);
if (iframes.length > 1) {
Array.from(iframes).slice(1).forEach(iframe => iframe.remove());
}
},
addScrollListener() {
window.addEventListener('scroll', this.handleScroll);
},
handleScroll() {
const iframe = document.getElementById(CONSTANTS.IFRAME_ID);
if (!iframe) return;
const playerContainer = document.querySelector(SELECTORS.ERROR_SCREEN);
if (!playerContainer) return;
const rect = playerContainer.getBoundingClientRect();
if (rect.top < 0) {
iframe.style.position = 'fixed';
iframe.style.top = '0';
iframe.style.left = '240px';
iframe.style.width = 'calc(100% - 240px)';
iframe.style.height = 'calc(100vh - 56px)'; // Adjust height as needed
} else {
iframe.style.position = 'relative';
iframe.style.top = '0';
iframe.style.left = '240px';
iframe.style.width = 'calc(100% - 240px)';
iframe.style.height = '100%';
}
}
};
// Updated worker with error handling
function createWorker() {
try {
const workerBlob = new Blob([`
let checkInterval;
self.onmessage = function(e) {
try {
if (e.data.type === 'startCheck') {
if (checkInterval) clearInterval(checkInterval);
checkInterval = setInterval(() => {
self.postMessage({ type: 'check' });
}, e.data.checkFrequency);
} else if (e.data.type === 'stopCheck') {
clearInterval(checkInterval);
}
} catch (error) {
self.postMessage({ type: 'error', error: error.message });
}
};
`], { type: 'text/javascript' });
const worker = new Worker(URL.createObjectURL(workerBlob));
worker.onerror = function (error) {
console.error('Worker error:', error);
};
return worker;
} catch (error) {
console.error('Failed to create worker:', error);
return null;
}
}
function loadSettings() {
const savedSettings = GM_getValue(CONSTANTS.STORAGE_KEY, {});
return Object.keys(defaultSettings).reduce((acc, key) => {
acc[key] = key in savedSettings ? savedSettings[key] : defaultSettings[key];
return acc;
}, {});
}
function saveSettings() {
GM_setValue(CONSTANTS.STORAGE_KEY, settings);
}
function createSettingsMenu() {
GM_registerMenuCommand('YouTube Enchantments Settings', showSettingsDialog);
}
function showSettingsDialog() {
let dialog = document.getElementById('youtube-enchantments-settings');
if (!dialog) {
dialog = createSettingsDialog();
document.body.appendChild(dialog);
}
dialog.style.display = 'block';
}
function createSettingsDialog() {
const dialog = document.createElement('div');
dialog.id = 'youtube-enchantments-settings';
const dialogHTML = `
<div class="dpe-dialog">
<h3>YouTube Enchantments</h3>
${createToggle('autoLikeEnabled', 'Auto Like', 'Automatically like videos of subscribed channels')}
${createToggle('autoLikeLiveStreams', 'Like Live Streams', 'Include live streams in auto-like feature')}
${createToggle('likeIfNotSubscribed', 'Like All Videos', 'Like videos even if not subscribed')}
${createToggle('adBlockBypassEnabled', 'AdBlock Bypass', 'Bypass AdBlock detection')}
<div class="dpe-slider-container" title="Percentage of video to watch before liking">
<label for="watchThreshold">Watch Threshold</label>
<input type="range" id="watchThreshold" min="0" max="100" step="10"
value="${settings.watchThreshold}">
<span id="watchThresholdValue">${settings.watchThreshold}%</span>
</div>
<div class="dpe-button-container">
<button id="saveSettingsButton" class="dpe-button dpe-button-save">Save</button>
<button id="closeSettingsButton" class="dpe-button dpe-button-cancel">Cancel</button>
</div>
</div>
`;
const styleSheet = `
<style>
.dpe-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #030d22;
border: 1px solid #2a2945;
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
z-index: 9999;
color: #ffffff;
width: 320px;
font-family: 'Roboto', Arial, sans-serif;
}
.dpe-dialog h3 {
margin-top: 0;
font-size: 1.8em;
text-align: center;
margin-bottom: 24px;
color: #ffffff;
font-weight: 700;
}
.dpe-toggle-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 8px;
border-radius: 8px;
background: #15132a;
transition: background-color 0.2s;
}
.dpe-toggle-container:hover {
background: #1a1832;
}
.dpe-toggle-label {
flex-grow: 1;
color: #ffffff;
font-size: 1.1em;
font-weight: 600;
margin-left: 12px;
}
.dpe-toggle {
position: relative;
display: inline-block;
width: 46px;
height: 24px;
}
.dpe-toggle input {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
margin: 0;
}
.dpe-toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #2a2945;
transition: .3s;
border-radius: 24px;
}
.dpe-toggle-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: #ffffff;
transition: .3s;
border-radius: 50%;
}
.dpe-toggle input:checked + .dpe-toggle-slider {
background-color: #cc0000;
}
.dpe-toggle input:checked + .dpe-toggle-slider:before {
transform: translateX(22px);
}
.dpe-slider-container {
margin: 24px 0;
padding: 12px;
background: #15132a;
border-radius: 8px;
}
.dpe-slider-container label {
display: block;
margin-bottom: 8px;
color: #ffffff;
font-size: 1.1em;
font-weight: 600;
}
.dpe-slider-container input[type="range"] {
width: 100%;
margin: 8px 0;
height: 4px;
background: #2a2945;
border-radius: 2px;
-webkit-appearance: none;
}
.dpe-slider-container input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: #cc0000;
border-radius: 50%;
cursor: pointer;
transition: background-color 0.2s;
}
.dpe-slider-container input[type="range"]::-webkit-slider-thumb:hover {
background: #990000;
}
.dpe-button-container {
display: flex;
justify-content: space-between;
margin-top: 24px;
gap: 12px;
}
.dpe-button {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 1.1em;
font-weight: 600;
transition: all 0.2s;
flex: 1;
}
.dpe-button-save {
background-color: #cc0000;
color: white;
}
.dpe-button-save:hover {
background-color: #990000;
transform: translateY(-1px);
}
.dpe-button-cancel {
background-color: #15132a;
color: white;
border: 1px solid #2a2945;
}
.dpe-button-cancel:hover {
background-color: #1a1832;
transform: translateY(-1px);
}
</style>
`;
dialog.innerHTML = styleSheet + dialogHTML;
// Add event listeners
dialog.querySelector('#saveSettingsButton').addEventListener('click', () => {
saveSettings();
hideSettingsDialog();
});
dialog.querySelector('#closeSettingsButton').addEventListener('click', hideSettingsDialog);
dialog.querySelectorAll('.dpe-toggle input').forEach(toggle => {
toggle.addEventListener('change', handleSettingChange);
});
dialog.querySelector('#watchThreshold').addEventListener('input', handleSliderInput);
return dialog;
}
function createToggle(id, label, title) {
return `
<div class="dpe-toggle-container" title="${title}">
<label class="dpe-toggle">
<input type="checkbox" data-setting="${id}" ${settings[id] ? 'checked' : ''}>
<span class="dpe-toggle-slider"></span>
</label>
<label class="dpe-toggle-label">${label}</label>
</div>
`;
}
function hideSettingsDialog() {
const dialog = document.getElementById('youtube-enchantments-settings');
if (dialog) {
dialog.style.display = 'none';
}
}
function formatSettingName(setting) {
return setting.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
}
function handleSettingChange(e) {
if (e.target.dataset.setting) {
if (e.target.type === 'checkbox') {
toggleSetting(e.target.dataset.setting);
} else if (e.target.type === 'range') {
updateNumericSetting(e.target.dataset.setting, e.target.value);
}
// Log the status of adBlockBypassEnabled if it is changed
if (e.target.dataset.setting === 'adBlockBypassEnabled') {
console.log(`AdBlock Ban Bypass is ${e.target.checked ? 'enabled' : 'disabled'}`);
}
}
}
function handleSliderInput(e) {
if (e.target.type === 'range') {
const value = e.target.value;
document.getElementById('watchThresholdValue').textContent = `${value}%`;
updateNumericSetting(e.target.dataset.setting, value);
}
}
function toggleSetting(settingName) {
settings[settingName] = !settings[settingName];
saveSettings();
}
function updateNumericSetting(settingName, value) {
settings[settingName] = parseInt(value, 10);
saveSettings();
}
function startBackgroundCheck() {
worker.postMessage({ type: 'startCheck', checkFrequency: settings.checkFrequency });
}
function checkAndLikeVideo() {
console.log('Checking if video should be liked...');
if (watchThresholdReached()) {
console.log('Watch threshold reached.');
if (settings.autoLikeEnabled) {
console.log('Auto-like is enabled.');
if (settings.likeIfNotSubscribed || isSubscribed()) {
console.log('User is subscribed or likeIfNotSubscribed is enabled.');
if (settings.autoLikeLiveStreams || !isLiveStream()) {
console.log('Video is not a live stream or auto-like for live streams is enabled.');
likeVideo();
} else {
console.log('Video is a live stream and auto-like for live streams is disabled.');
}
} else {
console.log('User is not subscribed and likeIfNotSubscribed is disabled.');
}
} else {
console.log('Auto-like is disabled.');
}
} else {
console.log('Watch threshold not reached.');
}
}
function watchThresholdReached() {
const player = document.querySelector(SELECTORS.PLAYER);
if (player) {
const watched = player.getCurrentTime() / player.getDuration();
const watchedTarget = settings.watchThreshold / 100;
if (watched < watchedTarget) {
console.log(`Waiting until watch threshold reached (${watched.toFixed(2)}/${watchedTarget})...`);
return false;
}
}
return true;
}
function isSubscribed() {
const subscribeButton = document.querySelector(SELECTORS.SUBSCRIBE_BUTTON);
return subscribeButton && (subscribeButton.hasAttribute('subscribe-button-invisible') || subscribeButton.hasAttribute('subscribed'));
}
function isLiveStream() {
const liveBadge = document.querySelector(SELECTORS.LIVE_BADGE);
return liveBadge && window.getComputedStyle(liveBadge).display !== 'none';
}
function likeVideo() {
console.log('Attempting to like the video...');
const likeButton = document.querySelector(SELECTORS.LIKE_BUTTON);
const dislikeButton = document.querySelector(SELECTORS.DISLIKE_BUTTON);
const videoId = getVideoId();
if (!likeButton || !dislikeButton || !videoId) {
console.log('Like button, dislike button, or video ID not found.');
return;
}
if (!isButtonPressed(likeButton) && !isButtonPressed(dislikeButton) && !autoLikedVideoIds.has(videoId)) {
console.log('Liking the video...');
likeButton.click();
if (isButtonPressed(likeButton)) {
console.log('Video liked successfully.');
autoLikedVideoIds.add(videoId);
} else {
console.log('Failed to like the video.');
}
} else {
console.log('Video already liked or disliked, or already auto-liked.');
}
}
function isButtonPressed(button) {
return button.classList.contains('style-default-active') || button.getAttribute('aria-pressed') === 'true';
}
function getVideoId() {
const watchFlexyElem = document.querySelector('#page-manager > ytd-watch-flexy');
if (watchFlexyElem && watchFlexyElem.hasAttribute('video-id')) {
return watchFlexyElem.getAttribute('video-id');
}
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('v');
}
function handleAdBlockError() {
if (!settings.adBlockBypassEnabled) {
console.log('AdBlock bypass is disabled.');
return; // Do nothing if the AdBlock bypass is disabled
}
const playabilityError = document.querySelector(SELECTORS.PLAYABILITY_ERROR);
if (playabilityError) {
playabilityError.remove();
playerManager.replacePlayer(window.location.href);
} else if (tries < CONSTANTS.MAX_TRIES) {
tries++;
setTimeout(handleAdBlockError, CONSTANTS.DELAY);
}
}
function handleKeyPress(event) {
switch (event.key) {
case 'F2':
toggleSettingsDialog();
break;
case 'PageDown':
toggleScrolling();
break;
case 'PageUp':
handlePageUp();
break;
}
}
function toggleSettingsDialog() {
const dialog = document.getElementById('youtube-enchantments-settings');
if (dialog && dialog.style.display === 'block') {
hideSettingsDialog();
} else {
showSettingsDialog();
}
}
function toggleScrolling() {
if (isScrolling) {
clearInterval(scrollInterval);
isScrolling = false;
} else {
isScrolling = true;
scrollInterval = setInterval(() => window.scrollBy(0, 50), 20);
}
}
function handlePageUp() {
if (isScrolling) {
clearInterval(scrollInterval);
isScrolling = false;
} else {
window.scrollTo(0, 0);
}
}
function setupEventListeners() {
window.addEventListener('beforeunload', () => {
currentPageUrl = window.location.href;
});
document.addEventListener('yt-navigate-finish', () => {
console.log('yt-navigate-finish event triggered');
const newUrl = window.location.href;
if (newUrl !== currentPageUrl) {
console.log('URL changed:', newUrl);
if (newUrl.endsWith('.com/')) {
const iframe = document.getElementById(CONSTANTS.IFRAME_ID);
if (iframe) {
console.log('Removing iframe');
iframe.remove();
}
} else {
console.log('Handling ad block error');
handleAdBlockError();
}
currentPageUrl = newUrl;
}
});
document.addEventListener('keydown', (event) => {
console.log('Key pressed:', event.key);
handleKeyPress(event);
});
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE &&
node.matches(SELECTORS.PLAYABILITY_ERROR)) {
console.log('Playability error detected');
handleAdBlockError();
return;
}
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
setInterval(() => {
console.log('Checking for duplicate players');
playerManager.removeDuplicates();
}, CONSTANTS.DUPLICATE_CHECK_INTERVAL);
}
// Updated main initialization function
async function initScript() {
try {
console.log('Initializing script for Edge compatibility');
createSettingsMenu();
setupEventListeners();
const worker = createWorker();
if (worker) {
startBackgroundCheck(worker);
} else {
console.warn('Worker creation failed, falling back to interval');
setInterval(checkAndLikeVideo, settings.checkFrequency);
}
// Check browser compatibility
const userAgent = navigator.userAgent;
if (userAgent.includes("Edg/")) {
console.log('Edge detected, applying specific optimizations');
CONSTANTS.DELAY = 300; // Specific adjustment for Edge
CONSTANTS.MAX_TRIES = 150;
}
} catch (error) {
console.error('Script initialization failed:', error);
}
}
initScript();
})();