nascent / YouTube Auto Liker

// ==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);
})();