diff --git a/crunchy.ts b/crunchy.ts index 58497e7..774f8bb 100644 --- a/crunchy.ts +++ b/crunchy.ts @@ -212,7 +212,11 @@ export default class Crunchy implements ServiceClass { console.info(''); } const fontUrl = fontsData.root + f; - const getFont = await this.req.getData(fontUrl); + const getFont = await this.req.getData(fontUrl, { + headers: { + 'User-Agent': api.defaultUserAgent + } + }); if(getFont.ok && getFont.res){ fs.writeFileSync(fontLoc, Buffer.from(await getFont.res.arrayBuffer())); console.info(`Downloaded: ${f}`); @@ -233,7 +237,8 @@ export default class Crunchy implements ServiceClass { 'grant_type': 'password', 'scope': 'offline_access', 'device_id': uuid, - 'device_type': 'Chrome on Windows' + 'device_name': 'iPhone', + 'device_type': 'iPhone 13' }).toString(); const authReqOpts: reqModule.Params = { method: 'POST', @@ -260,7 +265,8 @@ export default class Crunchy implements ServiceClass { 'grant_type': 'client_id', 'scope': 'offline_access', 'device_id': uuid, - 'device_type': 'Chrome on Windows' + 'device_name': 'iPhone', + 'device_type': 'iPhone 13' }).toString(); const authReqOpts: reqModule.Params = { method: 'POST', @@ -286,6 +292,7 @@ export default class Crunchy implements ServiceClass { const profileReqOptions = { headers: { Authorization: `Bearer ${this.token.access_token}`, + 'User-Agent': api.defaultUserAgent }, useProxy: true }; @@ -315,7 +322,8 @@ export default class Crunchy implements ServiceClass { //'grant_type': 'etp_rt_cookie', 'scope': 'offline_access', 'device_id': uuid, - 'device_type': 'Chrome on Windows' + 'device_name': 'iPhone', + 'device_type': 'iPhone 13' }).toString(); const authReqOpts: reqModule.Params = { method: 'POST', @@ -400,6 +408,7 @@ export default class Crunchy implements ServiceClass { const cmsTokenReqOpts = { headers: { Authorization: `Bearer ${this.token.access_token}`, + 'User-Agent': api.defaultUserAgent }, useProxy: true }; @@ -432,7 +441,11 @@ export default class Crunchy implements ServiceClass { 'Key-Pair-Id': this.cmsToken.cms.key_pair_id, }), ].join(''); - const indexReq = await this.req.getData(indexReqOpts); + const indexReq = await this.req.getData(indexReqOpts, { + headers: { + 'User-Agent': api.defaultUserAgent + } + }); if(!indexReq.ok || ! indexReq.res){ console.error('Get CMS index FAILED!'); return; @@ -448,6 +461,7 @@ export default class Crunchy implements ServiceClass { const searchReqOpts = { headers: { Authorization: `Bearer ${this.token.access_token}`, + 'User-Agent': api.defaultUserAgent }, useProxy: true }; @@ -715,6 +729,7 @@ export default class Crunchy implements ServiceClass { const AuthHeaders = { headers: { Authorization: `Bearer ${this.token.access_token}`, + 'User-Agent': api.defaultUserAgent }, useProxy: true }; @@ -756,6 +771,7 @@ export default class Crunchy implements ServiceClass { const AuthHeaders = { headers: { Authorization: `Bearer ${this.token.access_token}`, + 'User-Agent': api.defaultUserAgent }, useProxy: true }; @@ -795,6 +811,7 @@ export default class Crunchy implements ServiceClass { const newlyAddedReqOpts = { headers: { Authorization: `Bearer ${this.token.access_token}`, + 'User-Agent': api.defaultUserAgent }, useProxy: true }; @@ -829,6 +846,7 @@ export default class Crunchy implements ServiceClass { const AuthHeaders = { headers: { Authorization: `Bearer ${this.token.access_token}`, + 'User-Agent': api.defaultUserAgent }, useProxy: true }; @@ -1031,7 +1049,11 @@ export default class Crunchy implements ServiceClass { }), ].join(''); - const extIdReq = await this.req.getData(extIdReqOpts); + const extIdReq = await this.req.getData(extIdReqOpts, { + headers: { + 'User-Agent': api.defaultUserAgent + } + }); if (!extIdReq.ok || !extIdReq.res) { console.error('Objects Request FAILED!'); if (extIdReq.error && extIdReq.error.res && extIdReq.error.res.body) { @@ -1061,6 +1083,7 @@ export default class Crunchy implements ServiceClass { const AuthHeaders = { headers: { Authorization: `Bearer ${this.token.access_token}`, + 'User-Agent': api.defaultUserAgent }, useProxy: true }; @@ -1240,6 +1263,7 @@ export default class Crunchy implements ServiceClass { 'X-Cr-Disable-Drm': 'true', 'X-Cr-Enable-Drm': 'false', 'X-Cr-Stream-Limits': 'false', + 'User-Agent': api.defaultUserAgent //'X-Cr-Segment-CDN': 'all', //'User-Agent': 'Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27' } @@ -1271,11 +1295,19 @@ export default class Crunchy implements ServiceClass { const compiledChapters: string[] = []; if (options.chapters) { //Make Chapter Request - const chapterRequest = await this.req.getData(`https://static.crunchyroll.com/skip-events/production/${currentMediaId}.json`); + const chapterRequest = await this.req.getData(`https://static.crunchyroll.com/skip-events/production/${currentMediaId}.json`, { + headers: { + 'User-Agent': api.defaultUserAgent + } + }); if(!chapterRequest.ok || !chapterRequest.res){ //Old Chapter Request Fallback console.warn('Chapter request failed, attempting old API'); - const oldChapterRequest = await this.req.getData(`https://static.crunchyroll.com/datalab-intro-v2/${currentMediaId}.json`); + const oldChapterRequest = await this.req.getData(`https://static.crunchyroll.com/datalab-intro-v2/${currentMediaId}.json`, { + headers: { + 'User-Agent': api.defaultUserAgent + } + }); if(!oldChapterRequest.ok || !oldChapterRequest.res) { console.warn('Old Chapter API request failed'); } else { @@ -1734,7 +1766,10 @@ export default class Crunchy implements ServiceClass { '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); @@ -1947,7 +1982,11 @@ export default class Crunchy implements ServiceClass { fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep); const outFile = parseFileName(options.fileName + '.' + (mMeta.lang?.name || lang.name), variables, options.numbers, options.override).join(path.sep); console.info(`Output filename: ${outFile}`); - const chunkPage = await this.req.getData(selPlUrl); + const chunkPage = await this.req.getData(selPlUrl, { + headers: { + 'User-Agent': api.defaultUserAgent + } + }); if(!chunkPage.ok || !chunkPage.res){ console.error('CAN\'T FETCH VIDEO PLAYLIST!'); dlFailed = true; @@ -2097,7 +2136,11 @@ export default class Crunchy implements ServiceClass { if (files.some(a => a.type === 'Subtitle' && (a.language.cr_locale == langItem.cr_locale || a.language.locale == langItem.locale) && a.cc === isCC && a.signs === isSigns)) continue; if(options.dlsubs.includes('all') || options.dlsubs.includes(langItem.locale)){ - const subsAssReq = await this.req.getData(subsItem.url); + const subsAssReq = await this.req.getData(subsItem.url, { + headers: { + 'User-Agent': api.defaultUserAgent + } + }); if(subsAssReq.ok && subsAssReq.res){ let sBody = await subsAssReq.res.text(); if (subsItem.format == 'vtt') { @@ -2469,6 +2512,7 @@ export default class Crunchy implements ServiceClass { const AuthHeaders = { headers: { Authorization: `Bearer ${this.token.access_token}`, + 'User-Agent': api.defaultUserAgent }, useProxy: true }; @@ -2497,6 +2541,7 @@ export default class Crunchy implements ServiceClass { const AuthHeaders = { headers: { Authorization: `Bearer ${this.token.access_token}`, + 'User-Agent': api.defaultUserAgent }, useProxy: true }; diff --git a/modules/module.api-urls.ts b/modules/module.api-urls.ts index 1c213d7..bcd029a 100644 --- a/modules/module.api-urls.ts +++ b/modules/module.api-urls.ts @@ -24,6 +24,7 @@ export type APIType = { collections: string // beta api beta_auth: string + defaultUserAgent: string, authBasic: string authBasicMob: string authBasicSwitch: string, @@ -72,17 +73,20 @@ const api: APIType = { search3: `${domain.api}/autocomplete.0.json`, session: `${domain.api}/start_session.0.json`, collections: `${domain.api}/list_collections.0.json`, - // beta api - beta_auth: `${domain.api_beta}/auth/v1/token`, + // new api + beta_auth: `${domain.www}/auth/v1/token`, + // This User-Agent bypasses Cloudflare security by the newer Endpoint + defaultUserAgent: 'Crunchyroll/4.68.2 (bundle_identifier:com.crunchyroll.iphone; build_number:4007128.533694055) iOS/18.2.0 Gravity/4.68.2', authBasic: 'Basic bm9haWhkZXZtXzZpeWcwYThsMHE6', - authBasicMob: 'Basic dXU4aG0wb2g4dHFpOWV0eXl2aGo6SDA2VnVjRnZUaDJ1dEYxM0FBS3lLNE85UTRhX3BlX1o=', + authBasicMob: 'Basic eHVuaWh2ZWRidDNtYmlzdWhldnQ6MWtJUzVkeVR2akUwX3JxYUEzWWVBaDBiVVhVbXhXMTE=', authBasicSwitch: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=', - beta_profile: `${domain.api_beta}/accounts/v1/me/profile`, - beta_cmsToken: `${domain.api_beta}/index/v2`, - search: `${domain.api_beta}/content/v2/discover/search`, - cms: `${domain.api_beta}/content/v2/cms`, - beta_browse: `${domain.api_beta}/content/v1/browse`, - beta_cms: `${domain.api_beta}/cms/v2`, + beta_profile: `${domain.www}/accounts/v1/me/profile`, + beta_cmsToken: `${domain.www}/index/v2`, + search: `${domain.www}/content/v2/discover/search`, + cms: `${domain.www}/content/v2/cms`, + beta_browse: `${domain.www}/content/v1/browse`, + beta_cms: `${domain.www}/cms/v2`, + // beta api drm: `${domain.api_beta}/drm/v1/auth`, crunchyAuthHeader: {}, crunchyAuthHeaderMob: {}, @@ -106,8 +110,9 @@ api.crunchyAuthHeader = { }; api.crunchyAuthHeaderMob = { - Authorization: api.authBasicSwitch, - 'user-agent': 'Crunchyroll/3.60.0 Android/9 okhttp/4.12.0' + Authorization: api.authBasicMob, + 'User-Agent': api.defaultUserAgent, + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' }; api.crunchyAuthHeaderSwitch = {