Update inject.js
This commit is contained in:
parent
094f1edac2
commit
b3eec07085
745
inject.js
745
inject.js
@ -1,29 +1,33 @@
|
|||||||
let customBase64 = "PlaceHolder";
|
|
||||||
let psshFound = false;
|
|
||||||
let postRequestFound = false;
|
|
||||||
let firstValidLicenseResponse = false;
|
|
||||||
let firstValidServiceCertificate = false;
|
|
||||||
let remoteCDM = null;
|
|
||||||
let decryptionKeys = null;
|
|
||||||
let messageSuppressed = false;
|
|
||||||
let interceptType = "DISABLED"; // Default to LICENSE, can be changed to 'EME' for EME interception
|
|
||||||
let originalChallenge = null;
|
|
||||||
let widevineDeviceInfo = null;
|
let widevineDeviceInfo = null;
|
||||||
let playreadyDeviceInfo = null;
|
let playreadyDeviceInfo = null;
|
||||||
let drmOveride = "DISABLED"
|
let originalMessage = null;
|
||||||
|
let originalChallenge = null
|
||||||
|
let serviceCertFound = false;
|
||||||
|
let drmType = "NONE";
|
||||||
|
let psshFound = false;
|
||||||
|
let firstValidLicenseChallenge = false;
|
||||||
|
let pssh = null;
|
||||||
|
let drmOverride = "DISABLED";
|
||||||
|
let interceptType = "DISABLED";
|
||||||
|
let remoteCDM = null;
|
||||||
|
|
||||||
|
|
||||||
|
// Post message to content.js to get DRM override
|
||||||
window.postMessage({ type: "__GET_DRM_OVERRIDE__" }, "*");
|
window.postMessage({ type: "__GET_DRM_OVERRIDE__" }, "*");
|
||||||
|
|
||||||
|
// Add listener for DRM override messages
|
||||||
window.addEventListener("message", function(event) {
|
window.addEventListener("message", function(event) {
|
||||||
if (event.source !== window) return;
|
if (event.source !== window) return;
|
||||||
if (event.data.type === "__DRM_OVERRIDE__") {
|
if (event.data.type === "__DRM_OVERRIDE__") {
|
||||||
drmOveride = event.data.drmOverride || "DISABLED";
|
drmOverride = event.data.drmOverride || "DISABLED";
|
||||||
console.log("DRM Override set to:", drmOveride);
|
console.log("DRM Override set to:", drmOverride);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Post message to content.js to get injection type
|
||||||
window.postMessage({ type: "__GET_INJECTION_TYPE__" }, "*");
|
window.postMessage({ type: "__GET_INJECTION_TYPE__" }, "*");
|
||||||
|
|
||||||
|
// Add listener for injection type messages
|
||||||
window.addEventListener("message", function(event) {
|
window.addEventListener("message", function(event) {
|
||||||
if (event.source !== window) return;
|
if (event.source !== window) return;
|
||||||
|
|
||||||
@ -33,25 +37,25 @@ window.addEventListener("message", function(event) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Post message to get CDM devices
|
||||||
window.postMessage({ type: "__GET_CDM_DEVICES__" }, "*");
|
window.postMessage({ type: "__GET_CDM_DEVICES__" }, "*");
|
||||||
|
|
||||||
|
// Add listener for CDM device messages
|
||||||
window.addEventListener("message", function(event) {
|
window.addEventListener("message", function(event) {
|
||||||
if (event.source !== window) return;
|
if (event.source !== window) return;
|
||||||
|
|
||||||
if (event.data.type === "__CDM_DEVICES__") {
|
if (event.data.type === "__CDM_DEVICES__") {
|
||||||
const { widevine_device, playready_device } = event.data;
|
const { widevine_device, playready_device } = event.data;
|
||||||
|
|
||||||
// Now you can use widevine_device and playready_device!
|
|
||||||
console.log("Received device info:", widevine_device, playready_device);
|
console.log("Received device info:", widevine_device, playready_device);
|
||||||
|
|
||||||
// Store them globally
|
|
||||||
widevineDeviceInfo = widevine_device;
|
widevineDeviceInfo = widevine_device;
|
||||||
playreadyDeviceInfo = playready_device;
|
playreadyDeviceInfo = playready_device;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// PlayReady Remote CDM Class
|
||||||
class remotePlayReadyCDM {
|
class remotePlayReadyCDM {
|
||||||
constructor(security_level, host, secret, device_name) {
|
constructor(security_level, host, secret, device_name) {
|
||||||
this.security_level = security_level;
|
this.security_level = security_level;
|
||||||
@ -60,168 +64,120 @@ class remotePlayReadyCDM {
|
|||||||
this.device_name = device_name;
|
this.device_name = device_name;
|
||||||
this.session_id = null;
|
this.session_id = null;
|
||||||
this.challenge = null;
|
this.challenge = null;
|
||||||
|
this.keys = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open PlayReady session
|
||||||
async openSession() {
|
async openSession() {
|
||||||
const url = `${this.host}/remotecdm/playready/${this.device_name}/open`;
|
const url = `${this.host}/remotecdm/playready/${this.device_name}/open`;
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
const jsonData = await response.json();
|
const jsonData = await response.json();
|
||||||
|
|
||||||
if (response.ok && jsonData.data?.session_id) {
|
if (response.ok && jsonData.data?.session_id) {
|
||||||
this.session_id = jsonData.data.session_id;
|
this.session_id = jsonData.data.session_id;
|
||||||
return { success: true, session_id: this.session_id };
|
console.log("PlayReady session opened:", this.session_id);
|
||||||
} else {
|
} else {
|
||||||
return { success: false, error: jsonData.message || 'Unknown error occurred.' };
|
console.error("Failed to open PlayReady session:", jsonData.message);
|
||||||
}
|
throw new Error("Failed to open PlayReady session");
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get PlayReady challenge
|
||||||
async getChallenge(init_data) {
|
async getChallenge(init_data) {
|
||||||
const url = `${this.host}/remotecdm/playready/${this.device_name}/get_license_challenge`;
|
const url = `${this.host}/remotecdm/playready/${this.device_name}/get_license_challenge`;
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
session_id: this.session_id,
|
session_id: this.session_id,
|
||||||
init_data: init_data,
|
init_data: init_data
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonData = await response.json();
|
const jsonData = await response.json();
|
||||||
|
|
||||||
if (response.ok && jsonData.data?.challenge) {
|
if (response.ok && jsonData.data?.challenge) {
|
||||||
return {
|
this.challenge = jsonData.data.challenge;
|
||||||
success: true,
|
console.log("PlayReady challenge received:", this.challenge);
|
||||||
challenge: jsonData.data.challenge
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
console.error("Failed to get PlayReady challenge:", jsonData.message);
|
||||||
success: false,
|
throw new Error("Failed to get PlayReady challenge");
|
||||||
error: jsonData.message || 'Failed to retrieve license challenge.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseLicense(license_message) {
|
// Parse PlayReady license response
|
||||||
|
async parseLicenseResponse(license_message) {
|
||||||
const url = `${this.host}/remotecdm/playready/${this.device_name}/parse_license`;
|
const url = `${this.host}/remotecdm/playready/${this.device_name}/parse_license`;
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
session_id: this.session_id,
|
session_id: this.session_id,
|
||||||
license_message: license_message
|
license_message: license_message
|
||||||
};
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonData = await response.json();
|
const jsonData = await response.json();
|
||||||
|
if (response.ok && jsonData.message === "Successfully parsed and loaded the Keys from the License message")
|
||||||
if (response.ok && jsonData.message === "Successfully parsed and loaded the Keys from the License message") {
|
{
|
||||||
return {
|
console.log("PlayReady license response parsed successfully");
|
||||||
success: true,
|
return true;
|
||||||
message: jsonData.message
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
console.error("Failed to parse PlayReady license response:", jsonData.message);
|
||||||
success: false,
|
throw new Error("Failed to parse PlayReady license response");
|
||||||
error: jsonData.message || 'Failed to parse license.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async closeSession() {
|
// Get PlayReady keys
|
||||||
const url = `${this.host}/remotecdm/playready/${this.device_name}/close/${this.session_id}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
|
|
||||||
const jsonData = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
return { success: true, message: jsonData.message };
|
|
||||||
} else {
|
|
||||||
return { success: false, error: jsonData.message || 'Failed to close session.' };
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async getKeys() {
|
async getKeys() {
|
||||||
const url = `${this.host}/remotecdm/playready/${this.device_name}/get_keys`;
|
const url = `${this.host}/remotecdm/playready/${this.device_name}/get_keys`;
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
session_id: this.session_id
|
session_id: this.session_id
|
||||||
};
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
const jsonData = await response.json()
|
||||||
const jsonData = await response.json();
|
|
||||||
|
|
||||||
if (response.ok && jsonData.data?.keys) {
|
if (response.ok && jsonData.data?.keys) {
|
||||||
decryptionKeys = jsonData.data.keys;
|
this.keys = jsonData.data.keys;
|
||||||
|
console.log("PlayReady keys received:", this.keys);
|
||||||
// Automatically close the session after key retrieval
|
|
||||||
await this.closeSession();
|
|
||||||
|
|
||||||
return { success: true, keys: decryptionKeys };
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
console.error("Failed to get PlayReady keys:", jsonData.message);
|
||||||
success: false,
|
throw new Error("Failed to get PlayReady keys");
|
||||||
error: jsonData.message || 'Failed to retrieve decryption keys.'
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
// Close PlayReady session
|
||||||
return { success: false, error: error.message };
|
async closeSession () {
|
||||||
|
const url = `${this.host}/remotecdm/playready/${this.device_name}/close/${this.session_id}`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const jsonData = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
console.log("PlayReady session closed successfully");
|
||||||
|
} else {
|
||||||
|
console.error("Failed to close PlayReady session:", jsonData.message);
|
||||||
|
throw new Error("Failed to close PlayReady session");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Widevine Remote CDM Class
|
||||||
class remoteWidevineCDM {
|
class remoteWidevineCDM {
|
||||||
constructor(device_type, system_id, security_level, host, secret, device_name) {
|
constructor(device_type, system_id, security_level, host, secret, device_name) {
|
||||||
this.device_type = device_type;
|
this.device_type = device_type;
|
||||||
@ -232,201 +188,143 @@ class remoteWidevineCDM {
|
|||||||
this.device_name = device_name;
|
this.device_name = device_name;
|
||||||
this.session_id = null;
|
this.session_id = null;
|
||||||
this.challenge = null;
|
this.challenge = null;
|
||||||
|
this.keys = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async openSession() {
|
// Open Widevine session
|
||||||
|
async openSession () {
|
||||||
const url = `${this.host}/remotecdm/widevine/${this.device_name}/open`;
|
const url = `${this.host}/remotecdm/widevine/${this.device_name}/open`;
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonData = await response.json();
|
const jsonData = await response.json();
|
||||||
|
if (response.ok && jsonData.data?.session_id) {
|
||||||
if (response.ok && jsonData.status === 200 && jsonData.data?.session_id) {
|
|
||||||
this.session_id = jsonData.data.session_id;
|
this.session_id = jsonData.data.session_id;
|
||||||
return { success: true, session_id: this.session_id };
|
console.log("Widevine session opened:", this.session_id);
|
||||||
} else {
|
} else {
|
||||||
return { success: false, error: jsonData.message || 'Unknown error occurred.' };
|
console.error("Failed to open Widevine session:", jsonData.message);
|
||||||
}
|
throw new Error("Failed to open Widevine session");
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Widevine service certificate
|
||||||
async setServiceCertificate(certificate) {
|
async setServiceCertificate(certificate) {
|
||||||
const url = `${this.host}/remotecdm/widevine/${this.device_name}/set_service_certificate`;
|
const url = `${this.host}/remotecdm/widevine/${this.device_name}/set_service_certificate`;
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
session_id: this.session_id,
|
session_id: this.session_id,
|
||||||
certificate: certificate ?? null
|
certificate: certificate ?? null
|
||||||
};
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonData = await response.json();
|
const jsonData = await response.json();
|
||||||
|
|
||||||
if (response.ok && jsonData.status === 200) {
|
if (response.ok && jsonData.status === 200) {
|
||||||
return { success: true };
|
console.log("Service certificate set successfully");
|
||||||
} else {
|
} else {
|
||||||
return { success: false, error: jsonData.message || 'Failed to set service certificate.' };
|
console.error("Failed to set service certificate:", jsonData.message);
|
||||||
}
|
throw new Error("Failed to set service certificate");
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChallenge(init_data, license_type = 'STREAMING', privacy_mode = false) {
|
// Get Widevine challenge
|
||||||
|
async getChallenge(init_data, license_type = 'STREAMING') {
|
||||||
const url = `${this.host}/remotecdm/widevine/${this.device_name}/get_license_challenge/${license_type}`;
|
const url = `${this.host}/remotecdm/widevine/${this.device_name}/get_license_challenge/${license_type}`;
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
session_id: this.session_id,
|
session_id: this.session_id,
|
||||||
init_data: init_data,
|
init_data: init_data,
|
||||||
privacy_mode: privacy_mode
|
privacy_mode: serviceCertFound
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonData = await response.json();
|
const jsonData = await response.json();
|
||||||
|
if (response.ok && jsonData.data?.challenge_b64) {
|
||||||
if (response.ok && jsonData.status === 200 && jsonData.data?.challenge_b64) {
|
this.challenge = jsonData.data.challenge_b64;
|
||||||
return {
|
console.log("Widevine challenge received:", this.challenge);
|
||||||
success: true,
|
|
||||||
challenge: jsonData.data.challenge_b64
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
console.error("Failed to get Widevine challenge:", jsonData.message);
|
||||||
success: false,
|
throw new Error("Failed to get Widevine challenge");
|
||||||
error: jsonData.message || 'Failed to retrieve license challenge.'
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
// Parse Widevine license response
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async parseLicense(license_message) {
|
async parseLicense(license_message) {
|
||||||
const url = `${this.host}/remotecdm/widevine/${this.device_name}/parse_license`;
|
const url = `${this.host}/remotecdm/widevine/${this.device_name}/parse_license`;
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
session_id: this.session_id,
|
session_id: this.session_id,
|
||||||
license_message: license_message
|
license_message: license_message
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonData = await response.json();
|
const jsonData = await response.json();
|
||||||
|
|
||||||
if (response.ok && jsonData.status === 200) {
|
if (response.ok && jsonData.status === 200) {
|
||||||
return {
|
console.log("Widevine license response parsed successfully");
|
||||||
success: true,
|
return true;
|
||||||
message: jsonData.message
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
console.error("Failed to parse Widevine license response:", jsonData.message);
|
||||||
success: false,
|
throw new Error("Failed to parse Widevine license response");
|
||||||
error: jsonData.message || 'Failed to parse license.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async closeSession() {
|
// Get Widevine keys
|
||||||
const url = `${this.host}/remotecdm/widevine/${this.device_name}/close/${this.session_id}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
|
|
||||||
const jsonData = await response.json();
|
|
||||||
|
|
||||||
if (response.ok && jsonData.status === 200) {
|
|
||||||
return { success: true, message: jsonData.message };
|
|
||||||
} else {
|
|
||||||
return { success: false, error: jsonData.message || 'Failed to close session.' };
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async getKeys() {
|
async getKeys() {
|
||||||
const url = `${this.host}/remotecdm/widevine/${this.device_name}/get_keys/ALL`;
|
const url = `${this.host}/remotecdm/widevine/${this.device_name}/get_keys/ALL`;
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
session_id: this.session_id
|
session_id: this.session_id
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonData = await response.json();
|
const jsonData = await response.json();
|
||||||
|
if (response.ok && jsonData.data?.keys) {
|
||||||
if (response.ok && jsonData.status === 200 && jsonData.data?.keys) {
|
this.keys = jsonData.data.keys;
|
||||||
decryptionKeys = jsonData.data.keys;
|
console.log("Widevine keys received:", this.keys);
|
||||||
|
|
||||||
// Automatically close the session after key retrieval
|
|
||||||
await this.closeSession();
|
|
||||||
|
|
||||||
return { success: true, keys: decryptionKeys };
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
console.error("Failed to get Widevine keys:", jsonData.message);
|
||||||
success: false,
|
throw new Error("Failed to get Widevine keys");
|
||||||
error: jsonData.message || 'Failed to retrieve decryption keys.'
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
// Close Widevine session
|
||||||
return { success: false, error: error.message };
|
async closeSession() {
|
||||||
|
const url = `${this.host}/remotecdm/widevine/${this.device_name}/close/${this.session_id}`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const jsonData = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
console.log("Widevine session closed successfully");
|
||||||
|
} else {
|
||||||
|
console.error("Failed to close Widevine session:", jsonData.message);
|
||||||
|
throw new Error("Failed to close Widevine session");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
|
||||||
// --- Utility functions ---
|
|
||||||
const hexStrToU8 = hexString =>
|
const hexStrToU8 = hexString =>
|
||||||
Uint8Array.from(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
Uint8Array.from(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
||||||
|
|
||||||
@ -476,7 +374,6 @@ const isJson = (str) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Widevine-style PSSH extractor ---
|
|
||||||
function getWidevinePssh(buffer) {
|
function getWidevinePssh(buffer) {
|
||||||
const hex = u8ToHexStr(new Uint8Array(buffer));
|
const hex = u8ToHexStr(new Uint8Array(buffer));
|
||||||
const match = hex.match(/000000(..)?70737368.*/);
|
const match = hex.match(/000000(..)?70737368.*/);
|
||||||
@ -487,7 +384,6 @@ function getWidevinePssh(buffer) {
|
|||||||
return window.btoa(String.fromCharCode(...bytes));
|
return window.btoa(String.fromCharCode(...bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- PlayReady-style PSSH extractor ---
|
|
||||||
function getPlayReadyPssh(buffer) {
|
function getPlayReadyPssh(buffer) {
|
||||||
const u8 = new Uint8Array(buffer);
|
const u8 = new Uint8Array(buffer);
|
||||||
const systemId = "9a04f07998404286ab92e65be0885f95";
|
const systemId = "9a04f07998404286ab92e65be0885f95";
|
||||||
@ -503,7 +399,6 @@ function getPlayReadyPssh(buffer) {
|
|||||||
return window.btoa(String.fromCharCode(...psshBytes));
|
return window.btoa(String.fromCharCode(...psshBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Clearkey Support ---
|
|
||||||
function getClearkey(response) {
|
function getClearkey(response) {
|
||||||
let obj = JSON.parse((new TextDecoder("utf-8")).decode(response));
|
let obj = JSON.parse((new TextDecoder("utf-8")).decode(response));
|
||||||
return obj["keys"].map(o => ({
|
return obj["keys"].map(o => ({
|
||||||
@ -512,9 +407,8 @@ function getClearkey(response) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Convert Base64 to Uint8Array ---
|
|
||||||
function base64ToUint8Array(base64) {
|
function base64ToUint8Array(base64) {
|
||||||
const binaryStr = atob(base64); // Decode base64 to binary string
|
const binaryStr = atob(base64);
|
||||||
const len = binaryStr.length;
|
const len = binaryStr.length;
|
||||||
const bytes = new Uint8Array(len);
|
const bytes = new Uint8Array(len);
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
@ -527,392 +421,9 @@ function arrayBufferToBase64(uint8array) {
|
|||||||
let binary = '';
|
let binary = '';
|
||||||
const len = uint8array.length;
|
const len = uint8array.length;
|
||||||
|
|
||||||
// Convert each byte to a character
|
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
binary += String.fromCharCode(uint8array[i]);
|
binary += String.fromCharCode(uint8array[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the binary string to Base64
|
|
||||||
return window.btoa(binary);
|
return window.btoa(binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Intercepting EME Calls ---
|
|
||||||
const originalGenerateRequest = MediaKeySession.prototype.generateRequest;
|
|
||||||
|
|
||||||
MediaKeySession.prototype.generateRequest = async function(initDataType, initData) {
|
|
||||||
console.log(initData);
|
|
||||||
const session = this;
|
|
||||||
|
|
||||||
let playReadyAttempted = false;
|
|
||||||
let playReadySucceeded = false;
|
|
||||||
let playReadyPssh = null;
|
|
||||||
let widevinePssh = null;
|
|
||||||
|
|
||||||
if (!psshFound && !messageSuppressed && (interceptType === 'EME' || interceptType === 'LICENSE')) {
|
|
||||||
// === Try PlayReady First ===
|
|
||||||
playReadyPssh = getPlayReadyPssh(initData);
|
|
||||||
playReadyAttempted = !!playReadyPssh;
|
|
||||||
|
|
||||||
if (playReadyPssh && drmOveride !== "WIDEVINE") {
|
|
||||||
console.log("[PlayReady PSSH] Found:", playReadyPssh);
|
|
||||||
const drmType = {
|
|
||||||
type: "__DRM_TYPE__",
|
|
||||||
data: 'PlayReady'
|
|
||||||
};
|
|
||||||
window.postMessage(drmType, "*");
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
security_level, host, secret, device_name
|
|
||||||
} = playreadyDeviceInfo;
|
|
||||||
remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
|
|
||||||
const sessionResult = await remoteCDM.openSession();
|
|
||||||
if (sessionResult.success) {
|
|
||||||
console.log("PlayReady session opened:", sessionResult.session_id);
|
|
||||||
const challengeResult = await remoteCDM.getChallenge(playReadyPssh);
|
|
||||||
if (challengeResult.success) {
|
|
||||||
customBase64 = btoa(challengeResult.challenge);
|
|
||||||
playReadySucceeded = true;
|
|
||||||
psshFound = true;
|
|
||||||
window.postMessage({ type: "__PSSH_DATA__", data: playReadyPssh }, "*");
|
|
||||||
} else {
|
|
||||||
console.warn("PlayReady challenge failed:", challengeResult.error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn("PlayReady session failed:", sessionResult.error);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("PlayReady error:", err.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("[PlayReady PSSH] Not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Fallback to Widevine ===
|
|
||||||
if (!playReadySucceeded) {
|
|
||||||
widevinePssh = getWidevinePssh(initData);
|
|
||||||
if (widevinePssh && drmOveride !== "PLAYREADY") {
|
|
||||||
console.log("[Widevine PSSH] Found:", widevinePssh);
|
|
||||||
const drmType = {
|
|
||||||
type: "__DRM_TYPE__",
|
|
||||||
data: 'Widevine'
|
|
||||||
};
|
|
||||||
window.postMessage(drmType, "*");
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
device_type, system_id, security_level, host, secret, device_name
|
|
||||||
} = widevineDeviceInfo;
|
|
||||||
remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
|
|
||||||
const sessionResult = await remoteCDM.openSession();
|
|
||||||
if (sessionResult.success) {
|
|
||||||
console.log("Widevine session opened:", sessionResult.session_id);
|
|
||||||
const challengeResult = await remoteCDM.getChallenge(widevinePssh);
|
|
||||||
if (challengeResult.success) {
|
|
||||||
customBase64 = challengeResult.challenge;
|
|
||||||
psshFound = true;
|
|
||||||
window.postMessage({ type: "__PSSH_DATA__", data: widevinePssh }, "*");
|
|
||||||
} else {
|
|
||||||
console.warn("Widevine challenge failed:", challengeResult.error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn("Widevine session failed:", sessionResult.error);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Widevine error:", err.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("[Widevine PSSH] Not found.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Intercept License or EME Messages ===
|
|
||||||
if (!messageSuppressed && interceptType === 'EME') {
|
|
||||||
session.addEventListener("message", function originalMessageInterceptor(event) {
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
console.log("[Intercepted EME Message] Injecting custom message.");
|
|
||||||
console.log(event.data);
|
|
||||||
|
|
||||||
const uint8 = base64ToUint8Array(customBase64);
|
|
||||||
const arrayBuffer = uint8.buffer;
|
|
||||||
|
|
||||||
const syntheticEvent = new MessageEvent("message", {
|
|
||||||
data: event.data,
|
|
||||||
origin: event.origin,
|
|
||||||
lastEventId: event.lastEventId,
|
|
||||||
source: event.source,
|
|
||||||
ports: event.ports
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(syntheticEvent, "message", {
|
|
||||||
get: () => arrayBuffer
|
|
||||||
});
|
|
||||||
console.log(syntheticEvent);
|
|
||||||
setTimeout(() => session.dispatchEvent(syntheticEvent), 0);
|
|
||||||
}, { once: true });
|
|
||||||
|
|
||||||
messageSuppressed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!messageSuppressed && interceptType === 'LICENSE') {
|
|
||||||
session.addEventListener("message", function originalMessageInterceptor(event) {
|
|
||||||
if (playReadyAttempted && playReadySucceeded) {
|
|
||||||
const buffer = event.message;
|
|
||||||
const decoder = new TextDecoder('utf-16');
|
|
||||||
const decodedText = decoder.decode(buffer);
|
|
||||||
const match = decodedText.match(/<Challenge encoding="base64encoded">([^<]+)<\/Challenge>/);
|
|
||||||
if (match) {
|
|
||||||
originalChallenge = match[1];
|
|
||||||
console.log("[PlayReady Challenge Extracted]");
|
|
||||||
messageSuppressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!playReadySucceeded && widevinePssh && psshFound) {
|
|
||||||
const uint8Array = new Uint8Array(event.message);
|
|
||||||
const b64array = arrayBufferToBase64(uint8Array);
|
|
||||||
if (b64array !== "CAQ=") {
|
|
||||||
originalChallenge = b64array;
|
|
||||||
console.log("[Widevine Challenge Extracted]");
|
|
||||||
messageSuppressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, { once: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proceed with original generateRequest
|
|
||||||
return originalGenerateRequest.call(session, initDataType, initData);
|
|
||||||
};
|
|
||||||
|
|
||||||
// license message handler
|
|
||||||
const originalUpdate = MediaKeySession.prototype.update;
|
|
||||||
|
|
||||||
MediaKeySession.prototype.update = function(response) {
|
|
||||||
const uint8 = response instanceof Uint8Array ? response : new Uint8Array(response);
|
|
||||||
const base64Response = window.btoa(String.fromCharCode(...uint8));
|
|
||||||
|
|
||||||
// Handle Service Certificate
|
|
||||||
if (base64Response.startsWith("CAUS") && !firstValidServiceCertificate) {
|
|
||||||
const base64ServiceCertificateData = {
|
|
||||||
type: "__CERTIFICATE_DATA__",
|
|
||||||
data: base64Response
|
|
||||||
};
|
|
||||||
window.postMessage(base64ServiceCertificateData, "*");
|
|
||||||
firstValidServiceCertificate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle License Data
|
|
||||||
if (!base64Response.startsWith("CAUS") && (interceptType === 'EME' || interceptType === 'LICENSE')) {
|
|
||||||
|
|
||||||
// 🔁 Call parseLicense, then getKeys from global remoteCDM
|
|
||||||
if (remoteCDM !== null && remoteCDM.session_id) {
|
|
||||||
remoteCDM.parseLicense(base64Response)
|
|
||||||
.then(result => {
|
|
||||||
if (result.success) {
|
|
||||||
console.log("[Base64 Response]", base64Response);
|
|
||||||
const base64LicenseData = {
|
|
||||||
type: "__LICENSE_DATA__",
|
|
||||||
data: base64Response
|
|
||||||
};
|
|
||||||
window.postMessage(base64LicenseData, "*");
|
|
||||||
console.log("[remoteCDM] License parsed successfully");
|
|
||||||
|
|
||||||
// 🚀 Now call getKeys after parsing
|
|
||||||
return remoteCDM.getKeys();
|
|
||||||
} else {
|
|
||||||
console.warn("[remoteCDM] License parse failed:", result.error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(keysResult => {
|
|
||||||
if (keysResult?.success) {
|
|
||||||
const keysData = {
|
|
||||||
type: "__KEYS_DATA__",
|
|
||||||
data: keysResult.keys
|
|
||||||
};
|
|
||||||
window.postMessage(keysData, "*");
|
|
||||||
console.log("[remoteCDM] Decryption keys retrieved:", keysResult.keys);
|
|
||||||
} else if (keysResult) {
|
|
||||||
console.warn("[remoteCDM] Failed to retrieve keys:", keysResult.error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error("[remoteCDM] Unexpected error in license flow:", err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.warn("[remoteCDM] Cannot parse license: remoteCDM not initialized or session_id missing.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatePromise = originalUpdate.call(this, response);
|
|
||||||
|
|
||||||
if (!psshFound) {
|
|
||||||
updatePromise
|
|
||||||
.then(() => {
|
|
||||||
let clearKeys = getClearkey(response);
|
|
||||||
if (clearKeys && clearKeys.length > 0) {
|
|
||||||
console.log("[CLEARKEY] ", clearKeys);
|
|
||||||
const drmType = {
|
|
||||||
type: "__DRM_TYPE__",
|
|
||||||
data: 'ClearKey'
|
|
||||||
};
|
|
||||||
window.postMessage(drmType, "*");
|
|
||||||
const keysData = {
|
|
||||||
type: "__KEYS_DATA__",
|
|
||||||
data: clearKeys
|
|
||||||
};
|
|
||||||
window.postMessage(keysData, "*");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.log("[CLEARKEY] Not found");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatePromise;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Request Interception ---
|
|
||||||
(function interceptRequests() {
|
|
||||||
const sendToBackground = (data) => {
|
|
||||||
window.postMessage({ type: "__INTERCEPTED_POST__", data }, "*");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Intercept fetch
|
|
||||||
const originalFetch = window.fetch;
|
|
||||||
|
|
||||||
window.fetch = async function(input, init = {}) {
|
|
||||||
const method = (init.method || 'GET').toUpperCase();
|
|
||||||
|
|
||||||
if (method === "POST") {
|
|
||||||
const url = typeof input === "string" ? input : input.url;
|
|
||||||
let body = init.body;
|
|
||||||
|
|
||||||
// If the body is FormData, convert it to an object (or JSON)
|
|
||||||
if (body instanceof FormData) {
|
|
||||||
const formData = {};
|
|
||||||
body.forEach((value, key) => {
|
|
||||||
formData[key] = value;
|
|
||||||
});
|
|
||||||
body = JSON.stringify(formData); // Convert formData to JSON string
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = {};
|
|
||||||
if (init.headers instanceof Headers) {
|
|
||||||
init.headers.forEach((v, k) => { headers[k] = v; });
|
|
||||||
} else {
|
|
||||||
Object.assign(headers, init.headers || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let modifiedBody = body; // Keep a reference to the original body
|
|
||||||
|
|
||||||
// Handle body based on its type
|
|
||||||
if (typeof body === 'string') {
|
|
||||||
if (isJson(body)) {
|
|
||||||
const parsed = JSON.parse(body);
|
|
||||||
if (jsonContainsValue(parsed, customBase64)) {
|
|
||||||
sendToBackground({ url, method, headers, body });
|
|
||||||
}
|
|
||||||
if (jsonContainsValue(parsed, originalChallenge)) {
|
|
||||||
newJSONBody = jsonReplaceValue(parsed, originalChallenge, customBase64);
|
|
||||||
modifiedBody = JSON.stringify(newJSONBody)
|
|
||||||
sendToBackground({ url, method, headers, modifiedBody });
|
|
||||||
}
|
|
||||||
} else if (body === customBase64) {
|
|
||||||
sendToBackground({ url, method, headers, body });
|
|
||||||
} else if (btoa(body) == originalChallenge) {
|
|
||||||
modifiedBody = atob(customBase64);
|
|
||||||
sendToBackground({ url, method, headers, modifiedBody });
|
|
||||||
}
|
|
||||||
}else if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
|
|
||||||
const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
|
|
||||||
const base64Body = window.btoa(String.fromCharCode(...buffer));
|
|
||||||
if (base64Body === customBase64) {
|
|
||||||
sendToBackground({ url, method, headers, body: base64Body });
|
|
||||||
}
|
|
||||||
if (base64Body === originalChallenge) {
|
|
||||||
modifiedBody = base64ToUint8Array(customBase64); // Modify the body
|
|
||||||
sendToBackground({ url, method, headers, body: modifiedBody });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the modified body is used and passed to the original fetch call
|
|
||||||
init.body = modifiedBody;
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Error handling fetch body:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the original fetch method with the potentially modified body
|
|
||||||
return originalFetch(input, init);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Intercept XMLHttpRequest
|
|
||||||
const originalOpen = XMLHttpRequest.prototype.open;
|
|
||||||
const originalSend = XMLHttpRequest.prototype.send;
|
|
||||||
|
|
||||||
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
|
|
||||||
this._method = method;
|
|
||||||
this._url = url;
|
|
||||||
return originalOpen.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
XMLHttpRequest.prototype.send = function(body) {
|
|
||||||
if (this._method?.toUpperCase() === "POST") {
|
|
||||||
const xhr = this;
|
|
||||||
const headers = {};
|
|
||||||
const originalSetRequestHeader = xhr.setRequestHeader;
|
|
||||||
|
|
||||||
xhr.setRequestHeader = function(header, value) {
|
|
||||||
headers[header] = value;
|
|
||||||
return originalSetRequestHeader.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
try {
|
|
||||||
let modifiedBody = body; // Start with the original body
|
|
||||||
|
|
||||||
// Check if the body is a string and can be parsed as JSON
|
|
||||||
if (typeof body === 'string') {
|
|
||||||
if (isJson(body)) {
|
|
||||||
const parsed = JSON.parse(body);
|
|
||||||
if (jsonContainsValue(parsed, customBase64)) {
|
|
||||||
sendToBackground({ url: xhr._url, method: xhr._method, headers, body });
|
|
||||||
}
|
|
||||||
if (jsonContainsValue(parsed, originalChallenge)) {
|
|
||||||
newJSONBody = jsonReplaceValue(parsed, originalChallenge, customBase64);
|
|
||||||
modifiedBody = JSON.stringify(newJSONBody);
|
|
||||||
sendToBackground({ url: xhr._url, method: xhr._method, headers, modifiedBody });
|
|
||||||
}
|
|
||||||
} else if (body === originalChallenge) {
|
|
||||||
modifiedBody = customBase64
|
|
||||||
sendToBackground({ url: xhr._url, method: xhr._method, headers, body });
|
|
||||||
} else if (btoa(body) == originalChallenge) {
|
|
||||||
modifiedBody = atob(customBase64);
|
|
||||||
sendToBackground({ url: xhr._url, method: xhr._method, headers, body });
|
|
||||||
}
|
|
||||||
} else if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
|
|
||||||
const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
|
|
||||||
const base64Body = window.btoa(String.fromCharCode(...buffer));
|
|
||||||
if (base64Body === customBase64) {
|
|
||||||
sendToBackground({ url: xhr._url, method: xhr._method, headers, body: base64Body });
|
|
||||||
}
|
|
||||||
if (base64Body === originalChallenge) {
|
|
||||||
modifiedBody = base64ToUint8Array(customBase64); // Modify the body
|
|
||||||
sendToBackground({ url: xhr._url, method: xhr._method, headers, body: modifiedBody });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure original send is called only once with the potentially modified body
|
|
||||||
originalSend.apply(this, [modifiedBody]);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Error handling XHR body:", e);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
} else {
|
|
||||||
// Call the original send for non-POST requests
|
|
||||||
return originalSend.apply(this, arguments);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user