Decrypt and download HLS playlist(m3u8) of avgle.com video in browser.
// ==UserScript==
// @name avgleHPD - avgle HLS playlist downloader
// @namespace https://github.com/avotoko/avgle-HLS-playlist-downloader
// @version 0.1.2
// @icon https://avgle.com/favicon.ico
// @description Decrypt and download HLS playlist(m3u8) of avgle.com video in browser.
// @author avotoko
// @homepage https://avotoko.blogspot.com/2020/04/avgle-hls-playlist-downloader.html
// @supportURL https://github.com/avotoko/avgle-HLS-playlist-downloader
// @match *://avgle.com/video/*
// @connect *
// @run-at document-idle
// @grant unsafeWindow
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant GM_setClipboard
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_openInTab
// @grant GM_info
// @grant GM_registerMenuCommand
// ==/UserScript==
(function(){
"use strict";
let d = document, ver = "v.0.1.2";
function info(msg)
{
let e = d.querySelector('div.ahpd-info');
e && (e.textContent = msg);
}
function log()
{
console.log.apply(console,["[avgleHPD]"].concat(Array.from(arguments)));
}
function loginfo()
{
log.apply(console,arguments);
info.apply(console,arguments);
}
function appendStylesheet(rules, id)
{
let e = d.createElement("style");
if (id){
e.id = id;
}
e.type = "text/css";
e.innerHTML = rules;
d.getElementsByTagName("head")[0].appendChild(e);
}
function downloadPlaylist(playlist, filename)
{
let a = d.querySelector('.ahpd-download');
a.href = URL.createObjectURL(new Blob([playlist],{type:"application/x-mpegURL"}));
a.setAttribute("download",filename);
a.classList.remove("ahpd-hide");
}
function isSegmentUriEncrypted(playlist)
{
let a = playlist.split('\n');
for (let i = 0 ; i < a.length ; i++){
if (/^\s*$/.test(a[i])){
continue;
}
if (a[i].charAt(0) === "#"){
let tag = a[i];
if (/^#EXT-X-ENDLIST/.test(tag)){
break;
}
continue;
}
let uri = a[i];
if (uri.includes('!')){
return true;
}
}
return false;
}
function decryptPlaylist(playlist, options)
{
let a = playlist.split('\n');
for (let i = 0 ; i < a.length ; i++){
if (/^\s*$/.test(a[i])){
continue;
}
if (a[i].charAt(0) === "#"){
let tag = a[i];
if (/^#EXT-X-ENDLIST/.test(tag)){
break;
}
continue;
}
let uri = a[i];
if (! /^https:\/\//.test(uri)){
options.uri = uri;
options.decryptURI();
if (! options.uri){
log("can't decript uri:",uri);
throw Error("can't decrypt uri");
}
a[i] = options.uri;
}
}
return a.join('\n');
}
function main()
{
if (! videojs){
throw new Error("videojs not defined");
}
let s=document.getElementsByTagName("meta")[2].content;
let prevBeforeRequest = videojs.Hls.xhr.beforeRequest;
function restoreBeforeRequest()
{
videojs.Hls.xhr.beforeRequest = prevBeforeRequest;
log("restored videojs.Hls.xhr.beforeRequest");
}
videojs.Hls.xhr.beforeRequest = function (options) {
log("beforeRequest:",options.uri);
if (/\/(video)?playback/.test(options.uri)) {
log("got target request:",options.uri);
setTimeout(function () {
log("hooking request callback");
info("wating http response");
let prevCallback = options.callback;
options.callback = function(error,request){
loginfo("got response");
if (request.rawRequest.response.includes('#EXTM3U')){
let playlist = request.rawRequest.response;
loginfo("got hls playlist");
if (isSegmentUriEncrypted(playlist)){
loginfo("segment uri is encrypted");
let newOptions = videojs.Hls.xhr.beforeRequest({uri:"!dummy"});
if (typeof newOptions.decryptURI !== "function"){
throw new Error("can't retrieve decryptURI function");
}
log("decryptURI:\n",newOptions.decryptURI.toString());
loginfo("decrypting uri in playlist");
playlist = decryptPlaylist(playlist, newOptions);
log("decrypted playlist:\n"+ playlist);
info("decrypted playlist successfully");
downloadPlaylist(playlist, s + ".m3u8");
}
else {
log("segment uri is not encrypted");
downloadPlaylist(playlist, s + ".m3u8");
}
}
else {
loginfo("error: can't decrypt response!");
log("avgle-main-ah.js must already decrypt the response if the response is encrypted");
}
if (prevCallback){
prevCallback(error,request);
}
};
},0);
setTimeout(restoreBeforeRequest, 0);
}
return prevBeforeRequest(options);
};
log("hooked videojs.Hls.xhr.beforeRequest and waiting hls xhr request");
info("Please click the close button.");
d.querySelector("#player_3x2_container").addEventListener("click",()=>{
info("waiting hls xhr request");
log("the close button clicked");
});
log("waiting for the close button to be clicked");
}
try {
if (d.querySelector(".ahpd-area")){
alert("avgleHPD already executed");
return;
}
log("avgle HLS playlist downloader "+ver);
console.clear = function(){};
{
let s, e, sel = "div.container > div.row";
if (! (e = d.querySelector(sel))){
//log("element '"+sel+"' not found");
//alert("avgleHPD error: "+"element '+sel+' not found");
return;
}
appendStylesheet(".ahpd-area{display:flex; font-size:large; }.ahpd-ver{margin-right:5px; background-color:gold; font-weight:bold; text-align:center; vertical-align:middle; border:1px solid transparent; padding:8px 12px; width:min-content; white-space:nowrap; border-radius:4px; }.ahpd-info{margin-right:5px; background-color:beige; text-align:center; border:1px solid transparent; padding:8px 12px; width:min-content; white-space:nowrap; font-size:large; border-radius:4px; }.ahpd-download{font-weight:bold; padding:8px 12px; }.ahpd-download:hover{border:1px outset transparent; } .ahpd-hide{display:none;}");
let area = e.insertBefore(d.createElement("div"), e.firstElementChild);
area.className = "ahpd-area";
e = area.appendChild(d.createElement("div"));
e.className = "ahpd-ver";
e.textContent = "avgleHPD " + ver;
e = area.appendChild(d.createElement("div"));
e.className = "ahpd-info";
e.textContent = "avgleHPD information here";
e = area.appendChild(d.createElement("a"));
e.className = "btn-primary ahpd-download ahpd-hide";
e.textContent = "Download HLS Playlist";
}
main();
}
catch(e){
loginfo("error: " + e.message);
}
})();