// ==UserScript==
// @name Stream URL grabber
// @description Adds a button that gives you a .m3u8 file for a stream on Twitch or Hitbox.
// @include *://www.twitch.tv/*
// @include *://m.twitch.tv/*
// @include *://www.m.twitch.tv/*
// @include *://player.twitch.tv/*
// @include *://www.player.twitch.tv/*
// @include *://www.hitbox.tv/*
// @include *://api.twitch.tv/api/channels/*/access_token?grabber
// @namespace https://greasyforks.org/users/3167
// @run-at document-start
// @grant none
// @version 0.0.1.20161104053903
// ==/UserScript==
SERVERS = [
["San Francisco, US", "sfo"],
["Stockholm, Sweden", "arn"],
["London, UK", "lhr"],
["Amsterdam, NL", "ams"],
["Paris, France", "cdg"],
["Dallas, US", "dfw"],
["Frankfurt, Germany", "fra"],
["Washington, US", "iad"],
["New York, US", "jfk"],
["Los Angeles, US", "lax"],
["Prague, Czech Republic", "prg"],
["Seattle, US", "sea"],
["San Jose, US", "sjc"],
["Chicago, US", "ord"]
];
console.log("grabbing");
M3U8CONTENT = "";
function loadm3u8(url, filename)
{
var client = new XMLHttpRequest();
client.open('GET', url);
client.onreadystatechange = function() {
if (client.readyState==4 && client.status==200)
{
//alert(client.responseText);
M3U8CONTENT = client.responseText;
//console.log("m3u8:" + M3U8CONTENT);
document.body.innerHTML = 'Servers:';
var key;
for (key in SERVERS) {
var server = SERVERS[key];
parsem3u8(M3U8CONTENT, filename, server);
}
}
}
client.send();
}
function parsem3u8(text, filename, server)
{
//alert(client.responseText);
var temp = text;
//console.log("m3u8:" + M3U8CONTENT);
var oldpart1 = temp.split('NODE="video')[1].split('.')[0];
var oldpart2 = temp.split('NODE="video' + oldpart1 + '.')[1].split('0')[0];
//video24.arn01
var oldserver1 = "video" + oldpart1;
//video24-1.arn01
var oldserver2 = oldpart2 + "01";
var newserver1 = "video" + oldpart1;
var newserver2 = server[1] + "01";
console.log(oldserver1 + "." + oldserver2 + " -> " + newserver1 + "." + newserver2);
if (oldserver2==newserver2)
{
createm3u8(temp, filename, server, true, false);
}
else
{
//temp = temp.replace(oldserver1,newserver1);
//temp = temp.replace(oldserver2,newserver2);
temp = temp.split(oldserver1).join(newserver1);
temp = temp.split(oldserver2).join(newserver2);
createm3u8(temp, filename, server, false, false);
}
}
function createm3u8(text, filename, server, defaultserver, autostart)
{
if (autostart)
{
document.body.innerHTML = 'Link: ';
}
var textToWrite = text;
var textFileAsBlob = new Blob([textToWrite], {type:'text/plain'});
//var fileNameToSaveAs = filename + "_" + server[1] + '.m3u8';
var fileNameToSaveAs = filename + '.m3u8';
var downloadLink = document.createElement("a");
downloadLink.download = fileNameToSaveAs;
downloadLink.innerHTML = server[0];
if (defaultserver)
{
downloadLink.innerHTML = server[0] + " (default)";
}
// Firefox requires the link to be added to the DOM
// before it can be clicked.
downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
//downloadLink.onclick = destroyClickedElement;
//downloadLink.style.display = "none";
document.body.innerHTML = document.body.innerHTML + '<br>';
document.body.appendChild(downloadLink);
if (autostart)
{
//setTimeout(function(){
downloadLink.click();
window.close();
//},1000)
}
}
var host = window.location.host;
var grabbertest = document.URL.split("grabber")[1];
if (grabbertest!=undefined)
{
Object.defineProperty(navigator, 'userAgent', {
value: 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30'
});
if (host=="api.twitch.tv" || host=="www.api.twitch.tv")
{
//setTimeout(function(){
//console.log("loaded:" + GM_getValue("token"));
//var text = $("body").text();
var text = document.body.textContent;
//console.log("text:" + text);
var json = JSON.parse(text);
//console.log("json:" + json);
var user = document.URL.split("api.twitch.tv/api/channels/")[1].split("/")[0];
var token = json['token'];
var signature = json['sig'];
var url = location.protocol + '//usher.justin.tv/api/channel/hls/' + user + '.m3u8?allow_source=true&token=' + token + '&sig=' + signature;
var urle = encodeURI(url);
//console.log("url:" + url);
//console.log("urle:" + urle);
//window.open(url);
//$("body").html('<a href="' + urle + '">' + urle + '</a>"');
var manualserver = false;
if (manualserver)
{
document.body.innerHTML = '<a href="' + urle + '">Default server</a>';
var m3u8name = user;
loadm3u8(urle, m3u8name);
}
else
{
document.body.innerHTML = '<a href="' + urle + '">' + urle + '</a>';
//var link = document.createElement("a");
//link.download = urle;
//link.href = urle;
createm3u8(urle, user, ["default"], true, true)
//window.location.assign(urle);
//window.open(urle,"_blank");
//setTimeout(function(){
// link.click();
//window.close();
//},1000)
}
//localStorage.setItem("token", text);
//GM_setValue("token", text);
//},100)
console.log("Stream grab action on: " + host);
}
if (host=="m.twitch.tv" || host=="www.m.twitch.tv")
{
var grabberhook = function(retries) {
var videoelements = document.getElementsByTagName('video');
console.log(videoelements);
var videoelement = videoelements.item(0);
if (!videoelement || videoelement==null){
if (retries>0)
{
retries--;
setTimeout(function(){
grabberhook(retries);
},1000)
}
}
else
{
var videosource = videoelement.src;
var user = document.URL.split("twitch.tv/")[1].split("/")[0];
var urle = encodeURI(videosource);
createm3u8(urle, user, ["default"], true, true)
}
}
console.log("Stream grab action on: " + host);
grabberhook(10);
}
if (host=="player.twitch.tv" || host=="www.player.twitch.tv")
{
var grabberhook = function(retries) {
var videoplayback = document.getElementById("video-playback");
while (videoplayback && videoplayback!=null && videoplayback.childNodes.length > 1) {
videoplayback.removeChild(videoplayback.lastChild);
}
var videoelements = document.getElementsByTagName('video');
console.log(videoelements);
var videoelement = videoelements.item(0);
if (!videoelement || videoelement==null || videoelement.src==""){
if (retries>0)
{
retries--;
setTimeout(function(){
grabberhook(retries);
},1000)
}
}
else
{
var videosource = videoelement.src;
var vidurlsplit = videosource.split(".m3u8")[0].split("/");
var videoname = vidurlsplit[vidurlsplit.length-1];
var urle = encodeURI(videosource);
console.log(videosource);
createm3u8(videosource, videoname, ["default"], true, true)
}
}
console.log("Stream grab action on: " + host);
grabberhook(10);
}
return;
}
else
{
/*
openwithheaders_loaded = function()
{
console.log(this.responseText);
var json = JSON.parse(this.responseText);
console.log("json:");
console.log(json);
var user = document.URL.split("www.twitch.tv/")[1].split("/")[0];
var token = json['token'];
var signature = json['sig'];
var url = location.protocol + '//usher.justin.tv/api/channel/hls/' + user + '.m3u8?allow_source=true&token=' + token + '&sig=' + signature;
var urle = encodeURI(url);
document.body.innerHTML = '<a href="' + urle + '">' + urle + '</a>';
createm3u8(urle, user, ["default"], true, true)
}
openwithheaders = function(tokenurl)
{
var xhr = new XMLHttpRequest()
xhr.addEventListener("load", openwithheaders_loaded);
xhr.open("GET", tokenurl, true);
xhr.setRequestHeader("Client-ID", "jzkbprff40iqj646a697cyrvl0zt2m6");
xhr.send();
}
*/
var hook = function(retries)
{
console.log("Hooking... Retries left: " + retries);
var loaded = false;
if (retries>0)
{
retries--;
if (host=="www.twitch.tv")
{
var div = document.querySelectorAll('.channel-actions')[0];
if (div==null) {
div = document.querySelectorAll('.cn-metabar__more')[0];
}
if (div==null) {
setTimeout(function(){hook(retries);},1000);
return
}
var user = document.URL.split("twitch.tv/")[1].split("/")[0];
//var tokenurl = location.protocol + '//api.twitch.tv/api/channels/' + user + '/access_token?grabber';
//https://m.twitch.tv/outerheaven/v/98221000
//
//var tokenurl = location.protocol + '//m.twitch.tv/' + document.URL.split("twitch.tv/")[1] + '?grabber';
//
var tokenurl = location.protocol + '//player.twitch.tv/?!branding&!channelInfo&channel=' + user + '&grabber';
var isvideo = document.URL.split("/v/")[1];
if (isvideo!=undefined)
{
tokenurl = location.protocol + '//player.twitch.tv/?!branding&!channelInfo&video=v' + isvideo + '&grabber';
}
//var temp = div.innerHTML;
//div.innerHTML = temp + '<div class="action button"><a href="' + tokenurl + '" target="_blank">Grab stream link</a></div>';
//var clickaction = "window.open(" + tokenurl + ", 'newwindow', 'width=50%, height=50%'); return false;";
var clickaction = "window.open('" + tokenurl + "');";
//var clickaction = "openwithheaders('" + tokenurl + "');";
//div.innerHTML = temp + '<span class="ember-view"><span class="ember-view button action" onclick="' + clickaction + '">Grab stream link</span></span>';
var newspan = document.createElement("span");
newspan.innerHTML = '<div class="js-share-box balloon-wrapper inline-block"><span class="qa-share-box__button share-box__button button button--hollow mg-l-1" onclick="' + clickaction + '" style="cursor:pointer;">Grab stream link</span></div>';
div.appendChild(newspan);
//div.insertBefore(newspan, div.firstChild);
//
//clearInterval(grabberhook);
console.log("Stream link: " + tokenurl);
loaded = true;
}
if (host=="www.hitbox.tv")
{
var div = document.querySelectorAll('.status')[0];
if (div==null) {
setTimeout(function(){hook(retries);},1000);
return
}
var user = document.URL.split("hitbox.tv/")[1].split("/")[0];
var url = location.protocol + '//api.hitbox.tv/player/hls/' + user + '.m3u8';
//var temp = div.innerHTML;
var clickaction = "window.open('" + url + "');";
var newspan = document.createElement("span");
newspan.innerHTML = '<span onclick="' + clickaction + '" style="cursor:pointer;"> Grab stream link</span>';
div.appendChild(newspan);
//div.innerHTML = temp + '<span><a href="' + url + '" target="_blank">Grab stream link</a></span>';
//div.innerHTML = temp + '<span onclick="' + clickaction + '">Grab stream link</span>';
//clearInterval(grabberhook);
console.log("Stream link: " + url);
loaded = true;
}
}
if (loaded)
{
console.log("Stream grabber loaded on host: " + host);
}
else
{
console.log("Something went wrong when loading on host: " + host);
}
}
var retries = 10;
hook(retries);
}