// ==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 // @downloadURL https://update.greasyfork.icu/scripts/419624/Hulucom%20Subtitle%20Downloader.user.js // @updateURL https://update.greasyfork.icu/scripts/419624/Hulucom%20Subtitle%20Downloader.meta.js // ==/UserScript== waitForKeyElements('.PlayerSettingsGroup', pageDidLoad); function pageDidLoad(jNode) { jNode.append('
' + '' + '' + '' + '
'); 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(/>/g, '>').replace(/</, '<'); 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; }