Reddit button download videos

show button to download videos in post

// ==UserScript==
// @name               Reddit button download videos
// @namespace          https://greasyfork.org/users/821661
// @match              https://www.reddit.com/*
// @match              https://new.reddit.com/*
// @match              https://sh.reddit.com/*
// @grant              GM_download
// @grant              GM_xmlhttpRequest
// @version            0.3.1
// @author             hdyzen
// @description        show button to download videos in post
// @license            MIT
// ==/UserScript==
'use strict';

// Qualities used
const qualities = ['DASH_720.mp4', 'DASH_480.mp4', 'DASH_360.mp4', 'DASH_270.mp4', 'DASH_220.mp4'];

// Download icon
const svgDownload = `<svg height="25px" width="25px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" fill="#fff"><path d="M 8.303 33.782 L 31.698 33.782 C 32.286 33.782 32.765 34.371 32.765 34.849 L 32.765 36.59 C 32.765 37.179 32.286 37.657 31.698 37.657 L 8.303 37.657 C 7.714 37.657 7.236 37.068 7.236 36.59 L 7.236 34.849 C 7.236 34.26 7.714 33.782 8.303 33.782 Z"/><path d="M 19.22 29.561 C 19.636 29.974 20.311 29.974 20.728 29.561 L 28.733 21.571 C 29.04 21.261 29.128 20.796 28.956 20.394 C 28.785 19.992 28.383 19.738 27.948 19.75 L 21.942 19.75 L 21.942 3.41 C 21.942 3.127 21.832 2.854 21.629 2.656 C 21.43 2.456 21.157 2.343 20.875 2.343 L 19.134 2.343 C 18.545 2.343 18.067 2.821 18.067 3.41 L 18.067 19.76 L 12.031 19.76 C 11.595 19.747 11.193 20.001 11.022 20.403 C 10.847 20.805 10.936 21.27 11.246 21.58 L 19.22 29.561 Z"/></svg>`;

// Add css
function addCSS(text) {
    document.documentElement.insertAdjacentHTML('beforeend', `<style rel='stylesheet'>${text}</style>`);
}

// Try fetch for the possible qualities of the video - if other methods to obtain the src fail use it
async function tryFetchQualities(url) {
    for (const quality of qualities) {
        const response = await fetch(url + quality);
        if (response.ok) {
            return response;
        }
    }
}

// Get source of videos
async function getSrc(player) {
    // Try get from player element
    if (player.hasAttribute('packaged-media-json')) {
        const json = JSON.parse(player.getAttribute('packaged-media-json'));
        const sources = json.playbackMp4s.permutations;
        return sources.at(-1)?.source?.url;
    }

    // Try get from new.reddit.com post page
    const response = await new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            url: player.hasAttribute('permalink') ? `https://new.reddit.com${player.getAttribute('permalink')}`: window.location.pathname.startsWith('/r/') ? `https://new.reddit.com${window.location.pathname}` : player.closest('._2ED-O3JtIcOqp8iIL1G5cg, ._1poyrkZ7g36PawDueRza-J, [data-test-id="post-content"]').querySelector('a[href^="/r"][href*="comments"]'),
            responseType: 'text',
            onload: e => resolve(e),
            onerror: e => reject(e),
        });
    });

    const responseInText = response.responseText;
    const urlMatched = responseInText.match(/(?:(?<=source":{"url":"|hlsUrl":")).*?(?=")/gm);
    if (urlMatched?.at(-1)) {
        return urlMatched?.at(-1);
    }

    // Try to get source mpd - Not get sound
    const urlWithId = player.getAttribute('src').match(/https:\/\/v\.redd\.it\/[a-zA-Z0-9]+\//);
    const responseAlt = await tryFetchQualities(urlWithId);

    return responseAlt?.url;
}

// Create button for download when clicked
async function createButton(player) {
    player.setAttribute('dVideo', '');

    const button = document.createElement('div');
    const inShredditPost = player.closest('shreddit-post');
    const namePost = inShredditPost ? inShredditPost.getAttribute('post-title') : player.closest('._2ED-O3JtIcOqp8iIL1G5cg, ._1poyrkZ7g36PawDueRza-J, [data-test-id="post-content"]').querySelector('._2SdHzo12ISmrC8H86TgSCp').textContent;
    const source = await getSrc(player);
    const srcToUse = await source.replaceAll('\\u0026', '&');

    if (!srcToUse) {
        console.log('Error in getting source');
        return;
    }

    button.classList.add('download_button');
    button.innerHTML = svgDownload;
    button.addEventListener('click', e => {
        // e.stopPropagation();
        e.stopImmediatePropagation();
        GM_download({
            url: srcToUse,
            name: `${namePost}.${srcToUse.includes('.m3u8') ? 'm3u8' : 'mp4'}`,
        });
    });

    player.closest('media-telemetry-observer, div').appendChild(button);
}

// Observer to detect shreddit players
const observer = new MutationObserver(() => {
    const players = document.querySelectorAll(':is(shreddit-player, shreddit-player-2, video > source[src*="v.redd.it"]):not([dVideo])');

    players.forEach(createButton);
});

// Init observer
observer.observe(document.body, {
    childList: true,
    subtree: true,
});

addCSS('.download_button { position: absolute; inset: 15px 15px auto auto; cursor: pointer; transition: .2s ease; z-index: 99; } .download_button:hover { scale: 1.2; }');