const twitch = require("twitch-m3u8");
var tonicExpress = require("@runkit/runkit/express-endpoint/1.0.0");
var app = tonicExpress(module.exports);
//fetchretry+htmlparser
var originalFetch = require('isomorphic-fetch');
var fetch = require('fetch-retry')(originalFetch);
var HTMLParser = require('node-html-parser');
var cookieParser = require('cookie-parser');
//
app.use(cookieParser());
app.get('/', async(req, res) => {
//query
let quality = parseInt(req.query.q, 10);//0 = source
let channel = req.query.c;
//check channel
if ( channel == "" || channel === undefined ){
console.log(channel,'channel undefined');
//res.end('url/?c=channel&q=quality //0 = source');
channel = "Twitch"
};
let weburl = "https://www.twitch.tv/" + channel;
//cookies
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
};
let recenti = req.cookies.recent
if (!recenti){ recenti = []}
recenti.unshift(channel);
recenti = recenti.filter(onlyUnique);
if (recenti.length > 6) recenti.length = 6;
console.log(recenti)
res.cookie('recent', recenti, {encode: String, maxAge: 7 * 24 * 3600000});
recenti.shift();
//get m3u8
let errore
let streams = await twitch.getStream(channel.toLowerCase()).catch(err => errore = err);
console.log("Errore:"+errore);
//fetch meta title, image
let feci = await fetch(weburl, {
retryOn: async function(attempt, error, response) {
if (attempt > 2) return false;
//console.log("fetch-retry attempt "+ attempt);
let fecitext = await response.text();
let root = await HTMLParser.parse(fecitext);
image = await root.querySelector("meta[property='og:image']").getAttribute("content");
description = await root.querySelector("meta[property='og:description']").getAttribute("content");
console.log(description);
if (description == "Twitch is the world's leading video platform and community for gamers.") {
console.log(`retrying, attempt number ${attempt + 1}`);
return true;
}
}
});
//console.log("di Nuovo"+description);
//console.log("feci:"+feci);
//.then(function(response) {
//return response.text();
//});
//direct with quality query
if (quality >= 0){
console.log(`redirecting to ${channel} quality ${quality}`)
res.redirect(streams[quality].url);
} else {
console.log(`Creating html for ${channel}`);
let html = `<!DOCTYPE html>
<head>
<title>Twitch m3u8 - ${channel}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Raleway">
<link rel="icon" type="image/png" sizes="32x32" href="https://static.twitchcdn.net/assets/favicon-32-e29e246c157142c94346.png">
<link rel="icon" type="image/png" sizes="16x16" href="https://static.twitchcdn.net/assets/favicon-16-52e571ffea063af7a7f4.png">
<style>
body,h1 {font-family: "Raleway", sans-serif}
body, html {height: 100%}
.bgimg {
background-color: rgb(107, 91, 149);
min-height: 100%;
//background-image: url('https://wallpaperaccess.com/full/268724.jpg');
//background-position: center;
//background-size: cover;
}
form {
position: absolute;
top: 12px;
right: 24px;
background-color: #493e66;
width: 300px;
height: 44px;
border-radius: 5px;
display: flex;
flex-direction: row;
align-items: center;
}
input {
all: unset;
font: 16px system-ui;
color: #fff;
height: 100%;
width: 100%;
padding: 6px 10px;
}
button {
all: unset;
cursor: pointer;
width: 44px;
height: 44px;
}
::placeholder {
color: #fff;
opacity: 0.7;
}
svg {
color: #fff;
fill: currentColor;
width: 24px;
height: 24px;
padding: 10px;
}
</style>
</head>
<body>
<div class="bgimg w3-display-container w3-animate-opacity w3-text-white">
<div class="w3-display-topleft w3-padding-large w3-xlarge w3-hover-text-black">
<img src="https://static.twitchcdn.net/assets/favicon-32-e29e246c157142c94346.png" alt="Icon" width="32" height="32">
<a style="text-decoration:none" href="https://runkit.com/alexbestbro/twitch" target="_blank">Twitch m3u8</a>
</div>
<form id="form" class="w3-padding-large w3-xlarge">
<input type="search" id="query" name="c" placeholder="Search..." list="myList" required >
<button><svg viewBox="0 0 1024 1024"><path class="path1" d="M848.471 928l-263.059-263.059c-48.941 36.706-110.118 55.059-177.412 55.059-171.294 0-312-140.706-312-312s140.706-312 312-312c171.294 0 312 140.706 312 312 0 67.294-24.471 128.471-55.059 177.412l263.059 263.059-79.529 79.529zM189.623 408.078c0 121.364 97.091 218.455 218.455 218.455s218.455-97.091 218.455-218.455c0-121.364-103.159-218.455-218.455-218.455-121.364 0-218.455 97.091-218.455 218.455z"></path></svg></button>
<datalist id="myList">`
for (let y of recenti){
html += `<option value="${y}"></option>`
}
html += `</datalist>
</form>
<div class="w3-display-middle w3-center" style="margin-top: 10px;">
<img src="${image}" class="w3-animate-top" width="150" height="150" style="border-radius: 50%">
<h1 class="w3-xxxlarge w3-animate-top w3-hover-text-black">
<a style="text-decoration:none" href="${weburl}" target="_blank">
<b>${channel}</b>
</a>
</h1>
<a class="w3-mobile style="text-decoration:none">${description}</a>
<hr class="w3-border-white" style="margin:auto;width:100%">
<p class="w3-large w3-center w3-mobile w3-animate-bottom">`
if (!errore){
for (let x of streams){
html += `<a class="w3-mobile w3-hover-text-black" style="text-decoration:none" href="javascript:window.open('${x.url}','_blank','toolbar=0,width=1498,height=988,left=0');">${x.quality}</a> `
}
html += `</p>
<p class="w3-large w3-center w3-mobile w3-animate-bottom">
<a class="w3-mobile w3-hover-text-black" style="text-decoration:none" href="javascript:window.open('https://www.twitch.tv/popout/${channel}/chat?popout=','_blank','toolbar=no,width=400,height=988,left=1500');">Chat_only</a> 
<a class="w3-mobile w3-hover-text-black" style="text-decoration:none" href="javascript:window.open('https://runkit.io/alexbestbro/twitch-test/branches/master?chatsize=18&c=${channel}','_self');">Source+Chat</a><br>`
}else{html += `${errore}<br>`};
html += `</p>
</div>
</div>
</body>
</html>`
res.end(html);
}
});