Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ device_private_key
client_id.bin
private_key.pem
.idea
remote.json
remote.json
7 changes: 7 additions & 0 deletions background.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ window.requests=[];
window.bodys=[];
window.targetIds=[];
window.pageURL="";
window.title = "";
window.clearkey="";
window.manifests = [];

chrome.storage.local.get("isBlock", (value) => {
window.isBlock = value.isBlock;
Expand Down Expand Up @@ -62,11 +64,16 @@ chrome.runtime.onMessage.addListener(
case "PSSH":
window.psshs.push(request.text)
window.pageURL=sender.tab.url
window.title = sender.tab.title
window.targetIds=[sender.tab.id, sender.frameId]
break;
case "CLEARKEY":
window.clearkey=request.text
break;
case "MANIFEST":
window.manifests.push(JSON.parse(request.text))
break;

}
}
);
Expand Down
7 changes: 7 additions & 0 deletions content.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ document.addEventListener('clearkey', (e) => {
},null);
});

document.addEventListener('manifest', (e) => {
chrome.runtime.sendMessage({
type: "MANIFEST",
text: e.detail
},null);
});

//Fetch from original origin
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
Expand Down
129 changes: 129 additions & 0 deletions inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,132 @@ MediaKeySession.prototype.update = function(response) {
return result;
}
};



function uint8ArrayToString(uint8array) {
return String.fromCharCode.apply(null, uint8array)
}

class Evaluator {
static isDASH(text) {
return text.includes('<mpd') && text.includes('</mpd>');
}

static isHLS(text) {
return text.includes('#extm3u');
}

static isHLSMaster(text) {
return text.includes('#ext-x-stream-inf');
}

static isMSS(text) {
return text.includes('<smoothstreamingmedia') && text.includes('</smoothstreamingmedia>');
}

static getManifestType(text) {
const lower = text.toLowerCase();
if (this.isDASH(lower)) {
return "DASH";
} else if (this.isHLS(lower)) {
if (this.isHLSMaster(lower)) {
return "HLS_MASTER";
} else {
return "HLS_PLAYLIST";
}
} else if (this.isMSS(lower)) {
return "MSS";
}
}
}

const originalFetch = window.fetch;
window.fetch = function() {
return new Promise(async (resolve, reject) => {
originalFetch.apply(this, arguments).then((response) => {
if (response) {
response.clone().text().then((text) => {
const manifest_type = Evaluator.getManifestType(text);
if (manifest_type) {
if (arguments.length === 1) {
console.log('fetch1 manifest', arguments[0].url, manifest_type);
document.dispatchEvent(new CustomEvent('manifest', {
detail: JSON.stringify({
"url": arguments[0].url,
"type": manifest_type,
})
}));
} else if (arguments.length === 2) {
console.log('fetch2 manifest', arguments[0], manifest_type);
document.dispatchEvent(new CustomEvent('manifest', {
detail: JSON.stringify({
"url": arguments[0],
"type": manifest_type,
})
}));
}
}
resolve(response);
}).catch(() => {
resolve(response);
})
} else {
resolve(response);
}
}).catch(() => {
resolve();
})
})
}

const open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
this._method = method;
return open.apply(this, arguments);
};

const send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(postData) {
this.addEventListener('load', async function() {
if (this._method === "GET") {
let body = void 0;
switch (this.responseType) {
case "":
case "text":
body = this.responseText ?? this.response;
break;
case "json":
// TODO: untested
body = JSON.stringify(this.response);
break;
case "arraybuffer":
// TODO: untested
if (this.response.byteLength) {
const response = new Uint8Array(this.response);
body = uint8ArrayToString(new Uint8Array([...response.slice(0, 2000), ...response.slice(-2000)]));
}
break;
case "document":
// todo
break;
case "blob":
body = await this.response.text();
break;
}
if (body) {
const manifest_type = Evaluator.getManifestType(body);
if (manifest_type) {
console.log('send manifest', this.responseURL, manifest_type);
document.dispatchEvent(new CustomEvent('manifest', {
detail: JSON.stringify({
"url": this.responseURL,
"type": manifest_type,
})
}));
}
}
}
});
return send.apply(this, arguments);
};
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Widevine L3 Decrypter",
"version": "241029.1",
"version": "250709.2",
"version_name": "4316530",
"icons": {
"128": "icon.png"
Expand Down
20 changes: 20 additions & 0 deletions popup/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
<input type="text" id="license" disabled/>
<input type="button" id="licenseButton" value="Select" /><br>

<label for="title">Title</label>
<input type="text" id="title"><br>
<label for="manifest">Manifest</label>
<select id="manifest"></select><br>
<label for="command">Cmd</label>
<textarea id="command" rows="3" cols="50"></textarea><br>

<label for="schemeSelect">Challenge scheme</label>
<select id="schemeSelect">
<option value="Amazon">Amazon</option>
Expand Down Expand Up @@ -57,6 +64,19 @@
<textarea id="result" rows="10" cols="50"></textarea>

</form>
<fieldset id="settings">
<legend>Settings</legend>
<input type="checkbox" id="use-shaka">
<label for="use-shaka"> Use Shaka Packager</label><br>
<input type="checkbox" id="set-filename-from-title">
<label for="set-filename-from-title"> Set Filename from Title</label><br>
<input type="checkbox" id="use-select-video">
<label for="use-select-video"> Select Video: </label>
<input type="text" class="text-box" id="select-video-param"><br>
<input type="checkbox" id="use-select-audio">
<label for="use-select-audio"> Select Audio: </label>
<input type="text" class="text-box" id="select-audio-param">
</fieldset>
</div>
<div id="ckHome" class="hidden">
<h3>Clearkey detected</h3>
Expand Down
66 changes: 65 additions & 1 deletion popup/main.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { SettingsManager } from "./settingManager.js";

let psshs=chrome.extension.getBackgroundPage().psshs;
let requests=chrome.extension.getBackgroundPage().requests;
let pageURL=chrome.extension.getBackgroundPage().pageURL;
let title=chrome.extension.getBackgroundPage().title;
let targetIds=chrome.extension.getBackgroundPage().targetIds;
let clearkey=chrome.extension.getBackgroundPage().clearkey;
let manifests = chrome.extension.getBackgroundPage().manifests;

async function createCommand() {
const header_string = Object.entries(JSON.parse(requests[userInputs['license']]['headers'])).filter(([key, value]) => key != 'Host').map(([key, value]) => `-H "${key}: ${value.replace(/"/g, "'")}"`).join(' ');
return `N_m3u8DL-RE "${manifest_list.value}" ${header_string} ${document.getElementById('result').value.split('\n').filter(key => key != '').map(key => `--key ${key}`).join(' ')} ${await SettingsManager.getUseShakaPackager() ? "--use-shaka-packager " : ""}-M format=mkv${await SettingsManager.getSetFilenameFromTitle() && title ? " --save-name ".concat('"', title, '"') : ""}${await SettingsManager.getUseSelectVideo() ? " --select-video ".concat(await SettingsManager.getSelectVideoParam()) : ""}${await SettingsManager.getUseSelectAudio() ? " --select-audio ".concat(await SettingsManager.getSelectAudioParam()) : ""}`;
}

async function guess(){
//Be patient!
Expand Down Expand Up @@ -35,7 +44,9 @@ async function guess(){

//All Done!
document.body.style.cursor = "auto";
document.getElementById("guess").disabled=false
document.getElementById("guess").disabled = false
command.value = await createCommand();
command.disabled = false;
}

function copyResult(){
Expand All @@ -55,6 +66,7 @@ async function autoSelect(){
userInputs["license"]=0;
document.getElementById("license").value=requests[0]['url'];
document.getElementById('pssh').value=psshs[0];
document.getElementById('title').value = title;

let selectRules = await fetch("/selectRules.conf").then((r)=>r.text());
//Remove blank lines, comment-outs, and trailing spaces at the end of lines
Expand Down Expand Up @@ -83,5 +95,57 @@ if (clearkey) {
document.getElementById('home').style.display = 'grid';
document.getElementById('guess').addEventListener("click", guess);
document.getElementById('result').addEventListener("click", copyResult);
document.getElementById('command').addEventListener("click", copyResult);
autoSelect();
}

document.addEventListener('DOMContentLoaded', async function () {
use_shaka.checked = await SettingsManager.getUseShakaPackager();
set_filename_from_title.checked = await SettingsManager.getSetFilenameFromTitle();
use_select_video.checked = await SettingsManager.getUseSelectVideo();
select_video_param.value = await SettingsManager.getSelectVideoParam();
use_select_audio.checked = await SettingsManager.getUseSelectAudio();
select_audio_param.value = await SettingsManager.getSelectAudioParam();
});

const use_shaka = document.getElementById('use-shaka');
use_shaka.addEventListener('change', async function (){
await SettingsManager.saveUseShakaPackager(use_shaka.checked);
});

const set_filename_from_title = document.getElementById('set-filename-from-title');
set_filename_from_title.addEventListener('change', async function () {
await SettingsManager.saveSetFilenameFromTitle(set_filename_from_title.checked);
});

const use_select_video = document.getElementById('use-select-video');
use_select_video.addEventListener('change', async function (){
await SettingsManager.saveUseSelectVideo(use_select_video.checked);
});

const select_video_param = document.getElementById('select-video-param');
select_video_param.addEventListener('input', async function (event) {
await SettingsManager.saveSelectVideoParam(select_video_param.value);
});

const use_select_audio = document.getElementById('use-select-audio');
use_select_audio.addEventListener('change', async function () {
await SettingsManager.saveUseSelectAudio(use_select_audio.checked);
});

const select_audio_param = document.getElementById('select-audio-param');
select_audio_param.addEventListener('input', async function (event) {
await SettingsManager.saveSelectAudioParam(select_audio_param.value);
});

const command = document.getElementById('command');
command.disabled = true;
const manifest_list = document.getElementById('manifest');
manifest_list.addEventListener('change', async () => {
command.value = await createCommand();
});
manifests.forEach(element => {
const option = new Option(`[${element.type}] ${element.url}`, element.url, element.type.startsWith('DASH'), element.type.startsWith('DASH'));
manifest_list.add(option);
});

Loading