crunchy hotfix

This commit is contained in:
stratumadev 2025-05-06 21:35:47 +02:00
parent a0a0bf827a
commit 41a83734b0
4 changed files with 4763 additions and 3830 deletions

View file

@ -382,7 +382,6 @@ export default class Crunchy implements ServiceClass {
const authData = new URLSearchParams({
'refresh_token': this.token.refresh_token,
'grant_type': 'refresh_token',
//'grant_type': 'etp_rt_cookie',
'scope': 'offline_access',
'device_id': uuid,
'device_name': 'iPhone',
@ -1587,10 +1586,10 @@ export default class Crunchy implements ServiceClass {
// Delete the stream if it's not needed
if (options.novids && options.noaudio) {
if (playStream) {
await this.refreshToken(true, true);
await this.req.getData(`https://cr-play-service.prd.crunchyrollsvc.com/v1/token/${currentVersion ? currentVersion.guid : currentMediaId}/${playStream.token}`, {...{method: 'DELETE'}, ...AuthHeaders});
}
// if (playStream) {
// await this.refreshToken(true, true);
// await this.req.getData(`https://cr-play-service.prd.crunchyrollsvc.com/v1/token/${currentVersion ? currentVersion.guid : currentMediaId}/${playStream.token}`, {...{method: 'DELETE'}, ...AuthHeaders});
// }
}
if(!dlFailed && curStream !== undefined && !(options.novids && options.noaudio)){
@ -1602,10 +1601,10 @@ export default class Crunchy implements ServiceClass {
const streamPlaylistBody = await streamPlaylistsReq.res.text();
if (streamPlaylistBody.match('MPD')) {
//We have the stream, so go ahead and delete the active stream
if (playStream) {
await this.refreshToken(true, true);
await this.req.getData(`https://cr-play-service.prd.crunchyrollsvc.com/v1/token/${currentVersion ? currentVersion.guid : currentMediaId}/${playStream.token}`, {...{method: 'DELETE'}, ...AuthHeaders});
}
// if (playStream) {
// await this.refreshToken(true, true);
// await this.req.getData(`https://cr-play-service.prd.crunchyrollsvc.com/v1/token/${currentVersion ? currentVersion.guid : currentMediaId}/${playStream.token}`, {...{method: 'DELETE'}, ...AuthHeaders});
// }
//Parse MPD Playlists
const streamPlaylists = await parse(streamPlaylistBody, langsData.findLang(langsData.fixLanguageTag(pbData.meta.audio_locale as string) || ''), curStream.url.match(/.*\.urlset\//)[0]);
@ -1776,44 +1775,49 @@ export default class Crunchy implements ServiceClass {
//Handle Decryption if needed
if ((chosenVideoSegments.pssh_wvd ||chosenVideoSegments.pssh_prd || chosenAudioSegments.pssh_wvd || chosenAudioSegments.pssh_prd) && (videoDownloaded || audioDownloaded)) {
const assetIdRegex = chosenVideoSegments.segments[0].uri.match(/\/assets\/(?:p\/)?([^_,]+)/);
const assetId = assetIdRegex ? assetIdRegex[1] : null;
const sessionId = new Date().getUTCMilliseconds().toString().padStart(3, '0') + process.hrtime.bigint().toString().slice(0, 13);
// const assetIdRegex = chosenVideoSegments.segments[0].uri.match(/\/assets\/(?:p\/)?([^_,]+)/);
// const assetId = assetIdRegex ? assetIdRegex[1] : null;
// const sessionId = new Date().getUTCMilliseconds().toString().padStart(3, '0') + process.hrtime.bigint().toString().slice(0, 13);
console.info('Decryption Needed, attempting to decrypt');
const decReq = await this.req.getData(`${api.drm}`, {
'method': 'POST',
'body': JSON.stringify({
'accounting_id': 'crunchyroll',
'asset_id': assetId,
'session_id': sessionId,
'user_id': this.token.account_id
}),
headers: {
'User-Agent': api.defaultUserAgent
}
});
if(!decReq.ok || !decReq.res){
console.error('Request to DRM Authentication failed:', decReq.error?.res.status, decReq.error?.message);
return undefined;
}
const authData = await decReq.res.json() as {'custom_data': string, 'token': string};
// const decReq = await this.req.getData(`${api.drm}`, {
// 'method': 'POST',
// 'body': JSON.stringify({
// 'accounting_id': 'crunchyroll',
// 'asset_id': assetId,
// 'session_id': sessionId,
// 'user_id': this.token.account_id
// }),
// headers: {
// 'User-Agent': api.defaultUserAgent
// }
// });
// if(!decReq.ok || !decReq.res){
// console.error('Request to DRM Authentication failed:', decReq.error?.res.status, decReq.error?.message);
// return undefined;
// }
// const authData = await decReq.res.json() as {'custom_data': string, 'token': string};
let encryptionKeys;
if (cdm === 'widevine') {
encryptionKeys = await getKeysWVD(chosenVideoSegments.pssh_wvd, 'https://lic.drmtoday.com/license-proxy-widevine/cenc/', {
'dt-custom-data': authData.custom_data,
'x-dt-auth-token': authData.token
encryptionKeys = await getKeysWVD(chosenVideoSegments.pssh_wvd, api.drm_widevine, {
Authorization: `Bearer ${this.token.access_token}`,
'User-Agent': api.defaultUserAgent,
Pragma: 'no-cache',
'Cache-Control': 'no-cache',
'content-type': 'application/octet-stream',
'x-cr-content-id': currentVersion!.guid,
'x-cr-video-token': playStream!.token
});
}
if (cdm === 'playready') {
encryptionKeys = await getKeysPRD(chosenVideoSegments.pssh_prd, 'https://lic.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx', {
'dt-custom-data': authData.custom_data,
'x-dt-auth-token': authData.token
});
}
// if (cdm === 'playready') {
// encryptionKeys = await getKeysPRD(chosenVideoSegments.pssh_prd, 'https://lic.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx', {
// 'dt-custom-data': authData.custom_data,
// 'x-dt-auth-token': authData.token
// });
// }
if (!encryptionKeys || encryptionKeys.length == 0) {
console.error('Failed to get encryption keys');

View file

@ -99,12 +99,17 @@ export async function getKeysWVD(
//Generate license
let response;
try {
response = await got(licenseServer, {
console.log(licenseServer)
console.log({
method: 'POST',
body: session.createLicenseRequest(),
headers: authData,
responseType: 'text',
headers: authData
} as any)
try {
response = await fetch(licenseServer, {
method: 'POST',
body: session.createLicenseRequest(),
headers: authData
});
} catch (_error) {
const error = _error as {
@ -142,19 +147,21 @@ export async function getKeysWVD(
return [];
}
if (response.statusCode === 200) {
if (response.status === 200) {
//Parse License and return keys
const buffer = await response.arrayBuffer();
const text = new TextDecoder().decode(buffer);
try {
const json = JSON.parse(response.body);
const json = JSON.parse(text);
return session.parseLicense(Buffer.from(json['license'], 'base64'));
} catch {
return session.parseLicense(response.rawBody);
return session.parseLicense(Buffer.from(new Uint8Array(buffer)));
}
} else {
console.info(
'License request failed:',
response.statusMessage,
response.body
response.status,
await response.text()
);
return [];
}

View file

@ -59,13 +59,13 @@ const api: APIType = {
rss_gid: `${domain.www}/syndication/feed?type=episodes&group_id=`, // &lang=enUS
media_page: `${domain.www}/media-`,
series_page: `${domain.www}/series-`,
auth: `${domain.api_beta}/auth/v1/token`,
auth: `${domain.www}/auth/v1/token`,
// mobile api
search3: `${domain.api}/autocomplete.0.json`,
session: `${domain.api}/start_session.0.json`,
collections: `${domain.api}/list_collections.0.json`,
// This User-Agent bypasses Cloudflare security of the newer Endpoint
defaultUserAgent: 'Crunchyroll/4.75.0 (bundle_identifier:com.crunchyroll.iphone; build_number:4100608.433889621) iOS/18.3.2 Gravity/4.75.0',
defaultUserAgent: 'Crunchyroll/4.77.2 (bundle_identifier:com.crunchyroll.iphone; build_number:4139672.438176041) iOS/18.3.2 Gravity/4.77.2',
beta_profile: `${domain.api_beta}/accounts/v1/me/profile`,
beta_cmsToken: `${domain.api_beta}/index/v2`,
search: `${domain.api_beta}/content/v2/discover/search`,
@ -73,6 +73,7 @@ const api: APIType = {
beta_browse: `${domain.api_beta}/content/v1/browse`,
beta_cms: `${domain.api_beta}/cms/v2`,
// beta api
// broken - deprecated since 06.05.2025
drm: `${domain.api_beta}/drm/v1/auth`,
// new drm endpoints
drm_widevine: `${domain.www}/license/v1/license/widevine`,

File diff suppressed because it is too large Load diff