API switching
This commit is contained in:
parent
942f673934
commit
ca01c04961
10 changed files with 314 additions and 160 deletions
56
@types/crunchyAndroidEpisodes.d.ts
vendored
56
@types/crunchyAndroidEpisodes.d.ts
vendored
|
|
@ -1,3 +1,5 @@
|
|||
import { Images } from './crunchyEpisodeList';
|
||||
|
||||
export interface CrunchyAndroidEpisodes {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
|
|
@ -5,13 +7,13 @@ export interface CrunchyAndroidEpisodes {
|
|||
__links__: Actions;
|
||||
__actions__: Actions;
|
||||
total: number;
|
||||
items: CrunchyEpisode[];
|
||||
items: CrunchyAndroidEpisode[];
|
||||
}
|
||||
|
||||
export interface Actions {
|
||||
}
|
||||
|
||||
export interface CrunchyEpisode {
|
||||
export interface CrunchyAndroidEpisode {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
|
|
@ -19,7 +21,7 @@ export interface CrunchyEpisode {
|
|||
__actions__: Actions;
|
||||
playback: string;
|
||||
id: string;
|
||||
channel_id: string;
|
||||
channel_id: ChannelID;
|
||||
series_id: string;
|
||||
series_title: string;
|
||||
series_slug_title: string;
|
||||
|
|
@ -37,19 +39,19 @@ export interface CrunchyEpisode {
|
|||
next_episode_id: string;
|
||||
next_episode_title: string;
|
||||
hd_flag: boolean;
|
||||
maturity_ratings: string[];
|
||||
maturity_ratings: MaturityRating[];
|
||||
extended_maturity_rating: Actions;
|
||||
is_mature: boolean;
|
||||
mature_blocked: boolean;
|
||||
episode_air_date: string;
|
||||
upload_date: string;
|
||||
availability_starts: string;
|
||||
availability_ends: string;
|
||||
episode_air_date: Date;
|
||||
upload_date: Date;
|
||||
availability_starts: Date;
|
||||
availability_ends: Date;
|
||||
eligible_region: string;
|
||||
available_date: Date;
|
||||
free_available_date: string;
|
||||
free_available_date: Date;
|
||||
premium_date: Date;
|
||||
premium_available_date: string;
|
||||
premium_available_date: Date;
|
||||
is_subbed: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_clip: boolean;
|
||||
|
|
@ -57,13 +59,13 @@ export interface CrunchyEpisode {
|
|||
seo_description: string;
|
||||
season_tags: string[];
|
||||
available_offline: boolean;
|
||||
subtitle_locales: string[];
|
||||
subtitle_locales: Locale[];
|
||||
availability_notes: string;
|
||||
audio_locale: string;
|
||||
audio_locale: Locale;
|
||||
versions: Version[];
|
||||
closed_captions_available: boolean;
|
||||
identifier: string;
|
||||
media_type: string;
|
||||
media_type: MediaType;
|
||||
slug: string;
|
||||
images: Images;
|
||||
duration_ms: number;
|
||||
|
|
@ -76,21 +78,17 @@ export interface CrunchyEpisode {
|
|||
}
|
||||
|
||||
export interface Links {
|
||||
'episode/channel': EpisodeChannel;
|
||||
'episode/next_episode': EpisodeChannel;
|
||||
'episode/season': EpisodeChannel;
|
||||
'episode/series': EpisodeChannel;
|
||||
streams: EpisodeChannel;
|
||||
'episode/channel': Link;
|
||||
'episode/next_episode': Link;
|
||||
'episode/season': Link;
|
||||
'episode/series': Link;
|
||||
streams: Link;
|
||||
}
|
||||
|
||||
export interface EpisodeChannel {
|
||||
export interface Link {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface Images {
|
||||
thumbnail: Array<Thumbnail[]>;
|
||||
}
|
||||
|
||||
export interface Thumbnail {
|
||||
width: number;
|
||||
height: number;
|
||||
|
|
@ -117,6 +115,18 @@ export enum Locale {
|
|||
jaJP = 'ja-JP',
|
||||
}
|
||||
|
||||
export enum MediaType {
|
||||
Episode = 'episode',
|
||||
}
|
||||
|
||||
export enum ChannelID {
|
||||
Crunchyroll = 'crunchyroll',
|
||||
}
|
||||
|
||||
export enum MaturityRating {
|
||||
Tv14 = 'TV-14',
|
||||
}
|
||||
|
||||
export interface Version {
|
||||
audio_locale: Locale;
|
||||
guid: string;
|
||||
|
|
|
|||
50
@types/crunchyAndroidStreams.d.ts
vendored
50
@types/crunchyAndroidStreams.d.ts
vendored
|
|
@ -5,8 +5,8 @@ export interface CrunchyAndroidStreams {
|
|||
__links__: Links;
|
||||
__actions__: Actions;
|
||||
media_id: string;
|
||||
audio_locale: string;
|
||||
subtitles: { [key: string]: Subtitle };
|
||||
audio_locale: Locale;
|
||||
subtitles: Subtitles;
|
||||
closed_captions: Actions;
|
||||
streams: Streams;
|
||||
bifs: string[];
|
||||
|
|
@ -14,6 +14,26 @@ export interface CrunchyAndroidStreams {
|
|||
captions: Actions;
|
||||
}
|
||||
|
||||
export interface Subtitles {
|
||||
'': Subtitle;
|
||||
'en-US'?: Subtitle;
|
||||
'es-LA'?: Subtitle;
|
||||
'es-419'?: Subtitle;
|
||||
'es-ES'?: Subtitle;
|
||||
'pt-BR'?: Subtitle;
|
||||
'fr-FR'?: Subtitle;
|
||||
'de-DE'?: Subtitle;
|
||||
'ar-ME'?: Subtitle;
|
||||
'ar-SA'?: Subtitle;
|
||||
'it-IT'?: Subtitle;
|
||||
'ru-RU'?: Subtitle;
|
||||
'tr-TR'?: Subtitle;
|
||||
'hi-IN'?: Subtitle;
|
||||
'zh-CN'?: Subtitle;
|
||||
'ko-KR'?: Subtitle;
|
||||
'ja-JP'?: Subtitle;
|
||||
}
|
||||
|
||||
export interface Actions {
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +50,7 @@ export interface Streams {
|
|||
}
|
||||
|
||||
export interface Download {
|
||||
hardsub_locale: string;
|
||||
hardsub_locale: Locale;
|
||||
hardsub_lang?: string;
|
||||
url: string;
|
||||
}
|
||||
|
|
@ -40,13 +60,13 @@ export interface Urls {
|
|||
}
|
||||
|
||||
export interface Subtitle {
|
||||
locale: string;
|
||||
locale: Locale;
|
||||
url: string;
|
||||
format: string;
|
||||
}
|
||||
|
||||
export interface Version {
|
||||
audio_locale: string;
|
||||
audio_locale: Locale;
|
||||
guid: string;
|
||||
original: boolean;
|
||||
variant: string;
|
||||
|
|
@ -54,3 +74,23 @@ export interface Version {
|
|||
media_guid: string;
|
||||
is_premium_only: boolean;
|
||||
}
|
||||
|
||||
export enum Locale {
|
||||
default = '',
|
||||
enUS = 'en-US',
|
||||
esLA = 'es-LA',
|
||||
es419 = 'es-419',
|
||||
esES = 'es-ES',
|
||||
ptBR = 'pt-BR',
|
||||
frFR = 'fr-FR',
|
||||
deDE = 'de-DE',
|
||||
arME = 'ar-ME',
|
||||
arSA = 'ar-SA',
|
||||
itIT = 'it-IT',
|
||||
ruRU = 'ru-RU',
|
||||
trTR = 'tr-TR',
|
||||
hiIN = 'hi-IN',
|
||||
zhCN = 'zh-CN',
|
||||
koKR = 'ko-KR',
|
||||
jaJP = 'ja-JP',
|
||||
}
|
||||
19
@types/crunchyEpisodeList.d.ts
vendored
19
@types/crunchyEpisodeList.d.ts
vendored
|
|
@ -1,3 +1,5 @@
|
|||
import { Links } from './crunchyAndroidEpisodes';
|
||||
|
||||
export interface CrunchyEpisodeList {
|
||||
total: number;
|
||||
data: CrunchyEpisode[];
|
||||
|
|
@ -41,27 +43,28 @@ export interface CrunchyEpisode {
|
|||
listing_id: string;
|
||||
episode_air_date: Date;
|
||||
slug: string;
|
||||
available_date: null;
|
||||
available_date: Date;
|
||||
subtitle_locales: Locale[];
|
||||
slug_title: string;
|
||||
available_offline: boolean;
|
||||
description: string;
|
||||
is_subbed: boolean;
|
||||
premium_date: null;
|
||||
premium_date: Date;
|
||||
upload_date: Date;
|
||||
season_slug_title: string;
|
||||
closed_captions_available: boolean;
|
||||
episode_number: number;
|
||||
season_tags: any[];
|
||||
maturity_ratings: MaturityRating[];
|
||||
streams_link: string;
|
||||
streams_link?: string;
|
||||
mature_blocked: boolean;
|
||||
is_clip: boolean;
|
||||
hd_flag: boolean;
|
||||
hide_season_title?: boolean;
|
||||
hide_season_number?: boolean;
|
||||
isSelected?: boolean;
|
||||
seq_id: string;
|
||||
hide_season_title?: boolean;
|
||||
hide_season_number?: boolean;
|
||||
isSelected?: boolean;
|
||||
seq_id: string;
|
||||
__links__?: Links;
|
||||
}
|
||||
|
||||
export enum Locale {
|
||||
|
|
@ -127,5 +130,5 @@ export interface Version {
|
|||
}
|
||||
|
||||
export interface Meta {
|
||||
versions_considered: boolean;
|
||||
versions_considered?: boolean;
|
||||
}
|
||||
6
@types/crunchyTypes.d.ts
vendored
6
@types/crunchyTypes.d.ts
vendored
|
|
@ -31,13 +31,15 @@ export type CrunchyDownloadOptions = {
|
|||
dlVideoOnce: boolean,
|
||||
skipmux?: boolean,
|
||||
syncTiming: boolean,
|
||||
apiType: 'web' | 'android'
|
||||
}
|
||||
|
||||
export type CurnchyMultiDownload = {
|
||||
export type CrunchyMultiDownload = {
|
||||
dubLang: string[],
|
||||
all?: boolean,
|
||||
but?: boolean,
|
||||
e?: string
|
||||
e?: string,
|
||||
crapi: 'web' | 'android'
|
||||
}
|
||||
|
||||
export type CrunchyMuxOptions = {
|
||||
|
|
|
|||
2
@types/playbackData.d.ts
vendored
2
@types/playbackData.d.ts
vendored
|
|
@ -1,7 +1,7 @@
|
|||
// Generated by https://quicktype.io
|
||||
export interface PlaybackData {
|
||||
total: number;
|
||||
data: { [key: string]: { [key: string]: StreamDetails } };
|
||||
data: [{ [key: string]: { [key: string]: StreamDetails } }];
|
||||
meta: Meta;
|
||||
}
|
||||
|
||||
|
|
|
|||
309
crunchy.ts
309
crunchy.ts
|
|
@ -26,21 +26,23 @@ import getKeys from './modules/cr_widevine';
|
|||
import { domain, api } from './modules/module.api-urls';
|
||||
import * as reqModule from './modules/module.req';
|
||||
import { CrunchySearch } from './@types/crunchySearch';
|
||||
//import { CrunchyEpisodeList, CrunchyEpisode } from './@types/crunchyEpisodeList';
|
||||
import { CrunchyDownloadOptions, CrunchyEpMeta, CrunchyMuxOptions, CurnchyMultiDownload, DownloadedMedia, ParseItem, SeriesSearch, SeriesSearchItem } from './@types/crunchyTypes';
|
||||
import { CrunchyEpisodeList, CrunchyEpisode } from './@types/crunchyEpisodeList';
|
||||
import { CrunchyDownloadOptions, CrunchyEpMeta, CrunchyMuxOptions, CrunchyMultiDownload, DownloadedMedia, ParseItem, SeriesSearch, SeriesSearchItem } from './@types/crunchyTypes';
|
||||
import { ObjectInfo } from './@types/objectInfo';
|
||||
import parseFileName, { Variable } from './modules/module.filename';
|
||||
//import { PlaybackData } from './@types/playbackData';
|
||||
import { PlaybackData } from './@types/playbackData';
|
||||
import { downloaded } from './modules/module.downloadArchive';
|
||||
import parseSelect from './modules/module.parseSelect';
|
||||
import { AvailableFilenameVars, getDefault } from './modules/module.args';
|
||||
import { AuthData, AuthResponse, Episode, ResponseBase, SearchData, SearchResponse, SearchResponseItem } from './@types/messageHandler';
|
||||
import { ServiceClass } from './@types/serviceClassInterface';
|
||||
import { CrunchyAndroidStreams } from './@types/crunchyAndroidStreams';
|
||||
import { CrunchyAndroidEpisodes, CrunchyEpisode } from './@types/crunchyAndroidEpisodes';
|
||||
import { CrunchyAndroidEpisodes } from './@types/crunchyAndroidEpisodes';
|
||||
import { parse } from './modules/module.transform-mpd';
|
||||
import { CrunchyAndroidObject } from './@types/crunchyAndroidObject';
|
||||
|
||||
import util from 'util';
|
||||
|
||||
export type sxItem = {
|
||||
language: langsData.LanguageItem,
|
||||
path: string,
|
||||
|
|
@ -112,7 +114,7 @@ export default class Crunchy implements ServiceClass {
|
|||
const selected = await this.downloadFromSeriesID(argv.series, { ...argv });
|
||||
if (selected.isOk) {
|
||||
for (const select of selected.value) {
|
||||
if (!(await this.downloadEpisode(select, {...argv, skipsubs: false }, true))) {
|
||||
if (!(await this.downloadEpisode(select, {...argv, skipsubs: false, apiType: argv.crapi }, true))) {
|
||||
console.error(`Unable to download selected episode ${select.episodeNumber}`);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -130,10 +132,10 @@ export default class Crunchy implements ServiceClass {
|
|||
console.info('One show can only be downloaded with one dub. Use --srz instead.');
|
||||
}
|
||||
argv.dubLang = [argv.dubLang[0]];
|
||||
const selected = await this.getSeasonById(argv.s, argv.numbers, argv.e, argv.but, argv.all);
|
||||
const selected = await this.getSeasonById(argv.s, argv.numbers, argv.e, argv.but, argv.all, argv.crapi);
|
||||
if (selected.isOk) {
|
||||
for (const select of selected.value) {
|
||||
if (!(await this.downloadEpisode(select, {...argv, skipsubs: false }))) {
|
||||
if (!(await this.downloadEpisode(select, {...argv, skipsubs: false, apiType: argv.crapi }))) {
|
||||
console.error(`Unable to download selected episode ${select.episodeNumber}`);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -143,9 +145,9 @@ export default class Crunchy implements ServiceClass {
|
|||
}
|
||||
else if(argv.e){
|
||||
await this.refreshToken();
|
||||
const selected = await this.getObjectById(argv.e, false);
|
||||
const selected = await this.getObjectById(argv.crapi, argv.e, false);
|
||||
for (const select of selected as Partial<CrunchyEpMeta>[]) {
|
||||
if (!(await this.downloadEpisode(select as CrunchyEpMeta, {...argv, skipsubs: false }))) {
|
||||
if (!(await this.downloadEpisode(select as CrunchyEpMeta, {...argv, skipsubs: false, apiType: argv.crapi }))) {
|
||||
console.error(`Unable to download selected episode ${select.episodeNumber}`);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -153,9 +155,9 @@ export default class Crunchy implements ServiceClass {
|
|||
return true;
|
||||
} else if (argv.extid) {
|
||||
await this.refreshToken();
|
||||
const selected = await this.getObjectById(argv.extid, false, true);
|
||||
const selected = await this.getObjectById(argv.crapi, argv.extid, false, true);
|
||||
for (const select of selected as Partial<CrunchyEpMeta>[]) {
|
||||
if (!(await this.downloadEpisode(select as CrunchyEpMeta, {...argv, skipsubs: false }))) {
|
||||
if (!(await this.downloadEpisode(select as CrunchyEpMeta, {...argv, skipsubs: false, apiType: argv.crapi }))) {
|
||||
console.error(`Unable to download selected episode ${select.episodeNumber}`);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -352,6 +354,7 @@ export default class Crunchy implements ServiceClass {
|
|||
this.cmsToken.cms.bucket,
|
||||
'/index?',
|
||||
new URLSearchParams({
|
||||
'preferred_audio_language': 'ja-JP',
|
||||
'Policy': this.cmsToken.cms.policy,
|
||||
'Signature': this.cmsToken.cms.signature,
|
||||
'Key-Pair-Id': this.cmsToken.cms.key_pair_id,
|
||||
|
|
@ -745,7 +748,7 @@ export default class Crunchy implements ServiceClass {
|
|||
console.info(` Total results: ${newlyAddedResults.total} (Page: ${pageCur}/${pageMax})`);
|
||||
}
|
||||
|
||||
public async getSeasonById(id: string, numbers: number, e: string|undefined, but: boolean, all: boolean) : Promise<ResponseBase<CrunchyEpMeta[]>> {
|
||||
public async getSeasonById(id: string, numbers: number, e: string|undefined, but: boolean, all: boolean, apiType: 'web' | 'android') : Promise<ResponseBase<CrunchyEpMeta[]>> {
|
||||
if(!this.cmsToken.cms){
|
||||
console.error('Authentication required!');
|
||||
return { isOk: false, reason: new Error('Authentication required') };
|
||||
|
|
@ -768,27 +771,42 @@ export default class Crunchy implements ServiceClass {
|
|||
const showInfo = JSON.parse(showInfoReq.res.body);
|
||||
this.logObject(showInfo.data[0], 0);
|
||||
|
||||
let episodeList = { total: 0, data: [], meta: {} } as CrunchyEpisodeList;
|
||||
//get episode info
|
||||
const reqEpsListOpts = [
|
||||
api.beta_cms,
|
||||
this.cmsToken.cms.bucket,
|
||||
'/episodes?',
|
||||
new URLSearchParams({
|
||||
'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);
|
||||
//const reqEpsList = await this.req.getData(`${api.cms}/seasons/${id}/episodes?preferred_audio_language=ja-JP`, 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.') };
|
||||
if (apiType == 'android') {
|
||||
const reqEpsListOpts = [
|
||||
api.beta_cms,
|
||||
this.cmsToken.cms.bucket,
|
||||
'/episodes?',
|
||||
new URLSearchParams({
|
||||
'preferred_audio_language': 'ja-JP',
|
||||
'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 = JSON.parse(reqEpsList.res.body) as CrunchyAndroidEpisodes;
|
||||
episodeList = {
|
||||
total: episodeListAndroid.total,
|
||||
data: episodeListAndroid.items,
|
||||
meta: {}
|
||||
};
|
||||
} else {
|
||||
const reqEpsList = await this.req.getData(`${api.cms}/seasons/${id}/episodes?preferred_audio_language=ja-JP`, 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 = JSON.parse(reqEpsList.res.body) as CrunchyEpisodeList;
|
||||
}
|
||||
//CrunchyEpisodeList
|
||||
const episodeList = JSON.parse(reqEpsList.res.body) as CrunchyAndroidEpisodes;
|
||||
|
||||
const epNumList: {
|
||||
ep: number[],
|
||||
|
|
@ -804,7 +822,7 @@ export default class Crunchy implements ServiceClass {
|
|||
const doEpsFilter = parseSelect(e as string);
|
||||
const selectedMedia: CrunchyEpMeta[] = [];
|
||||
|
||||
episodeList.items.forEach((item) => {
|
||||
episodeList.data.forEach((item) => {
|
||||
item.hide_season_title = true;
|
||||
if(item.season_title == '' && item.series_title != ''){
|
||||
item.season_title = item.series_title;
|
||||
|
|
@ -853,12 +871,18 @@ export default class Crunchy implements ServiceClass {
|
|||
image: images[Math.floor(images.length / 2)].source
|
||||
};
|
||||
// Check for streams_link and update playback var if needed
|
||||
if (item.__links__.streams.href) {
|
||||
if (item.__links__?.streams.href) {
|
||||
epMeta.data[0].playback = item.__links__.streams.href;
|
||||
if(!item.playback) {
|
||||
item.playback = item.__links__.streams.href;
|
||||
}
|
||||
}
|
||||
if (item.streams_link) {
|
||||
epMeta.data[0].playback = item.streams_link;
|
||||
if(!item.playback) {
|
||||
item.playback = item.streams_link;
|
||||
}
|
||||
}
|
||||
if (item.versions) {
|
||||
epMeta.data[0].versions = item.versions;
|
||||
}
|
||||
|
|
@ -906,7 +930,7 @@ export default class Crunchy implements ServiceClass {
|
|||
return true;
|
||||
}
|
||||
|
||||
public async getObjectById(e?: string, earlyReturn?: boolean, external_id?: boolean): Promise<ObjectInfo|Partial<CrunchyEpMeta>[]|undefined> {
|
||||
public async getObjectById(apiType: 'web' | 'android', e?: string, earlyReturn?: boolean, external_id?: boolean): Promise<ObjectInfo|Partial<CrunchyEpMeta>[]|undefined> {
|
||||
if(!this.cmsToken.cms){
|
||||
console.error('Authentication required!');
|
||||
return [];
|
||||
|
|
@ -923,6 +947,7 @@ export default class Crunchy implements ServiceClass {
|
|||
'/channels/crunchyroll/objects',
|
||||
'?',
|
||||
new URLSearchParams({
|
||||
'preferred_audio_language': 'ja-JP',
|
||||
'external_id': ob,
|
||||
'Policy': this.cmsToken.cms.policy,
|
||||
'Signature': this.cmsToken.cms.signature,
|
||||
|
|
@ -965,38 +990,53 @@ export default class Crunchy implements ServiceClass {
|
|||
};
|
||||
|
||||
// reqs
|
||||
const objectReqOpts = [
|
||||
api.beta_cms,
|
||||
this.cmsToken.cms.bucket,
|
||||
'/objects/',
|
||||
doEpsFilter.values.join(','),
|
||||
'?',
|
||||
new URLSearchParams({
|
||||
'Policy': this.cmsToken.cms.policy,
|
||||
'Signature': this.cmsToken.cms.signature,
|
||||
'Key-Pair-Id': this.cmsToken.cms.key_pair_id,
|
||||
}),
|
||||
].join('');
|
||||
const objectReq = await this.req.getData(objectReqOpts, AuthHeaders);
|
||||
//const objectReq = await this.req.getData(`${api.cms}/objects/${doEpsFilter.values.join(',')}?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||
if(!objectReq.ok || !objectReq.res){
|
||||
console.error('Objects Request FAILED!');
|
||||
if(objectReq.error && objectReq.error.res && objectReq.error.res.body){
|
||||
const objectInfo = JSON.parse(objectReq.error.res.body as string);
|
||||
console.info('Body:', JSON.stringify(objectInfo, null, '\t'));
|
||||
objectInfo.error = true;
|
||||
return objectInfo;
|
||||
let objectInfo: ObjectInfo = { total: 0, data: [], meta: {} };
|
||||
if (apiType == 'android') {
|
||||
const objectReqOpts = [
|
||||
api.beta_cms,
|
||||
this.cmsToken.cms.bucket,
|
||||
'/objects/',
|
||||
doEpsFilter.values.join(','),
|
||||
'?',
|
||||
new URLSearchParams({
|
||||
'preferred_audio_language': 'ja-JP',
|
||||
'Policy': this.cmsToken.cms.policy,
|
||||
'Signature': this.cmsToken.cms.signature,
|
||||
'Key-Pair-Id': this.cmsToken.cms.key_pair_id,
|
||||
}),
|
||||
].join('');
|
||||
const objectReq = await this.req.getData(objectReqOpts, AuthHeaders);
|
||||
if(!objectReq.ok || !objectReq.res){
|
||||
console.error('Objects Request FAILED!');
|
||||
if(objectReq.error && objectReq.error.res && objectReq.error.res.body){
|
||||
const objectInfo = JSON.parse(objectReq.error.res.body as string);
|
||||
console.info('Body:', JSON.stringify(objectInfo, null, '\t'));
|
||||
objectInfo.error = true;
|
||||
return objectInfo;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
const objectInfoAndroid = JSON.parse(objectReq.res.body) as CrunchyAndroidObject;
|
||||
objectInfo = {
|
||||
total: objectInfoAndroid.total,
|
||||
data: objectInfoAndroid.items,
|
||||
meta: {}
|
||||
};
|
||||
} else {
|
||||
const objectReq = await this.req.getData(`${api.cms}/objects/${doEpsFilter.values.join(',')}?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||
if(!objectReq.ok || !objectReq.res){
|
||||
console.error('Objects Request FAILED!');
|
||||
if(objectReq.error && objectReq.error.res && objectReq.error.res.body){
|
||||
const objectInfo = JSON.parse(objectReq.error.res.body as string);
|
||||
console.info('Body:', JSON.stringify(objectInfo, null, '\t'));
|
||||
objectInfo.error = true;
|
||||
return objectInfo;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
objectInfo = JSON.parse(objectReq.res.body) as ObjectInfo;
|
||||
}
|
||||
|
||||
//const objectInfo = JSON.parse(objectReq.res.body) as ObjectInfo;
|
||||
const objectInfoAndroid = JSON.parse(objectReq.res.body) as CrunchyAndroidObject;
|
||||
const objectInfo: ObjectInfo = {
|
||||
total: objectInfoAndroid.total,
|
||||
data: objectInfoAndroid.items,
|
||||
meta: {}
|
||||
};
|
||||
if(earlyReturn){
|
||||
return objectInfo;
|
||||
}
|
||||
|
|
@ -1134,29 +1174,15 @@ export default class Crunchy implements ServiceClass {
|
|||
mediaId = mediaId.split(':')[1];
|
||||
|
||||
// /cms/v2/BUCKET/crunchyroll/videos/MEDIAID/streams
|
||||
const videoStreamsReq = [
|
||||
api.beta_cms,
|
||||
`${this.cmsToken.cms.bucket}/videos/${mediaId}/streams`,
|
||||
'?',
|
||||
new URLSearchParams({
|
||||
streams: 'all',
|
||||
textType: 'all',
|
||||
'Policy': this.cmsToken.cms.policy,
|
||||
'Signature': this.cmsToken.cms.signature,
|
||||
'Key-Pair-Id': this.cmsToken.cms.key_pair_id,
|
||||
}),
|
||||
].join('');
|
||||
|
||||
let playbackReq = await this.req.getData(videoStreamsReq as string, AuthHeaders);
|
||||
//console.info(playbackReq.res.body);
|
||||
//let playbackReq = await this.req.getData(`${api.cms}/videos/${mediaId}/streams`, AuthHeaders);
|
||||
if(!playbackReq.ok || !playbackReq.res){
|
||||
console.error('Request Stream URLs FAILED! Attempting fallback');
|
||||
let pbData = { total: 0, data: {}, meta: {} } as PlaybackData;
|
||||
if (options.apiType == 'android') {
|
||||
const videoStreamsReq = [
|
||||
domain.api_beta,
|
||||
mMeta.playback,
|
||||
api.beta_cms,
|
||||
`${this.cmsToken.cms.bucket}/videos/${mediaId}/streams`,
|
||||
'?',
|
||||
new URLSearchParams({
|
||||
'preferred_audio_language': 'ja-JP',
|
||||
streams: 'all',
|
||||
textType: 'all',
|
||||
'Policy': this.cmsToken.cms.policy,
|
||||
|
|
@ -1164,16 +1190,42 @@ export default class Crunchy implements ServiceClass {
|
|||
'Key-Pair-Id': this.cmsToken.cms.key_pair_id,
|
||||
}),
|
||||
].join('');
|
||||
playbackReq = await this.req.getData(videoStreamsReq as string, AuthHeaders);
|
||||
//playbackReq = await this.req.getData(`${domain.api_beta}${mMeta.playback}`, AuthHeaders);
|
||||
if(!playbackReq.ok || !playbackReq.res){
|
||||
console.error('Fallback Request Stream URLs FAILED!');
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
//const pbData = JSON.parse(playbackReq.res.body) as PlaybackData;
|
||||
const pbData = JSON.parse(playbackReq.res.body) as CrunchyAndroidStreams;
|
||||
let playbackReq = await this.req.getData(videoStreamsReq as string, AuthHeaders);
|
||||
if(!playbackReq.ok || !playbackReq.res){
|
||||
console.error('Request Stream URLs FAILED! Attempting fallback');
|
||||
playbackReq = await this.req.getData(videoStreamsReq as string, AuthHeaders);
|
||||
if(!playbackReq.ok || !playbackReq.res){
|
||||
console.error('Fallback Request Stream URLs FAILED!');
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
const pbDataAndroid = JSON.parse(playbackReq.res.body) as CrunchyAndroidStreams;
|
||||
pbData = {
|
||||
total: 0,
|
||||
data: [pbDataAndroid.streams],
|
||||
meta: {
|
||||
audio_locale: pbDataAndroid.audio_locale,
|
||||
bifs: pbDataAndroid.bifs,
|
||||
captions: pbDataAndroid.captions,
|
||||
closed_captions: pbDataAndroid.closed_captions,
|
||||
media_id: pbDataAndroid.media_id,
|
||||
subtitles: pbDataAndroid.subtitles,
|
||||
versions: pbDataAndroid.versions
|
||||
}
|
||||
};
|
||||
} else {
|
||||
let playbackReq = await this.req.getData(`${api.cms}/videos/${mediaId}/streams`, AuthHeaders);
|
||||
if(!playbackReq.ok || !playbackReq.res){
|
||||
console.error('Request Stream URLs FAILED! Attempting fallback');
|
||||
playbackReq = await this.req.getData(`${domain.api_beta}${mMeta.playback}`, AuthHeaders);
|
||||
if(!playbackReq.ok || !playbackReq.res){
|
||||
console.error('Fallback Request Stream URLs FAILED!');
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
pbData = JSON.parse(playbackReq.res.body) as PlaybackData;
|
||||
}
|
||||
|
||||
variables.push(...([
|
||||
['title', medias.episodeTitle, true],
|
||||
|
|
@ -1193,11 +1245,11 @@ export default class Crunchy implements ServiceClass {
|
|||
|
||||
let streams: any[] = [];
|
||||
let hsLangs: string[] = [];
|
||||
const pbStreams = pbData.streams;
|
||||
const pbStreams = pbData.data[0];
|
||||
|
||||
for(const s of Object.keys(pbStreams)){
|
||||
//if(s.match(/hls/) && !s.match(/drm/) && !s.match(/trailer/)) {
|
||||
if((s.match(/hls/) || s.match(/dash/)) && !s.match(/trailer/)) {
|
||||
if((s.match(/hls/) || s.match(/dash/)) && !(s.match(/hls/) && s.match(/drm/)) && !s.match(/trailer/)) {
|
||||
const pb = Object.values(pbStreams[s]).map(v => {
|
||||
v.hardsub_lang = v.hardsub_locale
|
||||
? langsData.fixAndFindCrLC(v.hardsub_locale).locale
|
||||
|
|
@ -1219,7 +1271,7 @@ export default class Crunchy implements ServiceClass {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const audDub = langsData.findLang(langsData.fixLanguageTag(pbData.audio_locale as string) || '').code;
|
||||
const audDub = langsData.findLang(langsData.fixLanguageTag(pbData.meta.audio_locale as string) || '').code;
|
||||
hsLangs = langsData.sortTags(hsLangs);
|
||||
|
||||
streams = streams.map((s) => {
|
||||
|
|
@ -1738,8 +1790,8 @@ export default class Crunchy implements ServiceClass {
|
|||
}
|
||||
|
||||
if(!options.skipsubs && options.dlsubs.indexOf('none') == -1){
|
||||
if(pbData.subtitles && Object.values(pbData.subtitles).length > 0){
|
||||
const subsData = Object.values(pbData.subtitles);
|
||||
if(pbData.meta.subtitles && Object.values(pbData.meta.subtitles).length > 0){
|
||||
const subsData = Object.values(pbData.meta.subtitles);
|
||||
const subsDataMapped = subsData.map((s) => {
|
||||
const subLang = langsData.fixAndFindCrLC(s.locale);
|
||||
return {
|
||||
|
|
@ -1880,7 +1932,7 @@ export default class Crunchy implements ServiceClass {
|
|||
merger.cleanUp();
|
||||
}
|
||||
|
||||
public async listSeriesID(id: string): Promise<{ list: Episode[], data: Record<string, {
|
||||
public async listSeriesID(id: string, apiType: 'android' | 'web'): Promise<{ list: Episode[], data: Record<string, {
|
||||
items: CrunchyEpisode[];
|
||||
langs: langsData.LanguageItem[];
|
||||
}>}> {
|
||||
|
|
@ -1897,7 +1949,7 @@ export default class Crunchy implements ServiceClass {
|
|||
for(const season of Object.keys(result) as unknown as number[]) {
|
||||
for (const key of Object.keys(result[season])) {
|
||||
const s = result[season][key];
|
||||
(await this.getSeasonDataById(s))?.items?.forEach(episode => {
|
||||
(await this.getSeasonDataById(s, apiType))?.data?.forEach(episode => {
|
||||
//TODO: Make sure the below code is ok
|
||||
//Prepare the episode array
|
||||
let item;
|
||||
|
|
@ -1987,8 +2039,8 @@ export default class Crunchy implements ServiceClass {
|
|||
})};
|
||||
}
|
||||
|
||||
public async downloadFromSeriesID(id: string, data: CurnchyMultiDownload) : Promise<ResponseBase<CrunchyEpMeta[]>> {
|
||||
const { data: episodes } = await this.listSeriesID(id);
|
||||
public async downloadFromSeriesID(id: string, data: CrunchyMultiDownload) : Promise<ResponseBase<CrunchyEpMeta[]>> {
|
||||
const { data: episodes } = await this.listSeriesID(id, data.crapi);
|
||||
console.info('');
|
||||
console.info('-'.repeat(30));
|
||||
console.info('');
|
||||
|
|
@ -2133,7 +2185,7 @@ export default class Crunchy implements ServiceClass {
|
|||
return seasonsList;
|
||||
}
|
||||
|
||||
public async getSeasonDataById(item: SeriesSearchItem, log = false){
|
||||
public async getSeasonDataById(item: SeriesSearchItem, apiType: 'android' | 'web', log = false){
|
||||
if(!this.cmsToken.cms){
|
||||
console.error('Authentication required!');
|
||||
return;
|
||||
|
|
@ -2155,28 +2207,43 @@ export default class Crunchy implements ServiceClass {
|
|||
const showInfo = JSON.parse(showInfoReq.res.body);
|
||||
if (log)
|
||||
this.logObject(showInfo, 0);
|
||||
|
||||
let episodeList = { total: 0, data: [], meta: {} } as CrunchyEpisodeList;
|
||||
//get episode info
|
||||
|
||||
const reqEpsListOpts = [
|
||||
api.beta_cms,
|
||||
this.cmsToken.cms.bucket,
|
||||
'/episodes?',
|
||||
new URLSearchParams({
|
||||
'season_id': item.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);
|
||||
//const reqEpsList = await this.req.getData(`${api.cms}/seasons/${item.id}/episodes?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||
if(!reqEpsList.ok || !reqEpsList.res){
|
||||
console.error('Episode List Request FAILED!');
|
||||
return;
|
||||
if (apiType == 'android') {
|
||||
const reqEpsListOpts = [
|
||||
api.beta_cms,
|
||||
this.cmsToken.cms.bucket,
|
||||
'/episodes?',
|
||||
new URLSearchParams({
|
||||
'preferred_audio_language': 'ja-JP',
|
||||
'season_id': item.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;
|
||||
}
|
||||
//CrunchyEpisodeList
|
||||
const episodeListAndroid = JSON.parse(reqEpsList.res.body) as CrunchyAndroidEpisodes;
|
||||
episodeList = {
|
||||
total: episodeListAndroid.total,
|
||||
data: episodeListAndroid.items,
|
||||
meta: {}
|
||||
};
|
||||
} else {
|
||||
const reqEpsList = await this.req.getData(`${api.cms}/seasons/${item.id}/episodes?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||
if(!reqEpsList.ok || !reqEpsList.res){
|
||||
console.error('Episode List Request FAILED!');
|
||||
return;
|
||||
}
|
||||
//CrunchyEpisodeList
|
||||
episodeList = JSON.parse(reqEpsList.res.body) as CrunchyEpisodeList;
|
||||
}
|
||||
//CrunchyEpisodeList
|
||||
const episodeList = JSON.parse(reqEpsList.res.body) as CrunchyAndroidEpisodes;
|
||||
|
||||
if(episodeList.total < 1){
|
||||
console.info(' Season is empty!');
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ let argvC: {
|
|||
_: (string | number)[];
|
||||
$0: string;
|
||||
dlVideoOnce: boolean;
|
||||
crapi: 'android' | 'web';
|
||||
removeBumpers: boolean;
|
||||
originalFontSize: boolean;
|
||||
keepAllVideos: boolean;
|
||||
|
|
|
|||
|
|
@ -203,6 +203,20 @@ const args: TAppArg<boolean|number|string|unknown[]>[] = [
|
|||
default: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'crapi',
|
||||
describe: 'Selects the API type for Crunchyroll',
|
||||
type: 'string',
|
||||
group: 'dl',
|
||||
service: ['crunchy'],
|
||||
docDescribe: 'If set to Android, it has lower quality, but Non-DRM streams,'
|
||||
+ '\nIf set to Web, it has a higher quality adaptive stream, but everything is DRM.',
|
||||
usage: '',
|
||||
choices: ['android', 'web'],
|
||||
default: {
|
||||
default: 'android'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'removeBumpers',
|
||||
describe: 'Remove bumpers from final video',
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
"@babel/core": "^7.22.9",
|
||||
"@babel/plugin-syntax-flow": "^7.22.5",
|
||||
"@babel/plugin-transform-react-jsx": "^7.22.5",
|
||||
"@types/xmldom": "^0.1.34",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
|
|
@ -65,6 +66,7 @@
|
|||
"sei-helper": "^3.3.0",
|
||||
"typescript-eslint": "0.0.1-alpha.0",
|
||||
"ws": "^8.13.0",
|
||||
"xmldom": "^0.6.0",
|
||||
"yaml": "^2.3.1",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ dependencies:
|
|||
'@babel/plugin-transform-react-jsx':
|
||||
specifier: ^7.22.5
|
||||
version: 7.22.5(@babel/core@7.22.9)
|
||||
'@types/xmldom':
|
||||
specifier: ^0.1.34
|
||||
version: 0.1.34
|
||||
cheerio:
|
||||
specifier: 1.0.0-rc.12
|
||||
version: 1.0.0-rc.12
|
||||
|
|
@ -74,6 +77,9 @@ dependencies:
|
|||
ws:
|
||||
specifier: ^8.13.0
|
||||
version: 8.13.0
|
||||
xmldom:
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0
|
||||
yaml:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1
|
||||
|
|
@ -1982,6 +1988,10 @@ packages:
|
|||
'@types/node': 18.15.11
|
||||
dev: true
|
||||
|
||||
/@types/xmldom@0.1.34:
|
||||
resolution: {integrity: sha512-7eZFfxI9XHYjJJuugddV6N5YNeXgQE1lArWOcd1eCOKWb/FGs5SIjacSYuEJuwhsGS3gy4RuZ5EUIcqYscuPDA==}
|
||||
dev: false
|
||||
|
||||
/@types/yargs-parser@21.0.0:
|
||||
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
|
||||
dev: true
|
||||
|
|
@ -5681,6 +5691,11 @@ packages:
|
|||
optional: true
|
||||
dev: false
|
||||
|
||||
/xmldom@0.6.0:
|
||||
resolution: {integrity: sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dev: false
|
||||
|
||||
/y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
|
|||
Loading…
Reference in a new issue