NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name YouTube Auto Liker
// @namespace http://tampermonkey.net/
// @version 1.3
// @description Auto-like YouTube videos once a user-defined percentage is watched if not already liked/disliked.
// @author nascent
// @match https://www.youtube.com/*
// @icon https://www.google.com/s2/favicons?domain=youtube.com&sz=128
// @updateURL https://openuserjs.org/meta/nascent/YouTube_Auto_Liker.meta.js
// @downloadURL https://openuserjs.org/install/nascent/YouTube_Auto_Liker.user.js
// @copyright 2025, nascent (https://openuserjs.org/users/nascent)
// @license MIT
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
"use strict";
// ===================================================
// Global configuration for notifications
// ===================================================
let notificationsEnabled = GM_getValue("notificationsEnabled", true);
const NOTIFICATION_DURATION_MILLIS = 3000; // Duration for toast notification
let lastToastElement = null;
// Function to show a toast notification.
function showNotification(message) {
if (!notificationsEnabled) {
return;
}
// If a toast is already visible, remove it.
if (lastToastElement !== null) {
lastToastElement.remove();
lastToastElement = null;
}
// Create a toast element (YouTube uses tp-yt-paper-toast for notifications).
const toast = document.createElement('tp-yt-paper-toast');
toast.innerText = message;
toast.classList.add('toast-open');
// Set up inline style properties for the toast.
const styleProps = {
outline: 'none',
position: 'fixed',
left: '0',
bottom: '12px',
maxWidth: '297.547px',
maxHeight: '48px',
zIndex: '2202',
opacity: '1',
};
for (const prop in styleProps) {
toast.style[prop] = styleProps[prop];
}
document.body.appendChild(toast);
lastToastElement = toast;
// Show the toast notification.
setTimeout(() => {
toast.style.display = 'block';
}, 0);
// Ensure the toast animation occurs.
setTimeout(() => {
toast.style.transform = 'none';
}, 10);
setTimeout(() => {
toast.style.transform = 'translateY(200%)';
}, Math.max(0, NOTIFICATION_DURATION_MILLIS));
}
// ===================================================
// Configurable Threshold Using GM_getValue/GM_setValue
// ===================================================
const defaultThreshold = 70; // Default percentage threshold.
let percentage = GM_getValue("percentage", defaultThreshold);
// Option: Only auto-like if subscribed.
let onlyAutoLikeIfSubscribed = GM_getValue("onlyAutoLikeIfSubscribed", false);
// ===================================================
// Register Menu Commands
// ===================================================
GM_registerMenuCommand("Set Auto-Like Percentage", setThreshold);
GM_registerMenuCommand("Toggle Notifications", toggleNotifications);
GM_registerMenuCommand("Toggle Only Auto-Like If Subscribed", toggleOnlyAutoLikeIfSubscribed);
function setThreshold() {
const input = prompt("Enter new auto-like percentage (0-100):", percentage);
// Remove extra whitespace
input = input.trim();
if (input !== null) {
const newPercentage = Number(input);
if (isNaN(newPercentage) || newPercentage < 0 || newPercentage > 100) {
alert("Invalid value. Please enter a number between 0 and 100.");
return;
}
percentage = newPercentage;
GM_setValue("percentage", percentage);
alert(`Auto-Like percentage threshold updated to ${percentage}%.`);
}
const newPercentage = Number(input);
// Validate that the new percentage is an integer and between 0 (inclusive) and 100 (exclusive).
if (!Number.isInteger(newPercentage) || newPercentage < 0 || newPercentage >= 100) {
alert("Invalid value. Please enter an integer number between 0 and 99.");
return;
}
percentage = newPercentage;
GM_setValue("percentage", percentage);
alert(`Auto-Like percentage threshold updated to ${percentage}%.`);
}
function toggleNotifications() {
notificationsEnabled = !notificationsEnabled;
GM_setValue("notificationsEnabled", notificationsEnabled);
alert(`Notifications have been ${notificationsEnabled ? "enabled" : "disabled"}.`);
}
function toggleOnlyAutoLikeIfSubscribed() {
onlyAutoLikeIfSubscribed = !onlyAutoLikeIfSubscribed;
GM_setValue("onlyAutoLikeIfSubscribed", onlyAutoLikeIfSubscribed);
alert(`Auto-like only if subscribed is now ${onlyAutoLikeIfSubscribed ? "enabled" : "disabled"}.`);
}
// ===================================================
// Auto-Like Script Logic
// ===================================================
// Ensure we only attempt auto-like once per video.
function getVideoId() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get("v");
}
// Store the current video ID and liked state.
let currentVideoId = getVideoId();
let wasLiked = false;
const checkInterval = 10000; // Check every 10 seconds.
setInterval(() => {
// -----------------------------------------------------
// Ad detection: skip auto-like if an ad is playing.
// -----------------------------------------------------
const player = document.getElementById("movie_player");
if (player && player.classList.contains("ad-showing")) {
console.log("YouTube Auto Liker: Ad is playing. Skipping auto-like.");
return;
}
// -----------------------------------------------------
// Check if a new video has loaded (for inline playlists)
// -----------------------------------------------------
const newVideoId = getVideoId();
if (newVideoId && newVideoId !== currentVideoId) {
console.debug(`YouTube Auto Liker: Detected new video. Previous video id: ${currentVideoId}, New video id: ${newVideoId}`);
currentVideoId = newVideoId;
wasLiked = false; // reset auto-like flag for the new video
}
// Get the video element on the page.
const video = document.querySelector("video");
if (!video) {
console.debug("YouTube Auto Liker: Video element not found yet.");
return;
}
// Ensure we have a valid video duration.
if (!video.duration) {
console.debug("YouTube Auto Liker: Video duration not available yet.");
return;
}
// Calculate the percentage watched.
const progress = (video.currentTime / video.duration) * 100;
console.debug(`YouTube Auto Liker: Video progress: ${progress.toFixed(2)}%`);
// When the progress reaches the threshold and the video hasn't been processed yet...
if (progress >= percentage) {
// If the "only auto-like if subscribed" option is enabled, check subscription.
if (onlyAutoLikeIfSubscribed) {
const subscribeButton = document.querySelector("ytd-subscribe-button-renderer");
if (subscribeButton) {
const subscribeText = subscribeButton.textContent || "";
if (subscribeText.trim().includes("SubscribedSubscribed")) {
console.debug("YouTube Auto Liker: User is subscribed to the channel.");
} else {
console.log("YouTube Auto Liker: User is not subscribed to the channel. Skipping auto-like.");
return;
}
} else {
console.log("YouTube Auto Liker: Subscribe button not found. Skipping auto-like.");
return;
}
}
console.debug("YouTube Auto Liker: progress >= percentage:" + (progress >= percentage));
console.debug("YouTube Auto Liker: wasLiked:" + wasLiked);
if (!wasLiked) {
// Locate the like and dislike buttons using the updated selectors.
const likeButton = document.querySelector(
"segmented-like-dislike-button-view-model like-button-view-model button[aria-pressed]"
);
const dislikeButton = document.querySelector(
"segmented-like-dislike-button-view-model dislike-button-view-model button[aria-pressed]"
);
// Check if the buttons were found
if (!likeButton || !dislikeButton) {
console.error("YouTube Auto Liker: Like/Dislike buttons not found. Aborting check.");
return;
}
// Check the active state by reading the aria-pressed attribute.
const isLiked = likeButton.getAttribute("aria-pressed") === "true";
const isDisliked = dislikeButton.getAttribute("aria-pressed") === "true";
console.debug("YouTube Auto Liker: isLiked: " + isLiked);
console.debug("YouTube Auto Liker: isDisliked: " + isDisliked);
if (!isLiked && !isDisliked) {
console.log(`YouTube Auto Liker: Video ${currentVideoId} has reached ${progress.toFixed(2)}% and is neither liked nor disliked. Liking now...`);
likeButton.click();
// Show the "Video liked" notification.
showNotification("Video auto-liked ❤️");
} else {
console.log("YouTube Auto Liker: Video " + currentVideoId + " is already liked/disliked. No action taken.");
}
// Mark that the video has been processed to avoid repeated action.
wasLiked = true;
} else {
console.log("YouTube Auto Liker: Video " + currentVideoId + " has already been liked/disliked. No action taken.");
}
}
}, checkInterval);
})();