diff --git a/crunchy.ts b/crunchy.ts index a5a9215..f9fcddb 100644 --- a/crunchy.ts +++ b/crunchy.ts @@ -140,6 +140,18 @@ export default class Crunchy implements ServiceClass { await this.refreshToken(); await this.logMovieListingById(argv['movie-listing'] as string); } + else if(argv['show-raw'] && argv['show-raw'].match(/^[0-9A-Z]{9}$/)){ + await this.refreshToken(); + await this.logShowRawById(argv['show-raw'] as string); + } + else if(argv['season-raw'] && argv['season-raw'].match(/^[0-9A-Z]{9}$/)){ + await this.refreshToken(); + await this.logSeasonRawById(argv['season-raw'] as string); + } + else if(argv['show-list-raw']){ + await this.refreshToken(); + await this.logShowListRaw(); + } else if(argv.s && argv.s.match(/^[0-9A-Z]{9}$/)){ await this.refreshToken(); if (argv.dubLang.length > 1) { @@ -191,6 +203,173 @@ export default class Crunchy implements ServiceClass { } } + public async logShowRawById(id: string){ + // check token + if(!this.cmsToken.cms){ + console.error('Authentication required!'); + return; + } + // opts + const AuthHeaders = { + headers: { + Authorization: `Bearer ${this.token.access_token}`, + ...api.crunchyDefHeader + }, + 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); + if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){ + console.error('Series Request FAILED!'); + return; + } + const seriesData = await seriesSeasonListReq.res.json(); + for (const item of seriesData.data) { + // stringify each object, then a newline + console.log(JSON.stringify(item)); + } + return seriesData.data; + } + + + public async logSeasonRawById(id: string){ + // check token + if(!this.cmsToken.cms){ + console.error('Authentication required!'); + return; + } + // opts + const AuthHeaders = { + headers: { + Authorization: `Bearer ${this.token.access_token}`, + ...api.crunchyDefHeader + }, + useProxy: true + }; + // seasons list + let episodeList = { total: 0, data: [], meta: {} } as CrunchyEpisodeList; + //get episode info + if (this.api == 'android') { + const reqEpsListOpts = [ + api.beta_cms, + this.cmsToken.cms.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, + }), + ].join(''); + const reqEpsList = await this.req.getData(reqEpsListOpts, AuthHeaders); + if(!reqEpsList.ok || !reqEpsList.res){ + console.error('Episode List Request FAILED!'); + return { isOk: false, reason: new Error('Episode List request failed. No more information provided.') }; + } + //CrunchyEpisodeList + const episodeListAndroid = await reqEpsList.res.json() as CrunchyAndroidEpisodes; + episodeList = { + total: episodeListAndroid.total, + data: episodeListAndroid.items, + meta: {} + }; + } else { + const reqEpsList = await this.req.getData(`${api.cms}/seasons/${id}/episodes?force_locale=&preferred_audio_language=ja-JP&locale=${this.locale}`, AuthHeaders); + if(!reqEpsList.ok || !reqEpsList.res){ + console.error('Episode List Request FAILED!'); + return { isOk: false, reason: new Error('Episode List request failed. No more information provided.') }; + } + //CrunchyEpisodeList + episodeList = await reqEpsList.res.json() as CrunchyEpisodeList; + + + } + for (const item of episodeList.data) { + // stringify each object, then a newline + console.log(JSON.stringify(item)); + } + + // Return the data directly if this function is called by other code + return episodeList.data; + } + + public async logShowListRaw() { + // check token + if(!this.cmsToken.cms){ + console.error('Authentication required!'); + return; + } + + // opts + const AuthHeaders = { + headers: { + Authorization: `Bearer ${this.token.access_token}`, + ...api.crunchyDefHeader + }, + useProxy: true + }; + + let allShows: any[] = []; + let page = 1; + let hasMorePages = true; + + if(this.debug){ + console.info('Retrieving complete show list...'); + } + + while (hasMorePages) { + const searchStart = (page - 1) * 50; + const params = new URLSearchParams({ + 'preferred_audio_language': 'ja-JP', + 'locale': this.locale, + 'ratings': 'true', + 'sort_by': 'alphabetical', + 'n': '50', + 'start': searchStart.toString() + }).toString(); + + const showListReq = await this.req.getData(`${api.browse_all_series}?${params}`, AuthHeaders); + + if (!showListReq.ok || !showListReq.res) { + console.error(`Show List Request FAILED on page ${page}!`); + return allShows; + } + + const showListData = await showListReq.res.json(); + + // Add current page data + for (const item of showListData.data) { + // stringify each object, then a newline + console.log(JSON.stringify(item)); + allShows.push(item); + } + + // Calculate pagination info + const totalItems = showListData.total; + const totalPages = Math.ceil(totalItems / 50); + if(this.debug){ + console.info(`Retrieved page ${page}/${totalPages} (${allShows.length}/${totalItems} items)`); + } + + // Check if we need to fetch more pages + if (page >= totalPages) { + hasMorePages = false; + } else { + page++; + // Add a small delay to avoid rate limiting + await this.sleep(1000); + } + } + + if(this.debug){ + console.info(`Complete show list retrieved: ${allShows.length} items`); + } + return allShows; + } + public async getFonts() { console.info('Downloading fonts...'); const fonts = Object.values(fontsData.fontFamilies).reduce((pre, curr) => pre.concat(curr)); diff --git a/modules/module.api-urls.ts b/modules/module.api-urls.ts index 04761d1..f76d3a3 100644 --- a/modules/module.api-urls.ts +++ b/modules/module.api-urls.ts @@ -26,6 +26,7 @@ export type APIType = { defaultUserAgent: string, beta_profile: string beta_cmsToken: string + browse_all_series: string, search: string cms: string beta_browse: string @@ -73,6 +74,7 @@ const api: APIType = { cms: `${domain.api_beta}/content/v2/cms`, beta_browse: `${domain.api_beta}/content/v1/browse`, beta_cms: `${domain.api_beta}/cms/v2`, + browse_all_series: `${domain.api_beta}/content/v2/discover/browse`, // beta api // broken - deprecated since 06.05.2025 drm: `${domain.api_beta}/drm/v1/auth`, diff --git a/modules/module.args.ts b/modules/module.args.ts index 2338c93..d2d3120 100644 --- a/modules/module.args.ts +++ b/modules/module.args.ts @@ -144,6 +144,36 @@ const args: TAppArg[] = [ type: 'string', usage: '${ID}', }, + { + group: 'dl', + alias: 'sraw', + name: 'show-raw', + describe: 'get raw show data', + docDescribe: true, + service: ['crunchy'], + type: 'string', + usage: '${ID}', + }, + { + group: 'dl', + alias: 'seraw', + name: 'season-raw', + describe: 'Get Raw Season data', + docDescribe: true, + service: ['crunchy'], + type: 'string', + usage: '${ID}', + }, + { + group: 'dl', + alias: 'slraw', + name: 'show-list-raw', + describe: 'Get Raw show list data', + docDescribe: true, + service: ['crunchy'], + type: 'boolean', + usage: '', + }, { name: 'series', group: 'dl',