avgleHPD - avgle HLS playlist downloader

Decrypt and download HLS playlist(m3u8) of avgle.com video in browser.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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);
	}
})();