ArenaVision Helper

A script that makes browsing ArenaVision website easier on touchscreens and on browsers with adblock enabled. It can retrieve direct AceStream links for browsers that don't support the player embedded in the page.

// ==UserScript==
// @name     ArenaVision Helper
// @name:it  Script di aiuto per ArenaVision
// @namespace    StephenP
// @version      2.1.0
// @description  A script that makes browsing ArenaVision website easier on touchscreens and on browsers with adblock enabled. It can retrieve direct AceStream links for browsers that don't support the player embedded in the page.
// @description:it  Uno script che rende la navigazione su Arenavision facile con i touchscreen e sui browser con adblock abilitato. È un grado di ottenere i link diretti AceStream per i browser che non supportano il lettore video integrato nella pagina.
// @author       StephenP
// @grant        GM.xmlHttpRequest
// @grant        GM.openInTab
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.deleteValue
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEVldC3+AwD/dW//rKr///+uAvmgAAAAAXRSTlMAQObYZgAAAFdJREFUCNdFjtERgCAMQ8MGRliADUS7ACX7zyQCan7yLtdLCiCQGzB8EodW8ERBzpM8YFIRU4NyqlasQoyeXBywq3Uo0anKfnw5UyaC5Tp73sJv4h9db9zQhg1MUu12QwAAAABJRU5ErkJggg==
// @include https://arenavision.cc/*
// @include https://www.arenavision.cc/*
// @include https://arenavision.in/*
// @include https://www.arenavision.in/*
// @include https://arenavision.biz/*
// @include https://www.arenavision.biz/*
// @include https://arenavision.us/*
// @include https://www.arenavision.us/*
// @include https://arenavision.link/*
// @include https://www.arenavision.link/*
// @include https://arenavision.live/*
// @include https://www.arenavision.live/*
// @include https://arenavision.club/*
// @include https://www.arenavision.club/*
// @include https://arenavision.site/*
// @include https://www.arenavision.site/*
// @include https://linkotes.com/*
// @include https://linkotes.com
// @connect *
// ==/UserScript==
//NOTE: I would like to avoid using the "@connect *" property, but the process of retrieving the acestream link is very complex and it will probably be changed day by day as the Arenavision's webmaster tries to prevent my script from working. As Tampermonkey validates every URL a XmlHttpRequest is going through, I need to be sure that any possible domain is allowed to successfully complete the request.
(async function(){
  var alwaysGuide=await getAlwaysGuide();
  var exceptionAuth=await getExceptionAuth();
  start(alwaysGuide,exceptionAuth);
})();
async function getAlwaysGuide(){
  var alwaysGuide=await GM.getValue("alwaysGuide");
  if(alwaysGuide===undefined){
   GM.setValue("alwaysGuide",true);
   alwaysGuide=true;
  }  
  return alwaysGuide;
}
async function getExceptionAuth(){
  var exceptionAuth=await GM.getValue("exceptionAuth");
  if(exceptionAuth!==undefined){
   GM.deleteValue("exceptionAuth");
  }  
  return exceptionAuth;
}
var infobox=setupInfobox();
var chLinks=listLinks();
var channel=0;
const altListName="OG";           //set the name for the alternate list of channel: "OG" was for Olympic Games
const altListLinkTag="olympic";		//set a text that is included in the links of the alternate list of channels, but not in the standard ArenaVision list. Olympic Channels used to include "olympic" in the href.
const isAlternateAvailable=false;	//set to true if an alternate list is available at the moment: could be the next Olympic Games, the World Soccer Championship or anything else.
const styleCSS=`
:root {
  --main-bg-color: #f0f0f0;
	--even-line-color: #f2f2f2;
  --odd-line-color: #e0e0e0;
	--main-font-color: black;
	--link-color: #f07100;
}
#selector{
	z-index: 3;
	position: fixed;
	text-align: center;
	padding: 0.5em;
	background-color: var(--main-bg-color);
	top: 0;
	left: 0;
	height: 1.5em;
	width: 100%;
	border-bottom: 0.2em solid var(--odd-line-color);
	filter: drop-shadow(0 0 0.5em black);
}
#intDiv{
	margin: auto;
}
#ch{
	font-size: inherit;
}
#go{
	font-size: inherit;
}
#alternateLabel{
	display: none;
	margin-left: 5px;
}
#alternate{
	display: none;
}
#footer{
	width: 100%;
	position: fixed;
	bottom: 0;
	left: 0;
	background-color: var(--main-bg-color);
	border-top: 0.2em solid var(--odd-line-color);
	filter: drop-shadow(0 0 0.5em black);
}
#footerText{
  margin: 1em;
}
a{
	color: var(--link-color); 
}
#changeAlwaysGuide{
  cursor: pointer;
}
td{
	padding-right: 0.5em;
	padding-left: 0.5em;
}
table{
	padding-top: 5em;
  padding-bottom: 5em;
}
#tableWrapper{
	overflow-x: auto;
}
body{
	margin: 0;
	font-family: Arial;
	font-size: 1em;
	background-color: var(--main-bg-color);
	color: var(--main-font-color);
}
tr.even{
	background-color: var(--even-line-color);
}
tr.odd{
	background-color: var(--odd-line-color);
}
#infobox{
	position: fixed;
	bottom: 2em;
	right: 2em;
	z-index: 9999;
	background-color: var(--main-bg-color);
	border-radius: 5px;
	padding: 1em;
	color: var(--main-font-color);
	box-shadow: 5px 5px 5px rgba(0,0,0,0.5);
	text-align: left;
}
h1{
	display: none;
}
@media screen and (min-width: 1921px) and (max-resolution: 150dpi){
	body{
		font-size: 3em;
	}
	#footerText{
		font-size: 0.8em;
	}
}
@media (max-aspect-ratio: 1/1) or (min-aspect-ratio: 5/2){
  #footer {
    position: inherit;
		padding-top: 1em;
		padding-bottom: 2em;
		width: auto;
		margin-bottom: 3em;
		filter: none;
  }
  table{
    padding-top: 1em;
    padding-bottom: 1em;
  }
	#selector{
		top: unset;
		bottom: 0;
		border-bottom: none;
		border-top: 0.2em solid var(--odd-line-color);
		padding: 1.5em;
	}
	h1{
    display: block;
    font-size: 2em;
  }
}
@media (prefers-color-scheme: dark){
	:root {
    --main-bg-color: #303030;
    --even-line-color: #323232;
    --odd-line-color: #202020;
		--main-font-color: #dfdfdf;
    --link-color: orange;
  }
}
`;
function start(alwaysGuide,exceptionAuth){
 	if(exceptionAuth){
    if(document.location.href.includes(exceptionAuth)){
 		}
    else{
      removeIFrames();
    }
  }
  else{
    removeIFrames();
  }
  console.log(document.location);
  if(!document.location.href.includes("linkotes")){
    var newHTML='<div id="selector"><div id="intDiv"><label for="ch">Channel: </label><input id="ch" name="Channel" type="number" max="99" min="1" value=""><button id="go">GO!</button><label id="alternateLabel" for="alternate">'+altListName+'</label><input type="checkbox" id="alternate"></div></div><h1>ArenaVision Events</h1>';
    var newHTMLFooter='<div id="footer"><p id="footerText">If ArenaVision is actively blocking this script you can use <a id="linkotesLink" href="https://linkotes.com/arenavision/">linkotes.com</a> instead: it provides the direct Acestream IDs/AceLive files for the same ArenaVision channels. If you don\'t want to load the guide automatically when you visit ArenaVision, <a id="changeAlwaysGuide">click here</a>.</p></div>';
    if(alwaysGuide===false){
      newHTMLFooter=newHTMLFooter.replace("If you don\'t want","If you want");
    }
    var menus=document.getElementsByClassName("expanded");
    for(var m=0;m<menus.length;m++){
      menus[m].children[0].removeAttribute("href");
    }
    var style=document.createElement("style");
    style.innerHTML=styleCSS;
    document.getElementsByTagName("HEAD")[0].appendChild(style); 
    document.getElementsByTagName("HEAD")[0].innerHTML+="<meta name='viewport' content='width=device-width, user-scalable=no'>";
    if(document.getElementsByClassName("title").length>0){
      if(document.getElementsByClassName("title")[0].innerHTML.toLowerCase().includes("events")){
        var tableList=document.getElementsByTagName("TABLE");
        var table;
        var h=0;
        for(;h<tableList.length;h++){
          try{
            if((tableList[h].children[0].children[0].children.length==6)){
              table=tableList[h].cloneNode(true);            
              document.body.innerHTML="";
              document.head.innerHTML="";
              document.getElementsByTagName("HEAD")[0].appendChild(style);
              document.getElementsByTagName("HEAD")[0].innerHTML+="<meta name='viewport' content='width=device-width, user-scalable=no'>";
              var lines=table.getElementsByTagName("TR");
              var totalLines=lines.length-1;
              lines[0].style.fontWeight="bold";
              lines[0].className="even";
              for(var i=1;i<totalLines;i++){
                try{
                  const regexp = /([0-9])+/g
                  const matches = lines[i].lastElementChild.textContent.matchAll(regexp);
                  var txt=lines[i].lastElementChild.textContent.toString();
                  if(isAlternateAvailable===false){//adds clickable links to the channel numbers in the guide: it currently doesn't work for alternate (e.g. olympic) channels, as they would be replaced by normal channels with the current script
                    try{
                      lines[i].lastElementChild.innerHTML='';
                      let lastIndex=0;
                      for (const match of matches) {
                        lines[i].lastElementChild.innerHTML+=txt.substring(lastIndex,match.index)
                        lines[i].lastElementChild.innerHTML+=`<a href="javascript:document.getElementById('ch').value='`+txt.substring(match.index,match.index + match[0].length)+`';document.getElementById('go').click();">`+txt.substring(match.index,match.index + match[0].length)+`</a>`;
                        lastIndex=match.index + match[0].length;
                      }
                      lines[i].lastElementChild.innerHTML+=txt.substring(lastIndex);
                    }
                    catch(e){
                      console.log(e);
                      lines[i].lastElementChild.innerHTML=txt;
                    }
                  }
                  if(i%2==0){
                    lines[i].className="even";
                  }
                  else{
                    lines[i].className="odd";
                  }
                }
                catch(err){
                  console.log(err);
                  lines[i].parentNode.removeChild(lines[i]);
                  i--;
                  totalLines--;
                }
              }
              document.body.innerHTML=newHTML;
              if(isAlternateAvailable){
                document.getElementById("alternate").style.display="inline";
                document.getElementById("alternateLabel").style.display="inline";
              }
              var d=document.createElement("DIV");
              d.id="tableWrapper";
              d.appendChild(table);
              document.body.appendChild(d);
              document.body.innerHTML+=newHTMLFooter;
              document.body.appendChild(infobox);
              document.getElementById("go").addEventListener("click", function(){goToChannel()});
              document.getElementById("ch").addEventListener("keypress", function(e){if(e.key=='Enter'){goToChannel()}});
              h=-1;
              break;
            }
          }
          catch(err){
            console.log(err);
          }
        }
        if(h==tableList.length){
          ignoreTableReconstruction(newHTML,newHTMLFooter);
        }
      }
      else if((document.location.href.includes("/stream/live/arenavision-"))&&(!document.location.href.includes("?exception="+exceptionAuth))){
        var links=document.getElementsByTagName("SCRIPT");
        for(var j=0;j<links.length;j++){
          if(links[j].innerHTML.includes('window.atob("')){
            extractLink(links[j].innerHTML);
          }
        }
        
        document.body.innerHTML+=newHTMLFooter;
      }
      else if(document.location.href.includes("?exception="+exceptionAuth)){
        var videoFrame=document.querySelectorAll("#main .content iframe");
        if(videoFrame.length===1){
          document.location.href=videoFrame[0].src;
        }
        else{
        	document.body.innerHTML+=newHTMLFooter;
        }
      }
      else{
         document.body.innerHTML+=newHTMLFooter;
      }
    }
    else{
      if(alwaysGuide){
        document.body.style.display="none";
        let menuLinks=document.querySelectorAll(".leaf>a");
        let found=false;
        for(let l of menuLinks){
          if((l.textContent.toLowerCase().includes("guide"))||(l.textContent.toLowerCase().includes("events"))){
            if(l.hasAttribute("href")){
              window.location.href=l.getAttribute("href");
              found=true;
              break;
            }
          }
        }
        if(!found){
          document.body.innerHTML+=newHTMLFooter;
          document.body.style.display="block";
        }
      }
      else{
        document.body.innerHTML+=newHTMLFooter;
      }
    }
    let changeAlwaysGuideBtn=document.getElementById("changeAlwaysGuide");
    if(changeAlwaysGuideBtn){
      changeAlwaysGuideBtn.addEventListener('click',function(){changeAlwaysGuide(alwaysGuide)});
    }
  }
}
function changeAlwaysGuide(alwaysGuide){
  if(alwaysGuide){
    alwaysGuide=false;
    GM.setValue("alwaysGuide",false);
    window.location.reload(true);
  }
  else{
    alwaysGuide=true;
    GM.setValue("alwaysGuide",true);
    window.location.reload(true);
  }
}
function ignoreTableReconstruction(newHTML,newHTMLFooter){
  document.body.innerHTML=newHTML+document.body.innerHTML+newHTMLFooter;
  document.body.appendChild(infobox);
  infobox.innerHTML="Can't decode events table. I'll just add the channel selector.";
  infobox.style.display="block";
  setTimeout(function(){infobox.style.display="none";},5000);
  document.getElementById("go").addEventListener("click", function(){goToChannel()});
  document.getElementById("ch").addEventListener("keypress", function(e){if(e.key=='Enter'){goToChannel()}});
}
function goToChannel(){
  channel=document.getElementById("ch").value.toString();
  let channelName=channel;
  if(document.getElementById("alternate").checked===true){
    channelName=altListName+channel;
  }
  infobox.innerHTML="Loading channel "+channelName+"...";
  infobox.style.display="block";
  //Old method, left for reference
  /*var globalURL=location.href.substring(0,location.href.lastIndexOf("/")+1)+"arenavision-";
  var channelURL;
  channelURL=globalURL+channel;*/
  //New method
  var offset=0;
  if(document.getElementById("alternate").checked===true){
		for(let chLink of chLinks){
      offset++;
      if(chLink.includes(altListLinkTag)){
        offset--;
        break;
      }
    }
  }
  var channelURL=chLinks[offset+parseInt(channel)-1];
  console.log(channelURL);
  try {  
    var request = new XMLHttpRequest();  
    request.onreadystatechange = function() {
      
      if (request.readyState == 4) {  
        if(request.status==404){
          infobox.innerHTML="Channel "+channelName+" is not available.";
          setTimeout(function(){infobox.style.display="none";},5000);
        }
        else if(request.status==403){
          infobox.innerHTML="Error 403: the access to the page requested is forbidden.";
          setTimeout(function(){infobox.style.display="none";},5000);
        }
        else if(request.status==1006){
          infobox.innerHTML="Your IP address has been banned from ArenaVision server. Reinitiate your connection to get a new IP.";
          setTimeout(function(){infobox.style.display="none";},10000);
        }
        else{
          var channelPageHTML = request.responseText;
          if(channelPageHTML.includes('window.atob("')){
            extractLink(channelPageHTML);
          }
          else{
            var exceptionAuth=Math.random().toString().substr(2);
            GM.setValue("exceptionAuth",exceptionAuth);
            if(channelPageHTML.includes('class="ray_id"')){//if server needs to do a check for DDos protection, tries to load the channel page in an IFrame
              useIframe(channelURL,exceptionAuth);
              infobox.innerHTML="Trying to load channel "+channelName+" with another method...";
            }
            else{                                           //otherwise, if it was just impossible to retrieve the link, the script opens the original page in a new tab 
              openTab(channelURL+"?exception="+exceptionAuth,false);   //and avoids the execution of the script, so you can try to watch the stream on the page.
              infobox.style.display="none";
            }
          }
        }
      }  
    };
    request.open('GET', channelURL); 
    request.send();
  }
  catch (err) { 
    useIframe(channelURL,authCode);
    infobox.innerHTML="Trying to load channel "+channelName+" with another method...";
    console.log(err);
  }
}
function useIframe(channelURL,exceptionAuth){//loads the channel page in an IFrame, required if XHR returns a DDos protection page.
  var chpage=document.createElement("IFRAME");
  chpage.src=channelURL+"?exception="+exceptionAuth;
  chpage.style.width="0";
  chpage.style.height="0";
  chpage.style.border="none";
  chpage.style.position="fixed";
  document.body.appendChild(chpage);
  chpage.onload = function() {
    extractLink(chpage.contentDocument.documentElement.innerHTML);
  }
}
function extractLink(page){//This function should support both AceStreams and HTTP streams.
  try{
    var linkStart=page.indexOf('window.atob("')+13;
    var linkEnd=page.indexOf('"',linkStart);
    var acestream=window.atob(page.substring(linkStart,linkEnd));
    console.log("Decoded link: "+acestream);
    linkStart=acestream.indexOf("?id=")+4;
    if(linkStart==3){//indexOf("?id=")==0 
      linkStart=acestream.indexOf("?url=")+5;
      if(linkStart==4){//indexOf("?id=")==0 
        linkStart=acestream.indexOf("http");
      }
    }
    linkEnd=acestream.indexOf("&");
    if(linkEnd==-1){
      acestream=acestream.substring(linkStart);
    }
    else{
      acestream=acestream.substring(linkStart,linkEnd);
    }
    if(acestream.includes("pb.m3u8")){
      GM.xmlHttpRequest({
        method: "GET",
        url: "https://"+window.location.hostname+acestream,
        onload: function(response) {
          aceResolver(response.finalUrl);
        },
        onerror: function(response) {//shall be the same as onload
          aceResolver(response.finalUrl);    
        }
      });  
    }
    else{
      openLink(acestream);
    }
  }
  catch(err){
    infobox.innerHTML="Failed to load the link.";
    setTimeout(function(){infobox.style.display="none";},5000);
  }
}
function aceResolver(prevUrl){
  if(prevUrl.indexOf("?url=")>-1){
    prevUrl=prevUrl.substring(prevUrl.indexOf("?url=")+5);
  }
  console.log("Executing recursive resolver on "+prevUrl);
  GM.xmlHttpRequest({
    method: "GET",
    headers: {
      "User-Agent": "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4371.0 Safari/537.36 AceStream /3.1.8.0",
      "Referer": chLinks[channel-1]
    },
    url: prevUrl,
    onload: function(response) {
      console.log(response.finalUrl);
      //infobox.innerHTML="Downloading the link...";
      infobox.innerHTML="Opening the link...";
      setTimeout(function(){infobox.style.display="none";},5000);
      if(response.finalUrl.match(/[0-9a-fA-F]{40}/g)!==null){
        openLink(response.finalUrl.match(/[0-9a-fA-F]{40}/g));
      }
      else{
        openTab(response.finalUrl,true);
      }
      //openTab(response.finalUrl,true);
    },
    onerror: function(response) {
      console.log(response);
      if((response.finalUrl!==null)&&(response.finalUrl!==undefined)){
        if(response.finalUrl.includes("127.0.0.1")){
          aceResolver(response.finalUrl);
        }
        else{
          if(response.finalUrl.match(/[0-9a-fA-F]{40}/g)!==null){
            openLink(response.finalUrl.match(/[0-9a-fA-F]{40}/g));
          }
          else{
            openTab(response.finalUrl,true);
          }
        }
      }
      else{
        infobox.innerHTML="Failed to load the link.";
        setTimeout(function(){infobox.style.display="none";},5000);
      }
    }
  });    
}
function openLink(acestream){
  console.log('AceStream ID or HTTP(S) URL: '+acestream);
  if(acestream!==null){
    infobox.innerHTML="Opening the link...";
    setTimeout(function(){infobox.style.display="none";},5000);
    if(!acestream.includes("://")){
      openTab('acestream://'+acestream,true);
    }
    else{
      openTab(acestream,true);
    }
  }
  else{
    infobox.innerHTML="I can't open the link. Open Ace Player manually and then try again.";
    setTimeout(function(){infobox.style.display="none";},5000);
  }
}
function setupInfobox(){
  var infobox=document.createElement("DIV");
  infobox.id="infobox";
  infobox.innerHTML="[empty]";
  infobox.style.display="none";
  return infobox;
}
function listLinks(){
  let t=document.getElementsByClassName("leaf");
  var m=[];
  for (let x of t){
    let l=x.getElementsByTagName("A");
    if((l.length==1)&&(x.parentNode.parentNode.className.includes("expanded"))){
    	m.push(l[0].href);
    }
  }
  console.log("LINKS:");
  console.log(m);
  return m;
}
function openTab(url,method){//this function roughly provides compatibility for different userscript managers.
  if(method===true){
    window.location.href=url;
  }
  else{
    try{
      //GM.openInTab(url,method);
      window.open(url,"_new");
    }
    catch(err){
      window.open(url,"_new");
    }
  }
}
function removeIFrames(){
  let iframes=document.getElementsByTagName("IFRAME");
  while(iframes.length>0){//removes iframes, none of them is necessary as they are only used for ads both on arenavision and linkotes
    iframes[0].remove();
  }
}