Youtube to mp4 background 1080p downloader

Reprogram the YouTube download button to automatically use yt2mp4 for 1080p downloads on left click, with a settings panel accessible via right click or Tampermonkey menu. Includes options to save popup position/size and block ad popups from the download service.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Youtube to mp4 background 1080p downloader
// @description  Reprogram the YouTube download button to automatically use yt2mp4 for 1080p downloads on left click, with a settings panel accessible via right click or Tampermonkey menu. Includes options to save popup position/size and block ad popups from the download service.
// @namespace    https://www.tampermonkey.net/
// @version      1.2
// @author       TTT
// @license      MIT
// @grant        GM_openInTab
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @match        https://www.google.com/webhp?igu=1&gws_rd=ssl
// @grant        GM_addValueChangeListener
// @grant        GM_registerMenuCommand
// @include     *://*yt1s.biz/*?query=*
// @include     *://*youtube.com*/*
// @run-at       document-end
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        window.close
// @grant        window.focus
// ==/UserScript==
if (window.location.href.includes('youtube.com/notfound')) {
       alert('place/size this popup to your desired default then double click "ok" to save it.'); document.addEventListener('click', function(event) {if (event.isTrusted && event.button === 0) {GM_setValue('Position', [window.screenX, window.screenY]); GM_setValue('Size', [window.outerWidth, window.outerHeight]); GM_setValue('Save', Date.now()); window.location.href = `https://yt1s.biz?query=${decodeURIComponent(window.location.href.split('/notfound/')[1] || '')}`;;}}); return;
}
var listenercheck = GM_addValueChangeListener("Save", function (key, oldValue, newValue, remote) {document.querySelector('.style-scope ytd-download-button-renderer')?.parentElement.dispatchEvent(new MouseEvent('contextmenu', {bubbles: true, button: 2})); document.getElementById("position").checked = true; document.getElementById("saveeeeee").checked = false; document.getElementById("btnsave").click();});

let PosSaving = false;
let PopupDelay = false;
let OpenType = 'Popup open';
let DownloadTime = 5000;
let DownloadType = '1080p';
let ButtonDelay = 100;
function loadsettingsglobally() {
let settings = GM_getValue('Settings');
  if (Array.isArray(settings)) {
    if (settings[0] === 'E') PosSaving = true; else if (settings[0] === 'D') {GM_setValue('Position', ' '); GM_setValue('Size', ' '); PosSaving = false;}
    if (settings[1] === 'E') {PopupDelay = true; GM_setValue('Size', [700, 500]);} else if (settings[1] === 'D') PopupDelay = false;
    if (settings[2] !== undefined) OpenType = String(settings[2]);
    if (settings[3] !== undefined) {DownloadType = settings[3].split(/\s+/).find(item => /\d/.test(item));}
    if (settings[4] !== undefined) {ButtonDelay = Number(settings[4].match(/\d+/)[0]);}
    if (settings[5] !== undefined) {let value = Number(settings[5].match(/\d+/)[0]); let definition = value * 1000; DownloadTime = definition;}
    }
}
loadsettingsglobally();

//TTP unblock to allow right click detection
const overwrite = false;
let needsTT = false;
const passThrough = s => s;
const policyName = "passthrough";
let TTP_default, TTP = {createHTML: passThrough, createScript: passThrough, createScriptURL: passThrough};
function doit() {
    try {
        if (typeof window.isSecureContext === 'boolean' && window.isSecureContext && window.trustedTypes && window.trustedTypes.createPolicy) {
            needsTT = true;
            if (trustedTypes.defaultPolicy) {
                TTP_default = trustedTypes.defaultPolicy;
                TTP = window.trustedTypes.createPolicy(overwrite ? "default" : policyName, TTP);
            } else {TTP_default = TTP = window.trustedTypes.createPolicy("default", TTP);}
        }
    } catch (e) {}
}

doit();

if (window.location.href.includes('youtube.com')) {
GM_registerMenuCommand("Settings", loadSettings);

function resetUIDefaults() {
    document.getElementById('position').checked = false;
    document.getElementById('saveeeeee').checked = false;
    document.getElementById('openingType').selectedIndex = 0;
    document.getElementById('downloadType').selectedIndex = 0;
    document.getElementById('buttonDelay').selectedIndex = 0;
    document.getElementById('downloadDelay').value = '5 (less = less download / folder choice time)';
}

function applySettings(settingsArray) {
    document.getElementById('position').checked = settingsArray[0] === 'E';
    document.getElementById('saveeeeee').checked = settingsArray[1] === 'E';
    const selectIDs = ['openingType', 'downloadType', 'buttonDelay'];

    for (let i = 0; i < selectIDs.length; i++) {
        const selectElement = document.getElementById(selectIDs[i]);
        const storedOptionText = settingsArray[i + 2];

        if (selectElement && storedOptionText) {
            for (let j = 0; j < selectElement.options.length; j++) {
                if (selectElement.options[j].text.trim() === storedOptionText.trim()) {
                    selectElement.selectedIndex = j;
                    break;
                }
            }
        }
    }

    const downloadDelayInput = document.getElementById('downloadDelay');
    if (downloadDelayInput && settingsArray.length >= 6) {downloadDelayInput.value = settingsArray[5];}
}
function loadSettings() {
    const savedSettings = GM_getValue('Settings');
    const overlayContainer = document.createElement('div');
    overlayContainer.id = 'Yt2mp4Settings';
    overlayContainer.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.8);
        z-index: 2147483647;
        display: flex;
        justify-content: center;
        align-items: center;
        overflow: auto;
    `;
    const style = `
<style>
    #settingsContent {
        background-color: black;
        color: white;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 4px 12px rgba(255, 255, 255, 0.1);
        max-width: 90%;
        max-height: 90%;
        overflow-y: auto;
        display: flex;
        flex-direction: column;
        align-items: center;
    }
    #settingsContent input, #settingsContent select, #settingsContent button {
        background-color: #222;
        color: white;
        border: 1px solid #444;
        padding: 4px 6px;
    }
    #settingsContent button:hover {
        background-color: #333;
        cursor: pointer;
    }
    #settingsContent label {
        margin-left: 1px;
        font-size: 1.15em;
    }
    #settingsContent .checkbox-group {
        display: flex;
        flex-direction: column;
        align-items: center;
        margin: 6px auto 6px auto;
    }
    #settingsContent .checkbox-item {
        margin: 5px 0;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
    }
    #settingsContent .center-aligned {
        width: 100%;
        text-align: center;
        margin-top: 7.5px;
    }
    #settingsContent .input-group {
        text-align: center;
        margin-top: 2.5px;
        display: flex;
        flex-direction: column;
        align-items: center;
    }
    #settingsContent .input-group > * {
        display: block;
        width: 100%;
        box-sizing: border-box;
        text-align: center;
    }
    #settingsContent .input-group input[type="text"], #settingsContent .input-group select {
        width: 80%;
        max-width: 300px;
        margin: 5px auto;
        display: block;
        text-align: center;
        text-align-last: center;
        -moz-text-align-last: center;
    }
    #settingsContent .button-group {
        display: flex;
        justify-content: center;
        flex-wrap: wrap;
        gap: 10px;
        margin-top: 10px;
    }
</style>`;
    const html = `
<div id="settingsContent">
    <h1>Yt2mp4 Settings</h1>
    <form>
    <div class="center-aligned">
        <button type="button" id="reset">Reset defaults</button>
    </div>
        <div class="checkbox-group">
            <div class="checkbox-item">
                <input type="checkbox" id="position">
                <label for="position">Enable position saving</label>
            </div>
            <div class="checkbox-item">
                <input type="checkbox" id="saveeeeee">
                <label for="saveeeeee">Delay popup saving function (to allow repositioning)</label>
            </div>
        </div>

        <div class="input-group">
            <label for="openingType">Opening type:</label>
            <select id="openingType">
                <option value="Popup" selected>Popup open</option>
                <option value="Tab">Tab open</option>
            </select>

            <label for="downloadDelay">Download time (seconds after the final click before popup is closed):</label>
            <input type="text" id="downloadDelay" value="5 (less = less download / folder choice time)" />
        </div>

        <div class="input-group">
            <label for="downloadType">Download type:</label>
            <select id="downloadType">
                <option value="Max mp4" selected> ((Max)) 1080p mp4 (usually)</option>
                <option value="Medium mp4">720p mp4</option>
                <option value="Low mp4">480p mp4</option>
            </select>

            <div class="input-group">
            <label for="buttonDelay">Button delay:</label>
            <select id="buttonDelay">
                <option value="Safe mode" selected>100ms</option>
                <option value="Fast mode">10ms</option>
                <option value="Unsafe mode">1ms (may cause lag on low-end devices)</option>
            </select>
        </div>
        </div>

        <div class="button-group">
            <button type="button" id="btnsave">Save settings</button>
            <button type="button" id="btnclose">Close</button>
        </div>
    </form>
</div>`;

    overlayContainer.innerHTML = style + html;
    document.body.appendChild(overlayContainer);
    if (savedSettings && savedSettings !== ' ') {applySettings(savedSettings);}
    else {resetUIDefaults();}
    const closeButton = document.getElementById('btnclose');
    if (closeButton) {closeButton.addEventListener('click', () => {const overlay = document.getElementById('Yt2mp4Settings'); if (overlay) {overlay.remove();}});}
    document.getElementById('saveeeeee').addEventListener('change', function() {if (this.checked) {document.getElementById('position').checked = false;}});
    document.getElementById('position').addEventListener('change', function() {if (this.checked) {document.getElementById('saveeeeee').checked = false;} else {GM_setValue('Size', ' ');GM_setValue('Position', ' ');}});
    const saveButton = document.getElementById('btnsave');
    if (saveButton) {
        saveButton.addEventListener('click', () => {
            const downloadDelayInput = document.getElementById('downloadDelay');
            const delayValue = downloadDelayInput.value;
            if (!/\d/.test(delayValue)) {alert("Save failed, download delay must have a number in its value"); return;}
            const positionCheckbox = document.getElementById('position');
            const saveCheckbox = document.getElementById('saveeeeee');
            const checkboxValues = [positionCheckbox.checked ? 'E' : 'D', saveCheckbox.checked ? 'E' : 'D'];
            const selects = [document.getElementById('openingType'), document.getElementById('downloadType'), document.getElementById('buttonDelay')];
            const selectValues = selects.map(select => select.options[select.selectedIndex].text);
            const settingsArray = [...checkboxValues, ...selectValues, delayValue];
            GM_setValue('Settings', settingsArray);
            const overlay = document.getElementById('Yt2mp4Settings');
            loadsettingsglobally();
            if (overlay) {overlay.remove();}
        });
    }

    const resetButton = document.getElementById('reset');
    if (resetButton) {resetButton.addEventListener('click', () => {GM_setValue('Settings', ' '); resetUIDefaults();});}
}

let popup = null;
function onetimefocus() {function handleVisibilityChange() {if (document.visibilityState === 'hidden') {let focusCheckCount = 0; const retryFocus = setInterval(() => {window.focus(); if (document.hasFocus()) {focusCheckCount++; if (focusCheckCount === 2) {clearInterval(retryFocus);}}}, 100); document.removeEventListener('visibilitychange', handleVisibilityChange);}}document.addEventListener('visibilitychange', handleVisibilityChange);} // short focus event to prevent infinite buffering on tab mode, while still leaving it in the background (mostly), double verifying focused before clearing and 100ms as absolute minimum for browsers to always consider the tab active, so responsive to clicks, flicker would work as well but unoptimal for low end devices

function handleDownloadClick() {
    const extractedText = window.location.href.split(/[&#]/)[0];
    let url = `https://yt1s.biz?query=${extractedText}`;
    const storedSize = GM_getValue('Size');
    const isStoredSizeArray = Array.isArray(storedSize) && storedSize.length >= 2;
    let width = isStoredSizeArray ? storedSize[0] : 1;
    let height = isStoredSizeArray ? storedSize[1] : 1;
    const storedPos = GM_getValue('Position');
    const isStoredPosArray = Array.isArray(storedPos) && storedPos.length >= 2;
    let left = isStoredPosArray ? storedPos[0] : window.screenX + window.outerWidth - width;
    let top = isStoredPosArray ? storedPos[1] : window.screenY + window.outerHeight - height;

    if (OpenType === 'Tab open') {GM_openInTab(url, true); onetimefocus();}
    else {
        if (PopupDelay === true) {url = `https://youtube.com/notfound/${encodeURIComponent(extractedText)}`;}
        popup = window.open(url, url, `width=${width},height=${height},left=${left},top=${top}`);
        if (popup) {
            const clickHandler = () => {
                if (popup && !popup.closed) {popup.focus();}
                else {document.removeEventListener('click', clickHandler);}
            };
            document.addEventListener('click', clickHandler);
        }

    }
}
const interval = setInterval(() => {
    const el = document.querySelector('#flexible-item-buttons ytd-download-button-renderer.style-scope.ytd-menu-renderer');
    if (!el) return;
    clearInterval(interval);
    function addClickBlockOverlay() {
        const target = document.querySelector('#flexible-item-buttons ytd-download-button-renderer.style-scope.ytd-menu-renderer');
        if (!target) return;
        target.addEventListener('contextmenu', (event) => {event.preventDefault(); loadSettings();});
        document.addEventListener("click", e => {if (target.contains(e.target)) {e.stopPropagation(); e.preventDefault(); handleDownloadClick();}}, true);
    }
    addClickBlockOverlay();
}, ButtonDelay);

}
if (window.location.href.includes('yt1s.biz')) {
    let focusCalled = false;

    const script = document.createElement('script');
    script.textContent = `(function() {window.open = function() {};})();`;
    document.documentElement.appendChild(script);
    script.remove();

    setInterval(() => {
    const el = document.querySelector('input[type="search"]');
    if (!el) return;
    const url = window.location.href;
    const index = url.indexOf('=');
    const extractedtext = index !== -1 ? url.substring(index + 1) : '';
    const desc = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(el), "value");
    if (desc && desc.set) {desc.set.call(el, el.value + extractedtext);}
    el.dispatchEvent(new Event("input", {bubbles: true}));
    }, ButtonDelay);
    setInterval(() => {if (!focusCalled) {window.focus(); focusCalled=true;} if (!document.getElementsByClassName("modal-overlay")[0]) {((s,v)=>{let e=document.createElement('script'); e.text=`window.handleDownload('mp4', ${v});`; document.body.appendChild(e); document.body.removeChild(e);})('', DownloadType.slice(0,-1));}}, ButtonDelay);
    const intervalId = setInterval(() => {const element = document.querySelector('.download-section > :first-child'); if (element) {element.click(); clearInterval(intervalId); setTimeout(() => {window.close();}, DownloadTime);}}, ButtonDelay);
}