您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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); } })();