Auto Scroll YouTube Shorts

Automatically scrolls to the next YouTube short when the current one finishes or if a specific ad class is detected.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Auto Scroll YouTube Shorts
// @namespace    facelook.hk
// @version      1.4
// @description  Automatically scrolls to the next YouTube short when the current one finishes or if a specific ad class is detected.
// @author       FacelookHK
// @match        https://www.youtube.com/shorts/*
// @match        https://www.facebook.com/reel/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let moving = false;
    let lastCurrentTime = -1;
    let lastPlayedTime = 0;
    let lastVideoElement = null;
    let lastVideoSrc = "";
    let paused = false;

    function moveToNextShort() {
        // Reset state
        lastCurrentTime = -1;
        lastPlayedTime = 0;
        lastVideoElement = null;
        lastVideoSrc = "";

        if (moving) return;

        moving = true;
        paused = true; // Ensure we stay paused during the action

        const currentUrl = window.location.href;

        if (currentUrl.indexOf('youtube') != -1) {
            const nextButton = document.querySelector(
                'button.yt-spec-button-shape-next[aria-label="Next video"]'
            );
            if (nextButton) {
                nextButton.click();
                console.log("Moved to next video.");
            }
        } else if (currentUrl.indexOf('facebook') != -1) {
            const nextCard = document.querySelector('[aria-label="Next Card"]');
            if (nextCard) {
                nextCard.click();
                console.log("Moved to next video.");
            }
        }

        moving = false;
        // Keep paused for a moment after clicking to let the new video load
        setTimeout(() => { paused = false; }, 500);
    }

    function checkFacebookSliderProgress() {
        if (paused) return false;
        const slider = document.querySelector('[aria-label="Change Position"][role="slider"]');
        if (slider) {
            const currentPosition = parseFloat(slider.getAttribute('aria-valuenow'));
            const maxPosition = parseFloat(slider.getAttribute('aria-valuemax'));

            if (maxPosition - currentPosition <= 0.5) {
                console.log("Facebook video near end. Moving next.");
                paused = true; // BLOCK further checks immediately
                setTimeout(moveToNextShort, 500);
                return true;
            }
        }
        return false;
    }

    function checkYouTubeVideoProgress() {
        if (paused) return;

        // Get the active reel container first
        const activeReel = document.querySelector('ytd-reel-video-renderer[is-active]');

        if (!activeReel) return;

        // 0. Immediate Ad Class Detection
        // Checks if the specific ad component exists within the currently active reel
        const adComponent = activeReel.querySelector('.ytwReelsAdCardButtonedViewModelHostIsClickableAdComponent');
        if (adComponent) {
            console.log("Ad class detected. Moving next immediately.");
            paused = true;
            moveToNextShort();
            return;
        }

        const video = activeReel.querySelector('video');

        if (!video) return;

        // Detect new video
        if (video !== lastVideoElement || video.src !== lastVideoSrc) {
            console.log("New video detected. Resetting timers.");
            lastVideoElement = video;
            lastVideoSrc = video.src;
            lastPlayedTime = 0;
            lastCurrentTime = -1;
            return;
        }

        const duration = video.duration;
        const currentTime = video.currentTime;

        // 1. Duration Check (Video Finishing)
        if (!isNaN(duration) && duration > 0 && duration - currentTime <= 0.5) {
            console.log(`Video finishing (Time: ${currentTime}/${duration}). Moving next.`);
            paused = true; // BLOCK further checks immediately
            setTimeout(moveToNextShort, 500);
            return;
        }

        // 2. Loop Detection
        if (lastPlayedTime > 0 && currentTime < 0.5) {
            // Only skip if we were previously near the end (prevent seeking triggers)
            const wasNearEnd = (duration - lastPlayedTime < 1.5);

            if (wasNearEnd) {
                console.log(`Video looped (Last: ${lastPlayedTime}, Dur: ${duration}). Moving next.`);
                paused = true; // BLOCK further checks
                moveToNextShort(); // Move immediately for loops (no delay needed)
                return;
            } else if (lastPlayedTime > 5) {
                console.log(`Manual replay detected (Last: ${lastPlayedTime}). Not skipping.`);
                lastPlayedTime = currentTime;
                lastCurrentTime = currentTime;
                return;
            }
        }

        lastCurrentTime = currentTime;
        lastPlayedTime = currentTime;
    }

    if (window.location.href.indexOf('facebook') != -1) {
        setInterval(checkFacebookSliderProgress, 500);
    } else if (window.location.href.indexOf('youtube') != -1) {
        setInterval(checkYouTubeVideoProgress, 250);
    }
})();