Neura V3

Neura V3 - 增強YouTube影片質量控制。加入社群 https://discord.gg/aeXXdqEKxw

目前為 2024-12-23 提交的版本,檢視 最新版本

// ==UserScript==
// @name                Neura V3
// @name:zh-TW          Neura V3
// @name:zh-CN          Neura V3
// @name:ja             Neura V3
// @author              Kan       
// @version             3.1
// @description         Neura V3 - Enhance YouTube video quality controls. Join the community at https://discord.gg/aeXXdqEKxw
// @description:zh-tw   Neura V3 - 增強YouTube影片質量控制。加入社群 https://discord.gg/aeXXdqEKxw
// @description:zh-cn   Neura V3 - 增强YouTube视频质量控制。加入社区 https://discord.gg/aeXXdqEKxw
// @description:ja      Neura V3 - YouTube動画品質制御の強化。コミュニティに参加 https://discord.gg/aeXXdqEKxw
// @match               *://www.youtube.com/*
// @match               *://m.youtube.com/*
// @match               *://www.youtube-nocookie.com/*
// @exclude             *://www.youtube.com/live_chat*
// @grant               GM.getValue
// @grant               GM.setValue
// @grant               GM.deleteValue
// @grant               GM.listValues
// @grant               GM.registerMenuCommand
// @grant               GM.unregisterMenuCommand
// @grant               GM_getValue
// @grant               GM_setValue
// @grant               GM_deleteValue
// @grant               GM_listValues
// @grant               GM_registerMenuCommand
// @grant               GM_unregisterMenuCommand
// @license             MIT     

// @namespace .gg/Neura
// ==/UserScript==

/*jshint esversion: 11 */

(function() {
    "use strict";

    const DEFAULT_SETTINGS = {
        targetResolution: "hd2160",
        expandMenu: false,
        debug: false
    };

    const BROWSER_LANGUAGE = navigator.language || navigator.userLanguage;

    const GET_PREFERRED_LANGUAGE = () => {
        if (BROWSER_LANGUAGE.startsWith('zh') && BROWSER_LANGUAGE !== 'zh-TW'){
            return 'zh-CN';
        } else {
            return BROWSER_LANGUAGE;
        }
    };

    const TRANSLATIONS = {
        'en-US': {
            qualityMenu: 'Quality Menu',
            debug: 'DEBUG'
        },
        'zh-TW': {
            qualityMenu: '畫質選單',
            debug: '偵錯'
        },
        'zh-CN': {
            qualityMenu: '画质菜单',
            debug: '排错'
        },
        'ja': {
            qualityMenu: '画質メニュー',
            debug: 'デバッグ'
        }
    };

    const GET_LOCALIZED_TEXT = () => {
        const language = GET_PREFERRED_LANGUAGE();
        return TRANSLATIONS[language] || TRANSLATIONS['en-US'];
    };

    const QUALITIES = {
        highres: 4320,
        hd2880:  2880,
        hd2160:  2160,
        hd1440:  1440,
        hd1080:  1080,
        hd720:   720,
        large:   480,
        medium:  360,
        small:   240,
        tiny:    144,
    };

    const PREMIUM_INDICATOR_LABEL = "Premium";

    let userSettings = { ...DEFAULT_SETTINGS };

    let useCompatilibtyMode = false;
    let isBrokenOrMissingGMAPI = false;

    let menuItems = [];

    let videoId = '';
    let resolvedTarget = '';
    let targetLabel = '';

    let ytPlayer = document.getElementById("movie_player") || document.getElementsByClassName("html5-video-player")[0];
    let video = document.querySelector('video');

    // --- CLASS DEFINITIONS -----------

    class AllowedExceptionError extends Error {
        constructor(message) {
            super(message);
            this.name = "Allowed Exception";
        }
    }

    // --- GM FUNCTION OVERRIDES ------

    const GMCustomRegisterMenuCommand = useCompatilibtyMode ? GM_registerMenuCommand : GM.registerMenuCommand;
    const GMCustomUnregisterMenuCommand = useCompatilibtyMode ? GM_unregisterMenuCommand : GM.unregisterMenuCommand;
    const GMCustomGetValue = useCompatilibtyMode ? GM_getValue : GM.getValue;
    const GMCustomSetValue = useCompatilibtyMode ? GM_setValue : GM.setValue;
    const GMCustomListValues = useCompatilibtyMode ? GM_listValues : GM.listValues;
    const GMCustomDeleteValue = useCompatilibtyMode ? GM_deleteValue : GM.deleteValue;

    // --- FUNCTIONS ------

    function debugLog(message) {
        if (!userSettings.debug) return;
        const stack = new Error().stack;
        const stackLines = stack.split("\n");
        const callerLine = stackLines[2] ? stackLines[2].trim() : "Line not found";
        message += "";
        if (!message.endsWith(".")) {
            message += ".";
        }
        console.log(`[Neura DEBUG] ${message} Function called ${callerLine}`);
    }

    // Attempt to set the video resolution to target quality or the next best quality
    function setResolution(force = false) {
        try {
            checkVideoValid(force);
            resolvedTarget = findNextAvailableQuality(userSettings.targetResolution, ytPlayer.getAvailableQualityLevels());
            const premiumData = ytPlayer.getAvailableQualityData().find(q => q.quality === resolvedTarget && q.qualityLabel.trim().endsWith(PREMIUM_INDICATOR_LABEL) && q.isPlayable);
            ytPlayer.setPlaybackQualityRange(resolvedTarget, resolvedTarget, premiumData?.formatId);
            targetLabel = QUALITIES[resolvedTarget] + "p" + (premiumData ? " " + PREMIUM_INDICATOR_LABEL : "");
            videoId = ytPlayer.getVideoData().video_id;
            debugLog(`Setting quality to: ${targetLabel}`);
        } catch (error) {
            debugLog("Did not set resolution. " + error);
        }
    }

    function checkVideoValid(force) {
        try {
            if (/^https?:\/\/(www\.)?youtube\.com\/shorts\//.test(window.location.href)) {
                throw new AllowedExceptionError("Skipping Youtube Shorts.");
            }
            if (!ytPlayer?.getAvailableQualityLabels().length) {
                throw "Video data missing.";
            }
            if (videoId == ytPlayer.getVideoData().video_id && !force) {
                throw new AllowedExceptionError("Duplicate Load. Skipping.");
            }
        } catch (error) {
            throw error;
        }
    }

    function findNextAvailableQuality(target, availableQualities) {
        const targetValue = QUALITIES[target];
        return availableQualities
            .map(q => ({ quality: q, value: QUALITIES[q] }))
            .find(q => q.value <= targetValue)?.quality;
    }

    function compareQualityLabelButIgnoreFrameRate(label1, label2) {
        const normalize = label => label.replace(new RegExp(`(\d+p)\d*( ${PREMIUM_INDICATOR_LABEL})?$`), '$1').trim();
        return normalize(label1) === normalize(label2);
    }

    // Tries to validate setting changes. If this fails to detect a successful update it will call setResolution to attempt to force the playback quality to update again.
    function verifyChange(retries = 10) {
        const retryDelay = 500;
        try {
            checkVideoValid(true);
            let success = ytPlayer.getPreferredQuality() !== "auto";
            let currentQualityLabel = ytPlayer.getPlaybackQualityLabel();
            let isManualChange = !compareQualityLabelButIgnoreFrameRate(currentQualityLabel, targetLabel);
            if (success) {
                debugLog(`Quality is ${currentQualityLabel}. ${isManualChange ? "Manual change detected!" : "Change verified!"}`);
            } else {
                throw "Unable to verify change";
            }
        } catch (error) {
            if (error instanceof AllowedExceptionError) {
                debugLog(error);
            } else if (retries) {
                debugLog(`Error when verifying quality change. [${error}] Attempting quality change and reverifying after ${retryDelay}ms...`);
                setResolution(true);
                setTimeout(() => verifyChange(retries - 1), retryDelay);
            } else {
                debugLog(`Failed to verify change after multiple attempts: ${error}`);
            }
        }
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址