// ==UserScript== // @name Hulu.com Subtitle Downloader // @namespace https://www.hulu.com // @version 1.0.1 // @description Downloads subtitle from Hulu.com as SRT format // @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/2199-waitforkeyelements/code/waitForKeyElements.js?version=6349 // @grant GM_xmlhttpRequest // @copyright 2021, subdiox // @downloadURL none // ==/UserScript== waitForKeyElements('.PlayerSettingsGroup', pageDidLoad); function pageDidLoad(jNode) { jNode.append('
' + '' + '' + '' + '
'); document.getElementById('download-button').addEventListener('click', downloadDidClick); } function downloadDidClick() { var x = new XMLHttpRequest(); x.open('GET', 'https://discover.hulu.com/content/v5/deeplink/playback?namespace=entity&schema=1&id=' + window.location.href.split('/').pop(), !1); x.withCredentials = !0; x.send(null); var json = JSON.parse(x.responseText); var eab_id = json.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=' + json.eab_id, !1); x.withCredentials = !0; x.send(null); json = JSON.parse(x.responseText); var filename = ''; var series = json.items[0].series_name; var season = json.items[0].season; var number = json.items[0].number; var name = json.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 = new XMLHttpRequest(); x.open('GET', 'https://www.hulu.com/captions.xml?content_id=' + eab_id, !1); x.withCredentials = !0; x.send(null); var parser = new DOMParser(); var xml = parser.parseFromString(x.responseText, "text/xml"); var url = xml.getElementsByTagName('en')[0].childNodes[0].nodeValue; var vttUrl = url.replace('captions', 'captions_webvtt').replace('.smi', '.vtt'); GM_xmlhttpRequest({ method: 'GET', url: vttUrl, onload: function (response) { var srt = ''; var vtt = response.responseText.replace(/>/g, '>').replace(/</, '<'); for (var vttLine of vtt.split('\n')) { srt += vttLine.replace(/(WEBVTT\s*(FILE)?.*)(\r\n)*/g, '').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') + '\r\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; }