Skip to content

Instantly share code, notes, and snippets.

@tausackhn
Last active August 1, 2016 09:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tausackhn/9cb96073b60b6c52edb8 to your computer and use it in GitHub Desktop.
Save tausackhn/9cb96073b60b6c52edb8 to your computer and use it in GitHub Desktop.
Getting Twitch.tv live source playlist
// ==UserScript==
// @name Twitch Playlists
// @namespace taus-guit
// @description Getting m3u8 playlist on live twitch channel.
// @include *.twitch.tv/*
// @version 1
// @grant GM_setClipboard
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// ==/UserScript==
GM_addStyle(" \
#playlistPanel { \
position:absolute; \
top:20%; \
left:60px; \
color:#D3D3D3; \
width:420px; \
height:110px; \
z-index:9002; \
border-radius:3px; \
border:1px solid #000; \
background-color:#1e1e1e; \
box-shadow:-1px 0 0 rgba(0,0,0,0.3),-2px 0 0 rgba(0,0,0,0.2),-3px 0 0 rgba(0,0,0,0.1),1px 0 0 rgba(0,0,0,0.3),2px 0 0 rgba(0,0,0,0.2),3px 0 0 rgba(0,0,0,0.1),0 -1px 0 rgba(0,0,0,0.3),0 -2px 0 rgba(0,0,0,0.2),0 -3px 0 rgba(0,0,0,0.1),0 1px 0 rgba(0,0,0,0.3),0 2px 0 rgba(0,0,0,0.2),0 3px 0 rgba(0,0,0,0.1); \
} \
#playlistPanel #header { \
border-top-left-radius: 3px; \
border-top-right-radius: 3px; \
background-color: #282828; \
width: 100%; \
border-bottom: 1px solid #111; \
font: 12.5px/1.5 \"Helvetica Neue\",Helvetica,Arial,sans-serif; \
font-size: 20px; \
color: #ADAFAE; \
} \
#playlistPanel #close { \
font-size: 30px; \
float: right; \
right: 20px; \
top: 0px; \
position: absolute; \
color: #FFF; \
text-shadow: 0px 1px 0px #000; \
opacity: 0.2; \
} \
#playlistPanel #close:hover, #playlistPanel #close:focus { \
color: #FFF; \
text-decoration: none; \
cursor: pointer; \
opacity: 0.4; \
} \
#playlistPanel #logo { \
text-shadow: 1px 1px 3px #000; \
font-size: 25px; \
vertical-align: bottom; \
padding: 10px; \
padding-right: 15px; \
} \
#playlistPanel #svg_logo { \
vertical-align: top; \
fill: rgba(255, 255, 255, 0.5); \
} \
#playlistPanel .buttonsPanel { \
padding: 10px; \
} \
#playlistPanel .playlist_button { \
min-width: calc(20% - 10px); \
margin: 5px; \
} \
#playlistPanel #live_text { \
font-size: 10px; \
visibility: hidden; \
margin-left: 20px; \
} \
");
function createPlaylistButton(name) {
panel = document.querySelector(".buttonsPanel");
var button = document.createElement('button');
button.className = "playlist_button button primary";
button.value = name;
button.innerHTML = "<span>" + name + "</span>";
button.disabled = "disabled";
return button;
}
function getPlaylistByUrl(url) {
var reName = /\.tv\/([^\/]*)/;
var reNameId = /\.tv\/([^/]*)\/v\/([0-9]*)/;
if ((list = reNameId.exec(url)) != null) {
getVodPlaylist(list[1], list[2]);
} else {
list = reName.exec(url);
getLivePlaylist(list[1]);
}
}
function getLivePlaylist(name) {
var tokenURL = "https://api.twitch.tv/api/channels/" + name + "/access_token";
var playlistURLs = [];
GM_xmlhttpRequest({
method: "GET",
url: tokenURL,
onload: function(s) {
var token = JSON.parse(s.responseText);
var playlistsURL = "http://usher.justin.tv/api/channel/hls/" + name + ".m3u8" +
"?p=435238" +
"&sig=" + token.sig +
"&token=" + token.token +
"&allow_source=true" +
"&player=twitchweb";
GM_xmlhttpRequest({
method: "GET",
url: playlistsURL,
onload: function(response) {
s = response.responseText;
if (response.status != 404) {
var reURL = /NAME=\"([^\"]*)\"(?:.*\n)+?(http[\S]*)/g;
var match;
while ((match = reURL.exec(s)) !== null) {
playlistURLs.push({'name': match[1], 'url': match[2]});
}
playlistURLs.forEach(function(item, i, arr) {
var button = document.querySelector(".playlist_button[value=\"" + item.name + "\"]");
button.removeAttribute("disabled");
button.onclick = function() {
GM_setClipboard(item.url, "text");
notify(item.name + " playlist URL copied");
}
});
} else {
document.querySelector("#live_text").style.visibility = "visible";
}
}
});
}
});
}
function getVodPlaylist(name, id) {
var tokenURL = "https://api.twitch.tv/api/vods/" + id + "/access_token";
var playlistURLs = [];
GM_xmlhttpRequest({
method: "GET",
url: tokenURL,
onload: function(s) {
var token = JSON.parse(s.responseText);
var playlistsURL = "http://usher.twitch.tv/vod/" + id +
"?nauthsig=" + token.sig +
"&nauth=" + token.token +
"&allow_source=true";
GM_xmlhttpRequest({
method: "GET",
url: playlistsURL,
onload: function(response) {
m3u8list = response.responseText;
if (response.status != 404) {
var reURL = /NAME=\"([^\"]*)\"(?:.*\n)+?(http[\S]*)/g;
var match;
while ((match = reURL.exec(m3u8list)) !== null) {
playlistURLs.push({'name': match[1], 'url': match[2]});
}
playlistURLs.forEach(function(item, i, arr) {
var button = document.querySelector(".playlist_button[value=\"" + item.name + "\"]");
button.removeAttribute("disabled");
button.onclick = function() {
GM_setClipboard(item.url, "text");
notify(item.name + " playlist URL copied");
}
});
} else {
document.querySelector("#live_text").style.visibility = "visible";
}
}
})
}
})
}
function notify(text) {
// Notify when copied to clipboard
if (!("Notification" in window)) {
alert(text);
} else if (Notification.permission === "granted") {
// If it's okay let's create a notification
var notification = new Notification(text);
} else if (Notification.permission !== 'denied') {
Notification.requestPermission(function(permission) {
// If the user accepts, let's create a notification
if (permission === "granted") {
var notification = new Notification(text);
}
});
}
};
function addButton() {
var smallMoreButton = document.querySelector("#small_more");
if (null != smallMoreButton) {
console.log('Call');
var item = document.createElement('dd');
item.id = "live_url";
item.className = "warp__item";
var button = document.createElement('a');
button.className = "warp__tipsy";
button.title = "Get live broadcast URL";
button.innerHTML = '<figure class="warp__avatar"><svg viewBox="0 0 512 512" version="1.1" id="playlist_button" x="0px" y="0px" width="16px" height="16px"> \
<path d="M14,434 14,479 164,479 164,434"></path> \
<path d="M14,334 14,379 164,379 164,334"></path> \
<path d="M14,234 14,279 284,279 284,234"></path> \
<path d="M14,134 14,179 284,179 284,134"></path> \
<path d="M14,34 14,79 284,79 284,34"></path> \
<path d="M253.803,471.326c-20.471-5.189-39.48-20.199-44.87-41.26c-5.641-20.109,1.74-41.701,14.399-57.58 c23.49-29.4,63.79-44.471,100.78-37.52c0.3-98.411-0.02-196.831,0.16-295.241c8.92,13.15,18.55,26.01,30.59,36.51 c25.59,23.2,57.43,37.94,83.74,60.2c22.68,19.311,42.37,43.26,52.42,71.62c7.37,20.28,8.87,42.55,4.58,63.68 c-5.65,27.109-22.28,42.379-40.2,63.301c6.1-25.25,13.06-42.271,5.26-67.611c-5.189-17.57-16.58-32.82-30.76-44.209 c-16.21-13.09-35.2-22.13-54.27-30.23c-0.25,65.02-0.03,130.051-0.11,195.081c1,22.449-10.45,43.93-26.85,58.68 C323.553,469.467,286.843,479.816,253.803,471.326"></path> \
</svg></figure>';
var body = document.body || document.getElementsByTagName('body')[0];
var playlistPanel = document.createElement('div');
playlistPanel.id = "playlistPanel";
playlistPanel.style = "display: none;";
body.appendChild(playlistPanel);
playlistPanel.innerHTML = '<div id="header"> \
<span id="logo"> \
<svg viewBox="0 0 512 512" version="1.1" id="svg_logo" x="0px" y="0px" width="30px" height="45px"> \
<path d="M14,434 14,479 164,479 164,434"></path> \
<path d="M14,334 14,379 164,379 164,334"></path> \
<path d="M14,234 14,279 284,279 284,234"></path> \
<path d="M14,134 14,179 284,179 284,134"></path> \
<path d="M14,34 14,79 284,79 284,34"></path> \
<path d="M253.803,471.326c-20.471-5.189-39.48-20.199-44.87-41.26c-5.641-20.109,1.74-41.701,14.399-57.58 c23.49-29.4,63.79-44.471,100.78-37.52c0.3-98.411-0.02-196.831,0.16-295.241c8.92,13.15,18.55,26.01,30.59,36.51 c25.59,23.2,57.43,37.94,83.74,60.2c22.68,19.311,42.37,43.26,52.42,71.62c7.37,20.28,8.87,42.55,4.58,63.68 c-5.65,27.109-22.28,42.379-40.2,63.301c6.1-25.25,13.06-42.271,5.26-67.611c-5.189-17.57-16.58-32.82-30.76-44.209 c-16.21-13.09-35.2-22.13-54.27-30.23c-0.25,65.02-0.03,130.051-0.11,195.081c1,22.449-10.45,43.93-26.85,58.68 C323.553,469.467,286.843,479.816,253.803,471.326"></path> \
</svg> \
</span> \
Broadcast .m3u8 urls \
<span id="live_text">Channel is offline</span> \
<span id="close">×</span></div> \
<div class="buttonsPanel"></div>';
qualityList = ["Source", "High", "Medium", "Low", "Mobile"];
qualityList.forEach(function(item, i, arr) {
buttonsPanel = document.querySelector(".buttonsPanel");
buttonsPanel.appendChild(createPlaylistButton(item));
});
playlistPanel.childNodes[0].lastChild.onclick = function() {
playlistPanel.style = "display: none;";
buttons = document.querySelectorAll("button.playlist_button");
for (var i = 0; i < buttons.length; ++i) {
buttons[i].disabled = "disabled";
}
document.querySelector("#live_text").style.visibility = "hidden";
};
button.onclick = function() {
playlists = getPlaylistByUrl(document.URL);
playlistPanel.style = "display: block;";
};
item.appendChild(button);
smallMoreButton.parentNode.insertBefore(item, smallMoreButton);
}
}
window.setTimeout(addButton, 4000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment