reverted crunchyroll to legacy api

This commit is contained in:
stratumadev 2025-11-26 17:08:47 +01:00
parent ea53e9071f
commit 160c8a1cb4
2 changed files with 75 additions and 35 deletions

View file

@ -390,15 +390,20 @@ export default class Crunchy implements ServiceClass {
// }
public async doAuth(data: AuthData): Promise<AuthResponse> {
const basic = atob(api.basic_auth_token);
const client = basic.split(':');
const uuid = randomUUID();
const authData = new URLSearchParams({
username: data.username,
password: data.password,
grant_type: 'password',
scope: 'offline_access',
client_id: client[0],
client_secret: client[1],
device_id: uuid,
device_name: 'iPhone',
device_type: 'iPhone 13'
device_name: 'emu64xa',
device_type: 'ANDROIDTV'
}).toString();
const authReqOpts: FetchParams = {
method: 'POST',
@ -411,6 +416,7 @@ export default class Crunchy implements ServiceClass {
console.error('Authentication failed!');
return { isOk: false, reason: new Error('Authentication failed') };
}
// To prevent any Cloudflare errors in the future
if (authReq.res.headers.get('Set-Cookie')) {
api.crunchyDefHeader['Cookie'] = authReq.res.headers.get('Set-Cookie') as string;
@ -422,6 +428,7 @@ export default class Crunchy implements ServiceClass {
api.crunchyDefHeader['User-Agent'] = authReq.headers['User-Agent'];
api.crunchyAuthHeader['User-Agent'] = authReq.headers['User-Agent'];
}
this.token = await authReq.res.json();
this.token.device_id = uuid;
this.token.expires = new Date(Date.now() + this.token.expires_in * 1000);
@ -432,13 +439,18 @@ export default class Crunchy implements ServiceClass {
}
public async doAnonymousAuth() {
const basic = atob(api.basic_auth_token);
const client = basic.split(':');
const uuid = randomUUID();
const authData = new URLSearchParams({
grant_type: 'client_id',
scope: 'offline_access',
client_id: client[0],
client_secret: client[1],
device_id: uuid,
device_name: 'iPhone',
device_type: 'iPhone 13'
device_name: 'emu64xa',
device_type: 'ANDROIDTV'
}).toString();
const authReqOpts: FetchParams = {
method: 'POST',
@ -462,6 +474,7 @@ export default class Crunchy implements ServiceClass {
api.crunchyDefHeader['User-Agent'] = authReq.headers['User-Agent'];
api.crunchyAuthHeader['User-Agent'] = authReq.headers['User-Agent'];
}
this.token = await authReq.res.json();
this.token.device_id = uuid;
this.token.expires = new Date(Date.now() + this.token.expires_in * 1000);
@ -473,6 +486,7 @@ export default class Crunchy implements ServiceClass {
console.error('No access token!');
return false;
}
const profileReqOptions = {
headers: {
...api.crunchyDefHeader,
@ -498,15 +512,20 @@ export default class Crunchy implements ServiceClass {
}
public async loginWithToken(refreshToken: string) {
const basic = atob(api.basic_auth_token);
const client = basic.split(':');
const uuid = randomUUID();
const authData = new URLSearchParams({
refresh_token: this.token.refresh_token,
grant_type: 'refresh_token',
//'grant_type': 'etp_rt_cookie',
scope: 'offline_access',
client_id: client[0],
client_secret: client[1],
device_id: uuid,
device_name: 'iPhone',
device_type: 'iPhone 13'
device_name: 'emu64xa',
device_type: 'ANDROIDTV'
}).toString();
const authReqOpts: FetchParams = {
method: 'POST',
@ -533,6 +552,7 @@ export default class Crunchy implements ServiceClass {
api.crunchyDefHeader['User-Agent'] = authReq.headers['User-Agent'];
api.crunchyAuthHeader['User-Agent'] = authReq.headers['User-Agent'];
}
this.token = await authReq.res.json();
this.token.device_id = uuid;
this.token.expires = new Date(Date.now() + this.token.expires_in * 1000);
@ -552,17 +572,24 @@ export default class Crunchy implements ServiceClass {
} else {
//console.info('[WARN] The token has expired compleatly. I will try to refresh the token anyway, but you might have to reauth.');
}
const basic = atob(api.basic_auth_token);
const client = basic.split(':');
const uuid = this.token.device_id || randomUUID();
const authData = new URLSearchParams({
refresh_token: this.token.refresh_token,
grant_type: 'refresh_token',
refresh_token: this.token.refresh_token,
scope: 'offline_access',
client_id: client[0],
client_secret: client[1],
device_id: uuid,
device_name: 'iPhone',
device_type: 'iPhone 13'
device_name: 'emu64xa',
device_type: 'ANDROIDTV'
}).toString();
const authReqOpts: FetchParams = {
method: 'POST',
headers: { ...api.crunchyAuthHeader, 'ETP-Anonymous-ID': uuid },
headers: { ...api.crunchyAuthHeader },
body: authData,
useProxy: true
};
@ -585,6 +612,7 @@ export default class Crunchy implements ServiceClass {
api.crunchyDefHeader['User-Agent'] = authReq.headers['User-Agent'];
api.crunchyAuthHeader['User-Agent'] = authReq.headers['User-Agent'];
}
this.token = await authReq.res.json();
this.token.device_id = uuid;
this.token.expires = new Date(Date.now() + this.token.expires_in * 1000);
@ -1719,7 +1747,7 @@ export default class Crunchy implements ServiceClass {
const me = await this.req.getData(api.me, AuthHeaders);
if (me.ok && me.res) {
const data_me = await me.res.json();
const benefits = await this.req.getData(`https://www.crunchyroll.com/subs/v1/subscriptions/${data_me.external_id}/benefits`, AuthHeaders);
const benefits = await this.req.getData(`https://beta-api.crunchyroll.com/subs/v1/subscriptions/${data_me.external_id}/benefits`, AuthHeaders);
if (benefits.ok && benefits.res) {
const data_benefits = (await benefits.res.json()) as { items: { benefit: string }[] };
if (data_benefits?.items && !data_benefits.items.find((i) => i.benefit === 'offline_viewing')) {
@ -1763,14 +1791,14 @@ export default class Crunchy implements ServiceClass {
if (activeStreamsReq.ok && activeStreamsReq.res) {
const data = await activeStreamsReq.res.json();
for (const s of data.items) {
await this.req.getData(`https://www.crunchyroll.com/playback/v1/token/${s.contentId}/${s.token}`, { ...{ method: 'DELETE' }, ...AuthHeaders });
await this.req.getData(`https://cr-play-service.prd.crunchyrollsvc.com/v1/token/${s.contentId}/${s.token}`, { ...{ method: 'DELETE' }, ...AuthHeaders });
}
console.warn(`Killed ${data.items?.length ?? 0} Sessions`);
}
}
const videoPlaybackReq = await this.req.getData(
`https://www.crunchyroll.com/playback/v3/${currentVersion ? currentVersion.guid : currentMediaId}/${CrunchyVideoPlayStreams['androidtv']}/play?queue=0`,
`https://cr-play-service.prd.crunchyrollsvc.com/v3/${currentVersion ? currentVersion.guid : currentMediaId}/${CrunchyVideoPlayStreams['androidtv']}/play?queue=0`,
AuthHeaders
);
if (!videoPlaybackReq.ok || !videoPlaybackReq.res) {
@ -1787,7 +1815,7 @@ export default class Crunchy implements ServiceClass {
}
if (isDLVideoBypass) {
const videoDLReq = await this.req.getData(
`https://www.crunchyroll.com/playback/v3/${currentVersion ? currentVersion.guid : currentMediaId}/${CrunchyVideoPlayStreams[options.vstream]}/download`,
`https://cr-play-service.prd.crunchyrollsvc.com/v3/${currentVersion ? currentVersion.guid : currentMediaId}/${CrunchyVideoPlayStreams[options.vstream]}/download`,
AuthHeaders
);
if (videoDLReq.ok && videoDLReq.res) {
@ -1824,7 +1852,7 @@ export default class Crunchy implements ServiceClass {
if (!options.cstream && options.vstream !== options.astream && videoStream) {
const audioPlaybackReq = await this.req.getData(
`https://www.crunchyroll.com/playback/v3/${currentVersion ? currentVersion.guid : currentMediaId}/${CrunchyAudioPlayStreams[options.astream]}/${isDLAudioBypass ? 'download' : 'play?queue=1'}`,
`https://cr-play-service.prd.crunchyrollsvc.com/v3/${currentVersion ? currentVersion.guid : currentMediaId}/${CrunchyAudioPlayStreams[options.astream]}/${isDLAudioBypass ? 'download' : 'play?queue=1'}`,
AuthHeaders
);
if (!audioPlaybackReq.ok || !audioPlaybackReq.res) {

View file

@ -1,7 +1,9 @@
// api domains
const domain = {
cr_www: 'https://www.crunchyroll.com',
cr_api: 'https://api.crunchyroll.com',
cr_api: 'https://beta-api.crunchyroll.com',
cr_playback: 'https://cr-play-service.prd.crunchyrollsvc.com',
cr_license: 'https://cr-license-proxy.prd.crunchyrollsvc.com',
hd_www: 'https://www.hidive.com',
hd_api: 'https://api.hidive.com',
hd_new: 'https://dce-frontoffice.imggaming.com'
@ -28,8 +30,9 @@ export type APIType = {
cms_auth: string;
// Crunchyroll Headers
crunchyDefUserAgent: string;
crunchyDefHeader: Record<string, string>;
crunchyDefHeader: Record<string, any>;
crunchyAuthHeader: Record<string, string>;
crunchyAuthRefreshHeader: Record<string, string>;
// Hidive
hd_apikey: string;
hd_devName: string;
@ -50,27 +53,28 @@ const api: APIType = {
bundlejs: 'https://static.crunchyroll.com/vilos-v2/web/vilos/js/bundle.js',
//
// Crunchyroll API
basic_auth_token: 'bGtlc2k3c25zeTlvb2ptaTJyOWg6LWFHRFhGRk5UbHVaTUxZWEVSbmdOWW5FanZnSDVvZHY=',
auth: `${domain.cr_www}/auth/v1/token`,
me: `${domain.cr_www}/accounts/v1/me`,
profile: `${domain.cr_www}/accounts/v1/me/profile`,
search: `${domain.cr_www}/content/v2/discover/search`,
content_cms: `${domain.cr_www}/content/v2/cms`,
content_music: `${domain.cr_www}/content/v2/music`,
browse: `${domain.cr_www}/content/v1/browse`,
browse_all_series: `${domain.cr_www}/content/v2/discover/browse`,
streaming_sessions: `${domain.cr_www}/playback/v1/sessions/streaming`,
drm_widevine: `${domain.cr_www}/license/v1/license/widevine`,
drm_playready: `${domain.cr_www}/license/v1/license/playReady`,
basic_auth_token: 'bmR0aTZicXlqcm9wNXZnZjF0dnU6elpIcS00SEJJVDlDb2FMcnBPREJjRVRCTUNHai1QNlg=',
auth: `${domain.cr_api}/auth/v1/token`,
me: `${domain.cr_api}/accounts/v1/me`,
profile: `${domain.cr_api}/accounts/v1/me/profile`,
search: `${domain.cr_api}/content/v2/discover/search`,
content_cms: `${domain.cr_api}/content/v2/cms`,
content_music: `${domain.cr_api}/content/v2/music`,
browse: `${domain.cr_api}/content/v1/browse`,
browse_all_series: `${domain.cr_api}/content/v2/discover/browse`,
streaming_sessions: `${domain.cr_playback}/v1/sessions/streaming`,
drm_widevine: `https://cr-license-proxy.prd.crunchyrollsvc.com/v1/license/widevine`,
drm_playready: `https://cr-license-proxy.prd.crunchyrollsvc.com/v1/license/playReady`,
//
// Crunchyroll Bucket
cms_bucket: `${domain.cr_www}/cms/v2`,
cms_auth: `${domain.cr_www}/index/v2`,
cms_bucket: `${domain.cr_api}/cms/v2`,
cms_auth: `${domain.cr_api}/index/v2`,
//
// Crunchyroll Headers
crunchyDefUserAgent: 'Crunchyroll/ANDROIDTV/3.49.1_22281 (Android 12; en-US; SHIELD Android TV Build/SR1A.211012.001)',
crunchyDefUserAgent: 'Crunchyroll/ANDROIDTV/3.50.0_22282 (Android 16; en-US; sdk_gphone64_x86_64)',
crunchyDefHeader: {},
crunchyAuthHeader: {},
crunchyAuthRefreshHeader: {},
//
//
// Hidive
@ -89,7 +93,6 @@ const api: APIType = {
api.crunchyDefHeader = {
'User-Agent': api.crunchyDefUserAgent,
Accept: '*/*',
'Accept-Encoding': 'gzip',
Connection: 'Keep-Alive',
Host: 'www.crunchyroll.com'
@ -97,10 +100,19 @@ api.crunchyDefHeader = {
// set header
api.crunchyAuthHeader = {
Authorization: `Basic ${api.basic_auth_token}`,
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
Accept: 'application/json',
'Accept-Charset': 'UTF-8',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Request-Type': 'SignIn',
...api.crunchyDefHeader
};
// set header
api.crunchyAuthRefreshHeader = {
Accept: 'application/json',
'Accept-Charset': 'UTF-8',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
...api.crunchyDefHeader
};
export { domain, api };