avgleHPD - avgle HLS playlist downloader

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

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

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