From b92ab022c7daeaac865b01441183cb71595d8406 Mon Sep 17 00:00:00 2001 From: stratumadev Date: Sat, 2 Aug 2025 21:34:16 +0200 Subject: [PATCH] updated cr bucket requests to www. --- crunchy.ts | 106 ++++++++++----------- modules/module.api-urls.ts | 184 +++++++++++++++++-------------------- 2 files changed, 135 insertions(+), 155 deletions(-) diff --git a/crunchy.ts b/crunchy.ts index fb0ad09..d5c227e 100644 --- a/crunchy.ts +++ b/crunchy.ts @@ -73,7 +73,7 @@ export default class Crunchy implements ServiceClass { } public checkToken(): boolean { - return Object.keys(this.cmsToken.cms ?? {}).length > 0; + return Object.keys(this.cmsToken.cms_web ?? {}).length > 0; } public async cli() { @@ -202,7 +202,7 @@ export default class Crunchy implements ServiceClass { public async logShowRawById(id: string){ // check token - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return; } @@ -215,7 +215,7 @@ export default class Crunchy implements ServiceClass { useProxy: true }; // seasons list - const seriesSeasonListReq = await this.req.getData(`${api.cms}/series/${id}/seasons?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); + const seriesSeasonListReq = await this.req.getData(`${api.content_cms}/series/${id}/seasons?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){ console.error('Series Request FAILED!'); return; @@ -231,7 +231,7 @@ export default class Crunchy implements ServiceClass { public async logSeasonRawById(id: string){ // check token - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return; } @@ -248,16 +248,16 @@ export default class Crunchy implements ServiceClass { //get episode info const reqEpsListOpts = [ api.cms_bucket, - this.cmsToken.cms.bucket, + this.cmsToken.cms_web.bucket, '/episodes?', new URLSearchParams({ 'force_locale': '', 'preferred_audio_language': 'ja-JP', 'locale': this.locale, 'season_id': id, - 'Policy': this.cmsToken.cms.policy, - 'Signature': this.cmsToken.cms.signature, - 'Key-Pair-Id': this.cmsToken.cms.key_pair_id, + 'Policy': this.cmsToken.cms_web.policy, + 'Signature': this.cmsToken.cms_web.signature, + 'Key-Pair-Id': this.cmsToken.cms_web.key_pair_id, }), ].join(''); const reqEpsList = await this.req.getData(reqEpsListOpts, AuthHeaders); @@ -283,7 +283,7 @@ export default class Crunchy implements ServiceClass { public async logShowListRaw() { // check token - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return; } @@ -624,8 +624,8 @@ export default class Crunchy implements ServiceClass { return; } - if (ifNeeded && this.cmsToken.cms) { - if (!(Date.now() >= new Date(this.cmsToken.cms.expires).getTime())) { + if (ifNeeded && this.cmsToken.cms_web) { + if (!(Date.now() >= new Date(this.cmsToken.cms_web.expires).getTime())) { return; } } @@ -637,38 +637,38 @@ export default class Crunchy implements ServiceClass { }, useProxy: true }; - const cmsTokenReq = await this.req.getData(api.cmsToken, cmsTokenReqOpts); + const cmsTokenReq = await this.req.getData(api.cms_auth, cmsTokenReqOpts); if(!cmsTokenReq.ok || !cmsTokenReq.res){ console.error('Authentication CMS token failed!'); return; } this.cmsToken = await cmsTokenReq.res.json(); - console.info('Your Country: %s\n', this.cmsToken.cms?.bucket.split('/')[1]); + console.info('Your Country: %s\n', this.cmsToken.cms_web?.bucket.split('/')[1]); } public async getCmsData(){ // check token - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return; } // opts const indexReqOpts = [ api.cms_bucket, - this.cmsToken.cms.bucket, + this.cmsToken.cms_web.bucket, '/index?', new URLSearchParams({ 'force_locale': '', 'preferred_audio_language': 'ja-JP', 'locale': this.locale, - 'Policy': this.cmsToken.cms.policy, - 'Signature': this.cmsToken.cms.signature, - 'Key-Pair-Id': this.cmsToken.cms.key_pair_id, + 'Policy': this.cmsToken.cms_web.policy, + 'Signature': this.cmsToken.cms_web.signature, + 'Key-Pair-Id': this.cmsToken.cms_web.key_pair_id, }), ].join(''); const indexReq = await this.req.getData(indexReqOpts, { headers: { - 'User-Agent': api.defaultUserAgent + 'User-Agent': api.crunchyDefUserAgent } }); if(!indexReq.ok || ! indexReq.res){ @@ -946,7 +946,7 @@ export default class Crunchy implements ServiceClass { pad = pad || 0; hideSeriesTitle = hideSeriesTitle !== undefined ? hideSeriesTitle : false; // check token - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return; } @@ -960,7 +960,7 @@ export default class Crunchy implements ServiceClass { }; // reqs if(!hideSeriesTitle){ - const seriesReq = await this.req.getData(`${api.cms}/series/${id}?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); + const seriesReq = await this.req.getData(`${api.content_cms}/series/${id}?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); if(!seriesReq.ok || !seriesReq.res){ console.error('Series Request FAILED!'); return; @@ -969,7 +969,7 @@ export default class Crunchy implements ServiceClass { await this.logObject(seriesData.data[0], pad, false); } // seasons list - const seriesSeasonListReq = await this.req.getData(`${api.cms}/series/${id}/seasons?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); + const seriesSeasonListReq = await this.req.getData(`${api.content_cms}/series/${id}/seasons?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){ console.error('Series Request FAILED!'); return; @@ -987,7 +987,7 @@ export default class Crunchy implements ServiceClass { public async logMovieListingById(id: string, pad?: number){ pad = pad || 2; - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return; } @@ -1002,7 +1002,7 @@ export default class Crunchy implements ServiceClass { }; //Movie Listing - const movieListingReq = await this.req.getData(`${api.cms}/movie_listings/${id}?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); + const movieListingReq = await this.req.getData(`${api.content_cms}/movie_listings/${id}?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); if(!movieListingReq.ok || !movieListingReq.res){ console.error('Movie Listing Request FAILED!'); return; @@ -1017,7 +1017,7 @@ export default class Crunchy implements ServiceClass { } //Movies - const moviesListReq = await this.req.getData(`${api.cms}/movie_listings/${id}/movies?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); + const moviesListReq = await this.req.getData(`${api.content_cms}/movie_listings/${id}/movies?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); if(!moviesListReq.ok || !moviesListReq.res){ console.error('Movies List Request FAILED!'); return; @@ -1073,14 +1073,14 @@ export default class Crunchy implements ServiceClass { await this.logObject(i, 2); } // calculate pages - const itemPad = parseInt(new URL(newlyAddedResults.__href__, domain.api_beta).searchParams.get('start') as string); + const itemPad = parseInt(new URL(newlyAddedResults.__href__, domain.cr_www).searchParams.get('start') as string); const pageCur = itemPad > 0 ? Math.ceil(itemPad/25) + 1 : 1; const pageMax = Math.ceil(newlyAddedResults.total/25); console.info(` Total results: ${newlyAddedResults.total} (Page: ${pageCur}/${pageMax})`); } public async getSeasonById(id: string, numbers: number, e: string|undefined, but: boolean, all: boolean) : Promise> { - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return { isOk: false, reason: new Error('Authentication required') }; } @@ -1095,7 +1095,7 @@ export default class Crunchy implements ServiceClass { //get show info - const showInfoReq = await this.req.getData(`${api.cms}/seasons/${id}?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); + const showInfoReq = await this.req.getData(`${api.content_cms}/seasons/${id}?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); if(!showInfoReq.ok || !showInfoReq.res){ console.error('Show Request FAILED!'); return { isOk: false, reason: new Error('Show request failed. No more information provided.') }; @@ -1107,16 +1107,16 @@ export default class Crunchy implements ServiceClass { //get episode info const reqEpsListOpts = [ api.cms_bucket, - this.cmsToken.cms.bucket, + this.cmsToken.cms_web.bucket, '/episodes?', new URLSearchParams({ 'force_locale': '', 'preferred_audio_language': 'ja-JP', 'locale': this.locale, 'season_id': id, - 'Policy': this.cmsToken.cms.policy, - 'Signature': this.cmsToken.cms.signature, - 'Key-Pair-Id': this.cmsToken.cms.key_pair_id, + 'Policy': this.cmsToken.cms_web.policy, + 'Signature': this.cmsToken.cms_web.signature, + 'Key-Pair-Id': this.cmsToken.cms_web.key_pair_id, }), ].join(''); const reqEpsList = await this.req.getData(reqEpsListOpts, AuthHeaders); @@ -1255,7 +1255,7 @@ export default class Crunchy implements ServiceClass { } public async getObjectById(e?: string, earlyReturn?: boolean, external_id?: boolean): Promise[]|undefined> { - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return []; } @@ -1267,7 +1267,7 @@ export default class Crunchy implements ServiceClass { for (const ob of epFilter.values) { const extIdReqOpts = [ api.cms_bucket, - this.cmsToken.cms.bucket, + this.cmsToken.cms_web.bucket, '/channels/crunchyroll/objects', '?', new URLSearchParams({ @@ -1275,15 +1275,15 @@ export default class Crunchy implements ServiceClass { 'preferred_audio_language': 'ja-JP', 'locale': this.locale, 'external_id': ob, - 'Policy': this.cmsToken.cms.policy, - 'Signature': this.cmsToken.cms.signature, - 'Key-Pair-Id': this.cmsToken.cms.key_pair_id, + 'Policy': this.cmsToken.cms_web.policy, + 'Signature': this.cmsToken.cms_web.signature, + 'Key-Pair-Id': this.cmsToken.cms_web.key_pair_id, }), ].join(''); const extIdReq = await this.req.getData(extIdReqOpts, { headers: { - 'User-Agent': api.defaultUserAgent + 'User-Agent': api.crunchyDefUserAgent } }); if (!extIdReq.ok || !extIdReq.res) { @@ -1324,7 +1324,7 @@ export default class Crunchy implements ServiceClass { let objectInfo: ObjectInfo = { total: 0, data: [], meta: {} }; const objectReqOpts = [ api.cms_bucket, - this.cmsToken.cms.bucket, + this.cmsToken.cms_web.bucket, '/objects/', doEpsFilter.values.join(','), '?', @@ -1332,9 +1332,9 @@ export default class Crunchy implements ServiceClass { 'force_locale': '', 'preferred_audio_language': 'ja-JP', 'locale': this.locale, - 'Policy': this.cmsToken.cms.policy, - 'Signature': this.cmsToken.cms.signature, - 'Key-Pair-Id': this.cmsToken.cms.key_pair_id, + 'Policy': this.cmsToken.cms_web.policy, + 'Signature': this.cmsToken.cms_web.signature, + 'Key-Pair-Id': this.cmsToken.cms_web.key_pair_id, }), ].join(''); const objectReq = await this.req.getData(objectReqOpts, AuthHeaders); @@ -1438,7 +1438,7 @@ export default class Crunchy implements ServiceClass { fileName: string, error: boolean } | undefined> { - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return; } @@ -1588,7 +1588,7 @@ export default class Crunchy implements ServiceClass { ); //We need the duration of the ep let epDuration: number | undefined; - const epiMeta = await this.req.getData(`${api.cms}/objects/${currentMediaId}?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); + const epiMeta = await this.req.getData(`${api.content_cms}/objects/${currentMediaId}?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); if(!epiMeta.ok || !epiMeta.res){ epDuration = 7200; } else { @@ -1639,7 +1639,7 @@ export default class Crunchy implements ServiceClass { if (options.tsd) { console.warn('Total Session Death Active'); - const activeStreamsReq = await this.req.getData(api.streaming, AuthHeaders); + const activeStreamsReq = await this.req.getData(api.streaming_sessions, AuthHeaders); if (activeStreamsReq.ok && activeStreamsReq.res){ const data = await activeStreamsReq.res.json(); for (const s of data.items) { @@ -2890,7 +2890,7 @@ export default class Crunchy implements ServiceClass { } public async parseSeriesById(id: string) { - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return; } @@ -2904,7 +2904,7 @@ export default class Crunchy implements ServiceClass { }; // seasons list - const seriesSeasonListReq = await this.req.getData(`${api.cms}/series/${id}/seasons?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); + const seriesSeasonListReq = await this.req.getData(`${api.content_cms}/series/${id}/seasons?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){ console.error('Series Request FAILED!'); return; @@ -2919,7 +2919,7 @@ export default class Crunchy implements ServiceClass { } public async getSeasonDataById(item: SeriesSearchItem, log = false){ - if(!this.cmsToken.cms){ + if(!this.cmsToken.cms_web){ console.error('Authentication required!'); return; } @@ -2933,7 +2933,7 @@ export default class Crunchy implements ServiceClass { }; //get show info - const showInfoReq = await this.req.getData(`${api.cms}/seasons/${item.id}?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); + const showInfoReq = await this.req.getData(`${api.content_cms}/seasons/${item.id}?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); if(!showInfoReq.ok || !showInfoReq.res){ console.error('Show Request FAILED!'); return; @@ -2950,16 +2950,16 @@ export default class Crunchy implements ServiceClass { const reqEpsListOpts = [ api.cms_bucket, - this.cmsToken.cms.bucket, + this.cmsToken.cms_web.bucket, '/episodes?', new URLSearchParams({ 'force_locale': '', 'preferred_audio_language': 'ja-JP', 'locale': this.locale, 'season_id': id, - 'Policy': this.cmsToken.cms.policy, - 'Signature': this.cmsToken.cms.signature, - 'Key-Pair-Id': this.cmsToken.cms.key_pair_id, + 'Policy': this.cmsToken.cms_web.policy, + 'Signature': this.cmsToken.cms_web.signature, + 'Key-Pair-Id': this.cmsToken.cms_web.key_pair_id, }), ].join(''); const reqEpsList = await this.req.getData(reqEpsListOpts, AuthHeaders); diff --git a/modules/module.api-urls.ts b/modules/module.api-urls.ts index 535c38b..07b9c4b 100644 --- a/modules/module.api-urls.ts +++ b/modules/module.api-urls.ts @@ -1,119 +1,99 @@ // api domains const domain = { - www: 'https://www.crunchyroll.com', - api: 'https://api.crunchyroll.com', - api_beta: 'https://beta-api.crunchyroll.com', - hd_www: 'https://www.hidive.com', - hd_api: 'https://api.hidive.com', - hd_new: 'https://dce-frontoffice.imggaming.com' + cr_www: 'https://www.crunchyroll.com', + cr_api: 'https://api.crunchyroll.com', + hd_www: 'https://www.hidive.com', + hd_api: 'https://api.hidive.com', + hd_new: 'https://dce-frontoffice.imggaming.com' }; export type APIType = { - bundlejs: string, - newani: string, - search1: string, - search2: string, - rss_cid: string, - rss_gid: string - media_page: string - series_page: string - auth: string - // mobile api - search3: string - session: string - collections: string - // beta api - defaultUserAgent: string, - profile: string - cmsToken: string - browse_all_series: string, - search: string - cms: string - cms_bucket: string - browse: string - drm: string; - drm_widevine: string; - drm_playready: string; - streaming: string; - /** - * Header - */ - crunchyDefHeader: Record, - crunchyAuthHeader: Record, - hd_apikey: string, - hd_devName: string, - hd_appId: string, - hd_clientWeb: string, - hd_clientExo: string, - hd_api: string, - hd_new_api: string, - hd_new_apiKey: string, - hd_new_version: string, -} + // Crunchyroll Vilos bundle.js + bundlejs: string; + // Crunchyroll API + auth: string; + profile: string; + search: string; + content_cms: string; + browse: string; + browse_all_series: string; + streaming_sessions: string; + drm_widevine: string; + drm_playready: string; + // Crunchyroll Bucket + cms_bucket: string; + cms_auth: string; + // Crunchyroll Headers + crunchyDefUserAgent: string; + crunchyDefHeader: Record; + crunchyAuthHeader: Record; + // Hidive + hd_apikey: string; + hd_devName: string; + hd_appId: string; + hd_clientWeb: string; + hd_clientExo: string; + hd_api: string; + hd_new_api: string; + hd_new_apiKey: string; + hd_new_version: string; +}; -// api urls const api: APIType = { - // web - bundlejs: 'https://static.crunchyroll.com/vilos-v2/web/vilos/js/bundle.js', - newani: `${domain.www}/rss/anime`, - search1: `${domain.www}/ajax/?req=RpcApiSearch_GetSearchCandidates`, - search2: `${domain.www}/search_page`, - rss_cid: `${domain.www}/syndication/feed?type=episodes&id=`, // &lang=enUS - rss_gid: `${domain.www}/syndication/feed?type=episodes&group_id=`, // &lang=enUS - media_page: `${domain.www}/media-`, - series_page: `${domain.www}/series-`, - 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`, - defaultUserAgent: 'Crunchyroll/4.83.0 (bundle_identifier:com.crunchyroll.iphone; build_number:4254815.324030705) iOS/19.0.0 Gravity/4.83.0', - profile: `${domain.www}/accounts/v1/me/profile`, - cmsToken: `${domain.www}/index/v2`, - search: `${domain.www}/content/v2/discover/search`, - cms: `${domain.www}/content/v2/cms`, - cms_bucket: `${domain.api_beta}/cms/v2`, - browse: `${domain.www}/content/v1/browse`, - browse_all_series: `${domain.www}/content/v2/discover/browse`, - // 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`, - // playready endpoint currently broken - drm_playready: `${domain.www}/license/v1/license/playReady`, - // endpoint to get active streaming sessions - streaming: `${domain.www}/playback/v1/sessions/streaming`, - crunchyDefHeader: {}, - crunchyAuthHeader: {}, - //hidive API - hd_apikey: '508efd7b42d546e19cc24f4d0b414e57e351ca73', - hd_devName: 'Android', - hd_appId: '24i-Android', - hd_clientWeb: 'okhttp/3.4.1', - hd_clientExo: 'smartexoplayer/1.6.0.R (Linux;Android 6.0) ExoPlayerLib/2.6.0', - hd_api: `${domain.hd_api}/api/v1`, - //Hidive New API - hd_new_api: `${domain.hd_new}/api`, - hd_new_apiKey: '857a1e5d-e35e-4fdf-805b-a87b6f8364bf', - hd_new_version: '6.0.1.bbf09a2' + // + // + // Crunchyroll + // Vilos bundle.js (where we can extract the basic token thats needed for the initial auth) + bundlejs: 'https://static.crunchyroll.com/vilos-v2/web/vilos/js/bundle.js', + // + // Crunchyroll API + auth: `${domain.cr_www}/auth/v1/token`, + profile: `${domain.cr_www}/accounts/v1/me/profile`, + search: `${domain.cr_www}/content/v2/discover/search`, + content_cms: `${domain.cr_www}/content/v2/cms`, + 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`, + // + // Crunchyroll Bucket + cms_bucket: `${domain.cr_www}/cms/v2`, + cms_auth: `${domain.cr_www}/index/v2`, + // + // Crunchyroll Headers + crunchyDefUserAgent: 'Crunchyroll/4.83.0 (bundle_identifier:com.crunchyroll.iphone; build_number:4254815.324030705) iOS/19.0.0 Gravity/4.83.0', + crunchyDefHeader: {}, + crunchyAuthHeader: {}, + // + // + // Hidive + // Hidive API + hd_apikey: '508efd7b42d546e19cc24f4d0b414e57e351ca73', + hd_devName: 'Android', + hd_appId: '24i-Android', + hd_clientWeb: 'okhttp/3.4.1', + hd_clientExo: 'smartexoplayer/1.6.0.R (Linux;Android 6.0) ExoPlayerLib/2.6.0', + hd_api: `${domain.hd_api}/api/v1`, + // Hidive New API + hd_new_api: `${domain.hd_new}/api`, + hd_new_apiKey: '857a1e5d-e35e-4fdf-805b-a87b6f8364bf', + hd_new_version: '6.0.1.bbf09a2' }; api.crunchyDefHeader = { - 'User-Agent': api.defaultUserAgent, - 'Accept': '*/*', - 'Accept-Encoding': 'gzip;q=1.0, compress;q=0.5', - 'Accept-Language': 'de-IT;q=1.0, it-IT;q=0.9, en-GB;q=0.8', - 'Connection': 'keep-alive', - 'Host': 'www.crunchyroll.com' + 'User-Agent': api.crunchyDefUserAgent, + Accept: '*/*', + 'Accept-Encoding': 'gzip;q=1.0, compress;q=0.5', + 'Accept-Language': 'de-IT;q=1.0, it-IT;q=0.9, en-GB;q=0.8', + Connection: 'keep-alive', + Host: 'www.crunchyroll.com' }; // set header api.crunchyAuthHeader = { - 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', ...api.crunchyDefHeader }; -export { - domain, api -}; +export { domain, api };