Greasy Fork is available in English.

Hulu.com Subtitle Downloader

Downloads subtitle from Hulu.com as SRT format (Chrome only)

// ==UserScript==
// @name         Hulu.com Subtitle Downloader
// @namespace    https://www.hulu.com
// @version      1.0.4
// @description  Downloads subtitle from Hulu.com as SRT format (Chrome only)
// @author       subdiox
// @match        https://www.hulu.com/watch/*
// @require  http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @require https://greasyfork.org/scripts/383527-wait-for-key-elements/code/Wait_for_key_elements.js?version=701631
// @grant        GM_xmlhttpRequest
// @copyright  2021, subdiox
// @license MIT
// ==/UserScript==

waitForKeyElements('.PlayerSettingsGroup', pageDidLoad);

function pageDidLoad(jNode) {
    jNode.append('<div id="download-button" aria-label="Download" tabindex="0" role="button" class="PlayerButton PlayerControlsButton" style="touch-action: none;">' +
    '<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 29.978 29.978" style="enable-background:new 0 0 29.978 29.978;" xml:space="preserve">' +
    '<path d="M25.462,19.105v6.848H4.515v-6.848H0.489v8.861c0,1.111,0.9,2.012,2.016,2.012h24.967c1.115,0,2.016-0.9,2.016-2.012 v-8.861H25.462z" fill="#FEFEFE" fill-rule="evenodd"/>' +
    '<path d="M14.62,18.426l-5.764-6.965c0,0-0.877-0.828,0.074-0.828s3.248,0,3.248,0s0-0.557,0-1.416c0-2.449,0-6.906,0-8.723 c0,0-0.129-0.494,0.615-0.494c0.75,0,4.035,0,4.572,0c0.536,0,0.524,0.416,0.524,0.416c0,1.762,0,6.373,0,8.742 c0,0.768,0,1.266,0,1.266s1.842,0,2.998,0c1.154,0,0.285,0.867,0.285,0.867s-4.904,6.51-5.588,7.193 C15.092,18.979,14.62,18.426,14.62,18.426z" fill="#FEFEFE" fill-rule="evenodd"/>' +
    '</svg></div>');
    document.getElementById('download-button').addEventListener('click', downloadDidClick);
}

async function downloadDidClick() {
    const x = new XMLHttpRequest();
    const id = window.location.href.split('/').pop();
    x.open('GET', `https://discover.hulu.com/content/v5/deeplink/playback?namespace=entity&schema=1&id=${id}`, !1);
    x.withCredentials = !0;
    x.send(null);
    const json1 = JSON.parse(x.responseText);
    const eab_id = json1.eab_id.split('::')[2];
    x.open('GET', `https://discover.hulu.com/content/v3/entity?device_context_id=1&language=en&referral_host=www.hulu.com&schema=4&eab_ids=${json1.eab_id}`, !1);
    x.withCredentials = !0;
    x.send(null);
    const json2 = JSON.parse(x.responseText);
    var filename = '';
    const series = json2.items[0].series_name;
    const season = json2.items[0].season;
    const number = json2.items[0].number;
    const name = json2.items[0].name;
    if (series) {
        filename += `${series} `;
    }
    if (season) {
        filename += `S ${season} `;
    }
    if (number) {
        filename += `E ${number} `;
    }
    if (name) {
        if (filename === '') {
            filename = `${name}.srt`;
        } else {
            filename += `- ${name}.srt`;
        }
    }
    if (filename === '') {
        filename = eab_id + '.srt';
    }
    x.open('GET', `https://www.hulu.com/captions.xml?content_id=${eab_id}`, !1);
    x.withCredentials = !0;
    x.send(null);
    const parser = new DOMParser();
    const xml = parser.parseFromString(x.responseText, 'text/xml');
    const element = xml.getElementsByTagName('en')[0];
    let vttUrl = `https://assetshuluimcom-a.akamaihd.net/captions_webvtt/${eab_id.substr(-3)}/${eab_id}_US_en_en.vtt`;
    if (element) {
        const url = element.childNodes[0].nodeValue;
        vttUrl = url.replace('captions', 'captions_webvtt').replace('.smi', '.vtt');
    }
    GM_xmlhttpRequest({
        method: 'GET',
        url: vttUrl,
        onload: (response) => {
            var srtWithoutNumber = '';
            const vtt = response.responseText.replace(/&gt;/g, '>').replace(/&lt;/, '<');
            for (var vttLine of vtt.split('\n')) {
                if (vttLine.search(/(WEBVTT\s*(FILE)?.*)(\n)*/g) === -1) {
                    srtWithoutNumber += vttLine.replace(/(\d{2}:\d{2}:\d{2})\.(\d{3}\s+)\-\-\>(\s+\d{2}:\d{2}:\d{2})\.(\d{3}\s*)/g, '$1,$2-->$3,$4') + '\n';
                }
            }
            var srt = '';
            for (var [i, srtLine] of srtWithoutNumber.split('\n\n').entries()) {
                if (srtLine.startsWith('\n')) {
                    if (srtLine.replace('\n', '') !== '') {
                        srt += `${i+1}${srtLine}\n\n`;
                    }
                } else {
                    srt += `${i+1}\n${srtLine}\n\n`;
                }
            }
            downloadURI(`data:text/html,${srt}`, filename);
        }
    });
}

function downloadURI(uri, name) {
    var link = document.createElement('a');
    link.download = name;
    link.href = uri;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    delete link;
}