[HD] Initial support for new Hidive API
The new API can be accessed with `--hdapi new`
This commit is contained in:
parent
5e95f600d9
commit
1c39e349ad
17 changed files with 1315 additions and 135 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -24,6 +24,7 @@ cr_token.yml
|
||||||
hd_profile.yml
|
hd_profile.yml
|
||||||
hd_sess.yml
|
hd_sess.yml
|
||||||
hd_token.yml
|
hd_token.yml
|
||||||
|
hd_new_token.yml
|
||||||
archive.json
|
archive.json
|
||||||
guistate.json
|
guistate.json
|
||||||
fonts
|
fonts
|
||||||
|
|
|
||||||
43
@types/newHidiveEpisode.d.ts
vendored
Normal file
43
@types/newHidiveEpisode.d.ts
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
export interface NewHidiveEpisode {
|
||||||
|
description: string;
|
||||||
|
duration: number;
|
||||||
|
title: string;
|
||||||
|
categories: string[];
|
||||||
|
contentDownload: ContentDownload;
|
||||||
|
favourite: boolean;
|
||||||
|
subEvents: any[];
|
||||||
|
thumbnailUrl: string;
|
||||||
|
longDescription: string;
|
||||||
|
posterUrl: string;
|
||||||
|
offlinePlaybackLanguages: string[];
|
||||||
|
externalAssetId: string;
|
||||||
|
maxHeight: number;
|
||||||
|
rating: Rating;
|
||||||
|
episodeInformation: EpisodeInformation;
|
||||||
|
id: number;
|
||||||
|
accessLevel: string;
|
||||||
|
playerUrlCallback: string;
|
||||||
|
thumbnailsPreview: string;
|
||||||
|
displayableTags: any[];
|
||||||
|
plugins: any[];
|
||||||
|
watchStatus: string;
|
||||||
|
computedReleases: any[];
|
||||||
|
licences: any[];
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContentDownload {
|
||||||
|
permission: string;
|
||||||
|
period: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EpisodeInformation {
|
||||||
|
seasonNumber: number;
|
||||||
|
episodeNumber: number;
|
||||||
|
season: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Rating {
|
||||||
|
rating: string;
|
||||||
|
descriptors: any[];
|
||||||
|
}
|
||||||
33
@types/newHidivePlayback.d.ts
vendored
Normal file
33
@types/newHidivePlayback.d.ts
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
export interface NewHidivePlayback {
|
||||||
|
watermark: null;
|
||||||
|
skipMarkers: any[];
|
||||||
|
annotations: null;
|
||||||
|
dash: Format[];
|
||||||
|
hls: Format[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Format {
|
||||||
|
subtitles: Subtitle[];
|
||||||
|
url: string;
|
||||||
|
drm: DRM;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DRM {
|
||||||
|
encryptionMode: string;
|
||||||
|
containerType: string;
|
||||||
|
jwtToken: string;
|
||||||
|
url: string;
|
||||||
|
keySystems: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Subtitle {
|
||||||
|
format: Formats;
|
||||||
|
language: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Formats {
|
||||||
|
Scc = 'scc',
|
||||||
|
Srt = 'srt',
|
||||||
|
Vtt = 'vtt',
|
||||||
|
}
|
||||||
91
@types/newHidiveSearch.d.ts
vendored
Normal file
91
@types/newHidiveSearch.d.ts
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
export interface NewHidiveSearch {
|
||||||
|
results: Result[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Result {
|
||||||
|
hits: Hit[];
|
||||||
|
nbHits: number;
|
||||||
|
page: number;
|
||||||
|
nbPages: number;
|
||||||
|
hitsPerPage: number;
|
||||||
|
exhaustiveNbHits: boolean;
|
||||||
|
exhaustiveTypo: boolean;
|
||||||
|
exhaustive: Exhaustive;
|
||||||
|
query: string;
|
||||||
|
params: string;
|
||||||
|
index: string;
|
||||||
|
renderingContent: RenderingContent;
|
||||||
|
processingTimeMS: number;
|
||||||
|
processingTimingsMS: ProcessingTimingsMS;
|
||||||
|
serverTimeMS: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Exhaustive {
|
||||||
|
nbHits: boolean;
|
||||||
|
typo: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Hit {
|
||||||
|
type: string;
|
||||||
|
weight: number;
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
meta: RenderingContent;
|
||||||
|
coverUrl: string;
|
||||||
|
smallCoverUrl: string;
|
||||||
|
seasonsCount: number;
|
||||||
|
tags: string[];
|
||||||
|
localisations: HitLocalisations;
|
||||||
|
ratings: Ratings;
|
||||||
|
objectID: string;
|
||||||
|
_highlightResult: HighlightResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HighlightResult {
|
||||||
|
name: Description;
|
||||||
|
description: Description;
|
||||||
|
tags: Description[];
|
||||||
|
localisations: HighlightResultLocalisations;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Description {
|
||||||
|
value: string;
|
||||||
|
matchLevel: string;
|
||||||
|
matchedWords: string[];
|
||||||
|
fullyHighlighted?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HighlightResultLocalisations {
|
||||||
|
en_US: PurpleEnUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurpleEnUS {
|
||||||
|
title: Description;
|
||||||
|
description: Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HitLocalisations {
|
||||||
|
[language: string]: HitLocalization;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HitLocalization {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RenderingContent {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Ratings {
|
||||||
|
US: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProcessingTimingsMS {
|
||||||
|
_request: Request;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Request {
|
||||||
|
queue: number;
|
||||||
|
roundTrip: number;
|
||||||
|
}
|
||||||
85
@types/newHidiveSeason.d.ts
vendored
Normal file
85
@types/newHidiveSeason.d.ts
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
export interface NewHidiveSeason {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
longDescription: string;
|
||||||
|
smallCoverUrl: string;
|
||||||
|
coverUrl: string;
|
||||||
|
titleUrl: string;
|
||||||
|
posterUrl: string;
|
||||||
|
seasonNumber: number;
|
||||||
|
episodeCount: number;
|
||||||
|
displayableTags: any[];
|
||||||
|
rating: Rating;
|
||||||
|
contentRating: Rating;
|
||||||
|
id: number;
|
||||||
|
series: Series;
|
||||||
|
episodes: Episode[];
|
||||||
|
paging: Paging;
|
||||||
|
licences: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Rating {
|
||||||
|
rating: string;
|
||||||
|
descriptors: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Episode {
|
||||||
|
accessLevel: string;
|
||||||
|
availablePurchases: any[];
|
||||||
|
licenceIds: any[];
|
||||||
|
type: string;
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
thumbnailUrl: string;
|
||||||
|
posterUrl: string;
|
||||||
|
duration: number;
|
||||||
|
favourite: boolean;
|
||||||
|
contentDownload: ContentDownload;
|
||||||
|
offlinePlaybackLanguages: string[];
|
||||||
|
externalAssetId: string;
|
||||||
|
subEvents: any[];
|
||||||
|
maxHeight: number;
|
||||||
|
thumbnailsPreview: string;
|
||||||
|
longDescription: string;
|
||||||
|
episodeInformation: EpisodeInformation;
|
||||||
|
categories: string[];
|
||||||
|
displayableTags: any[];
|
||||||
|
watchStatus: string;
|
||||||
|
computedReleases: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContentDownload {
|
||||||
|
permission: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EpisodeInformation {
|
||||||
|
seasonNumber: number;
|
||||||
|
episodeNumber: number;
|
||||||
|
season: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Paging {
|
||||||
|
moreDataAvailable: boolean;
|
||||||
|
lastSeen: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Series {
|
||||||
|
seriesId: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
longDescription: string;
|
||||||
|
displayableTags: any[];
|
||||||
|
rating: Rating;
|
||||||
|
contentRating: Rating;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NewHidiveEpisodeExtra extends Episode {
|
||||||
|
titleId: number;
|
||||||
|
nameLong: string;
|
||||||
|
seasonTitle: string;
|
||||||
|
seriesTitle: string;
|
||||||
|
seriesId?: number;
|
||||||
|
isSelected: boolean;
|
||||||
|
jwtToken?: string;
|
||||||
|
}
|
||||||
35
@types/newHidiveSeries.d.ts
vendored
Normal file
35
@types/newHidiveSeries.d.ts
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
export interface NewHidiveSeries {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
longDescription: string;
|
||||||
|
smallCoverUrl: string;
|
||||||
|
coverUrl: string;
|
||||||
|
titleUrl: string;
|
||||||
|
posterUrl: string;
|
||||||
|
seasons: Season[];
|
||||||
|
rating: Rating;
|
||||||
|
contentRating: Rating;
|
||||||
|
displayableTags: any[];
|
||||||
|
paging: Paging;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Rating {
|
||||||
|
rating: string;
|
||||||
|
descriptors: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Paging {
|
||||||
|
moreDataAvailable: boolean;
|
||||||
|
lastSeen: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Season {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
longDescription: string;
|
||||||
|
seasonNumber: number;
|
||||||
|
episodeCount: number;
|
||||||
|
displayableTags: any[];
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ const ServiceProvider: FCWithChildren = ({ children }) => {
|
||||||
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
|
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
|
||||||
<Button size='large' variant="contained" onClick={() => setService('funi')} startIcon={<Avatar src={'https://static.funimation.com/static/img/favicon.ico'} />}>Funimation</Button>
|
<Button size='large' variant="contained" onClick={() => setService('funi')} startIcon={<Avatar src={'https://static.funimation.com/static/img/favicon.ico'} />}>Funimation</Button>
|
||||||
<Button size='large' variant="contained" onClick={() => setService('crunchy')} startIcon={<Avatar src={'https://static.crunchyroll.com/cxweb/assets/img/favicons/favicon-32x32.png'} />}>Crunchyroll</Button>
|
<Button size='large' variant="contained" onClick={() => setService('crunchy')} startIcon={<Avatar src={'https://static.crunchyroll.com/cxweb/assets/img/favicons/favicon-32x32.png'} />}>Crunchyroll</Button>
|
||||||
<Button size='large' variant="contained" onClick={() => setService('hidive')} startIcon={<Avatar src={'https://www.hidive.com/favicon.ico'} />}>Hidive</Button>
|
<Button size='large' variant="contained" onClick={() => setService('hidive')} startIcon={<Avatar src={'https://static.diceplatform.com/prod/original/dce.hidive/settings/HIDIVE_AppLogo_1024x1024.0G0vK.jpg'} />}>Hidive</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
: <serviceContext.Provider value={service}>
|
: <serviceContext.Provider value={service}>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,13 @@ class HidiveHandler extends Base implements MessageHandler {
|
||||||
return { isOk: true, value: undefined };
|
return { isOk: true, value: undefined };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getAPIVersion() {
|
||||||
|
const _default = yargs.appArgv(this.hidive.cfg.cli, true);
|
||||||
|
this.hidive.api = _default.hdapi;
|
||||||
|
}
|
||||||
|
|
||||||
public async search(data: SearchData): Promise<SearchResponse> {
|
public async search(data: SearchData): Promise<SearchResponse> {
|
||||||
|
await this.getAPIVersion();
|
||||||
console.debug(`Got search options: ${JSON.stringify(data)}`);
|
console.debug(`Got search options: ${JSON.stringify(data)}`);
|
||||||
const hidiveSearch = await this.hidive.doSearch(data);
|
const hidiveSearch = await this.hidive.doSearch(data);
|
||||||
if (!hidiveSearch.isOk) {
|
if (!hidiveSearch.isOk) {
|
||||||
|
|
@ -42,7 +48,7 @@ class HidiveHandler extends Base implements MessageHandler {
|
||||||
public async availableDubCodes(): Promise<string[]> {
|
public async availableDubCodes(): Promise<string[]> {
|
||||||
const dubLanguageCodesArray: string[] = [];
|
const dubLanguageCodesArray: string[] = [];
|
||||||
for(const language of languages){
|
for(const language of languages){
|
||||||
if (language.hd_locale)
|
if (language.new_hd_locale)
|
||||||
dubLanguageCodesArray.push(language.code);
|
dubLanguageCodesArray.push(language.code);
|
||||||
}
|
}
|
||||||
return [...new Set(dubLanguageCodesArray)];
|
return [...new Set(dubLanguageCodesArray)];
|
||||||
|
|
@ -51,7 +57,7 @@ class HidiveHandler extends Base implements MessageHandler {
|
||||||
public async availableSubCodes(): Promise<string[]> {
|
public async availableSubCodes(): Promise<string[]> {
|
||||||
const subLanguageCodesArray: string[] = [];
|
const subLanguageCodesArray: string[] = [];
|
||||||
for(const language of languages){
|
for(const language of languages){
|
||||||
if (language.hd_locale)
|
if (language.new_hd_locale)
|
||||||
subLanguageCodesArray.push(language.locale);
|
subLanguageCodesArray.push(language.locale);
|
||||||
}
|
}
|
||||||
return ['all', 'none', ...new Set(subLanguageCodesArray)];
|
return ['all', 'none', ...new Set(subLanguageCodesArray)];
|
||||||
|
|
@ -62,63 +68,120 @@ class HidiveHandler extends Base implements MessageHandler {
|
||||||
if (isNaN(parse) || parse <= 0)
|
if (isNaN(parse) || parse <= 0)
|
||||||
return false;
|
return false;
|
||||||
console.debug(`Got resolve options: ${JSON.stringify(data)}`);
|
console.debug(`Got resolve options: ${JSON.stringify(data)}`);
|
||||||
const res = await this.hidive.getShow(parseInt(data.id), data.e, data.but, data.all);
|
await this.getAPIVersion();
|
||||||
if (!res.isOk || !res.value)
|
if (this.hidive.api == 'old') {
|
||||||
return res.isOk;
|
const res = await this.hidive.getShow(parseInt(data.id), data.e, data.but, data.all);
|
||||||
this.addToQueue(res.value.map(item => {
|
if (!res.isOk || !res.value)
|
||||||
return {
|
return res.isOk;
|
||||||
...data,
|
this.addToQueue(res.value.map(item => {
|
||||||
ids: [item.Id],
|
return {
|
||||||
title: item.Name,
|
...data,
|
||||||
parent: {
|
ids: [item.Id],
|
||||||
title: item.seriesTitle,
|
title: item.Name,
|
||||||
season: parseFloat(item.SeasonNumberValue+'')+''
|
parent: {
|
||||||
},
|
title: item.seriesTitle,
|
||||||
image: item.ScreenShotSmallUrl,
|
season: parseFloat(item.SeasonNumberValue+'')+''
|
||||||
e: parseFloat(item.EpisodeNumberValue+'')+'',
|
},
|
||||||
episode: parseFloat(item.EpisodeNumberValue+'')+'',
|
image: item.ScreenShotSmallUrl,
|
||||||
};
|
e: parseFloat(item.EpisodeNumberValue+'')+'',
|
||||||
}));
|
episode: parseFloat(item.EpisodeNumberValue+'')+'',
|
||||||
return true;
|
};
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
const res = await this.hidive.selectSeries(parseInt(data.id), data.e, data.but, data.all);
|
||||||
|
if (!res.isOk || !res.value)
|
||||||
|
return res.isOk;
|
||||||
|
this.addToQueue(res.value.map(item => {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
ids: [item.id],
|
||||||
|
title: item.title,
|
||||||
|
parent: {
|
||||||
|
title: item.seriesTitle,
|
||||||
|
season: item.episodeInformation.seasonNumber+''
|
||||||
|
},
|
||||||
|
image: item.thumbnailUrl,
|
||||||
|
e: item.episodeInformation.episodeNumber+'',
|
||||||
|
episode: item.episodeInformation.episodeNumber+'',
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async listEpisodes(id: string): Promise<EpisodeListResponse> {
|
public async listEpisodes(id: string): Promise<EpisodeListResponse> {
|
||||||
const parse = parseInt(id);
|
const parse = parseInt(id);
|
||||||
if (isNaN(parse) || parse <= 0)
|
if (isNaN(parse) || parse <= 0)
|
||||||
return { isOk: false, reason: new Error('The ID is invalid') };
|
return { isOk: false, reason: new Error('The ID is invalid') };
|
||||||
const request = await this.hidive.listShow(parse);
|
|
||||||
if (!request.isOk || !request.value)
|
|
||||||
return {isOk: false, reason: new Error('Unknown upstream error, check for additional logs')};
|
|
||||||
|
|
||||||
return { isOk: true, value: request.value.Episodes.map(function(item) {
|
await this.getAPIVersion();
|
||||||
const language = item.Summary.match(/^Audio: (.*)/m);
|
if (this.hidive.api == 'old') {
|
||||||
language?.shift();
|
const request = await this.hidive.listShow(parse);
|
||||||
const description = item.Summary.split('\r\n');
|
if (!request.isOk || !request.value)
|
||||||
return {
|
return {isOk: false, reason: new Error('Unknown upstream error, check for additional logs')};
|
||||||
e: parseFloat(item.EpisodeNumberValue+'')+'',
|
|
||||||
lang: language ? language[0].split(', ') : [],
|
return { isOk: true, value: request.value.Episodes.map(function(item) {
|
||||||
name: item.Name,
|
const language = item.Summary.match(/^Audio: (.*)/m);
|
||||||
season: parseFloat(item.SeasonNumberValue+'')+'',
|
language?.shift();
|
||||||
seasonTitle: request.value.Name,
|
const description = item.Summary.split('\r\n');
|
||||||
episode: parseFloat(item.EpisodeNumberValue+'')+'',
|
return {
|
||||||
id: item.Id+'',
|
e: parseFloat(item.EpisodeNumberValue+'')+'',
|
||||||
img: item.ScreenShotSmallUrl,
|
lang: language ? language[0].split(', ') : [],
|
||||||
description: description ? description[0] : '',
|
name: item.Name,
|
||||||
time: ''
|
season: parseFloat(item.SeasonNumberValue+'')+'',
|
||||||
};
|
seasonTitle: request.value.Name,
|
||||||
})};
|
episode: parseFloat(item.EpisodeNumberValue+'')+'',
|
||||||
|
id: item.Id+'',
|
||||||
|
img: item.ScreenShotSmallUrl,
|
||||||
|
description: description ? description[0] : '',
|
||||||
|
time: ''
|
||||||
|
};
|
||||||
|
})};
|
||||||
|
} else {
|
||||||
|
const request = await this.hidive.listSeries(parse);
|
||||||
|
if (!request.isOk || !request.value)
|
||||||
|
return {isOk: false, reason: new Error('Unknown upstream error, check for additional logs')};
|
||||||
|
|
||||||
|
return { isOk: true, value: request.value.map(function(item) {
|
||||||
|
const description = item.description.split('\r\n');
|
||||||
|
return {
|
||||||
|
e: item.episodeInformation.episodeNumber+'',
|
||||||
|
lang: [],
|
||||||
|
name: item.title,
|
||||||
|
season: item.episodeInformation.seasonNumber+'',
|
||||||
|
seasonTitle: request.series.seasons[item.episodeInformation.seasonNumber-1].title,
|
||||||
|
episode: item.episodeInformation.episodeNumber+'',
|
||||||
|
id: item.id+'',
|
||||||
|
img: item.thumbnailUrl,
|
||||||
|
description: description ? description[0] : '',
|
||||||
|
time: ''
|
||||||
|
};
|
||||||
|
})};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async downloadItem(data: DownloadData) {
|
public async downloadItem(data: DownloadData) {
|
||||||
this.setDownloading(true);
|
this.setDownloading(true);
|
||||||
console.debug(`Got download options: ${JSON.stringify(data)}`);
|
console.debug(`Got download options: ${JSON.stringify(data)}`);
|
||||||
const _default = yargs.appArgv(this.hidive.cfg.cli, true);
|
const _default = yargs.appArgv(this.hidive.cfg.cli, true);
|
||||||
const res = await this.hidive.getShow(parseInt(data.id), data.e, false, false);
|
this.hidive.api = _default.hdapi;
|
||||||
if (!res.isOk || !res.showData)
|
if (this.hidive.api == 'old') {
|
||||||
return this.alertError(new Error('Download failed upstream, check for additional logs'));
|
const res = await this.hidive.getShow(parseInt(data.id), data.e, false, false);
|
||||||
|
if (!res.isOk || !res.showData)
|
||||||
|
return this.alertError(new Error('Download failed upstream, check for additional logs'));
|
||||||
|
|
||||||
for (const ep of res.value) {
|
for (const ep of res.value) {
|
||||||
await this.hidive.getEpisode(ep, {..._default, callbackMaker: this.makeProgressHandler.bind(this), dubLang: data.dubLang, dlsubs: data.dlsubs, fileName: data.fileName, q: data.q, force: 'y', noaudio: data.noaudio, novids: data.novids });
|
await this.hidive.getEpisode(ep, {..._default, callbackMaker: this.makeProgressHandler.bind(this), dubLang: data.dubLang, dlsubs: data.dlsubs, fileName: data.fileName, q: data.q, force: 'y', noaudio: data.noaudio, novids: data.novids });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const res = await this.hidive.selectSeries(parseInt(data.id), data.e, false, false);
|
||||||
|
if (!res.isOk || !res.showData)
|
||||||
|
return this.alertError(new Error('Download failed upstream, check for additional logs'));
|
||||||
|
|
||||||
|
for (const ep of res.value) {
|
||||||
|
await this.hidive.downloadEpisode(ep, {..._default, callbackMaker: this.makeProgressHandler.bind(this), dubLang: data.dubLang, dlsubs: data.dlsubs, fileName: data.fileName, q: data.q, force: 'y', noaudio: data.noaudio, novids: data.novids });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.sendMessage({ name: 'finish', data: undefined });
|
this.sendMessage({ name: 'finish', data: undefined });
|
||||||
this.setDownloading(false);
|
this.setDownloading(false);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ const domain = {
|
||||||
www_beta: 'https://beta.crunchyroll.com',
|
www_beta: 'https://beta.crunchyroll.com',
|
||||||
api_beta: 'https://beta-api.crunchyroll.com',
|
api_beta: 'https://beta-api.crunchyroll.com',
|
||||||
hd_www: 'https://www.hidive.com',
|
hd_www: 'https://www.hidive.com',
|
||||||
hd_api: 'https://api.hidive.com'
|
hd_api: 'https://api.hidive.com',
|
||||||
|
hd_new: 'https://dce-frontoffice.imggaming.com'
|
||||||
};
|
};
|
||||||
|
|
||||||
export type APIType = {
|
export type APIType = {
|
||||||
|
|
@ -41,6 +42,9 @@ export type APIType = {
|
||||||
hd_clientWeb: string,
|
hd_clientWeb: string,
|
||||||
hd_clientExo: string,
|
hd_clientExo: string,
|
||||||
hd_api: string,
|
hd_api: string,
|
||||||
|
hd_new_api: string,
|
||||||
|
hd_new_apiKey: string,
|
||||||
|
hd_new_version: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
// api urls
|
// api urls
|
||||||
|
|
@ -77,6 +81,10 @@ const api: APIType = {
|
||||||
hd_clientWeb: 'okhttp/3.4.1',
|
hd_clientWeb: 'okhttp/3.4.1',
|
||||||
hd_clientExo: 'smartexoplayer/1.6.0.R (Linux;Android 6.0) ExoPlayerLib/2.6.0',
|
hd_clientExo: 'smartexoplayer/1.6.0.R (Linux;Android 6.0) ExoPlayerLib/2.6.0',
|
||||||
hd_api: `${domain.hd_api}/api/v1`,
|
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'
|
||||||
};
|
};
|
||||||
|
|
||||||
// set header
|
// set header
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ let argvC: {
|
||||||
dlVideoOnce: boolean;
|
dlVideoOnce: boolean;
|
||||||
chapters: boolean;
|
chapters: boolean;
|
||||||
crapi: 'android' | 'web';
|
crapi: 'android' | 'web';
|
||||||
|
hdapi: 'old' | 'new';
|
||||||
removeBumpers: boolean;
|
removeBumpers: boolean;
|
||||||
originalFontSize: boolean;
|
originalFontSize: boolean;
|
||||||
keepAllVideos: boolean;
|
keepAllVideos: boolean;
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,20 @@ const args: TAppArg<boolean|number|string|unknown[]>[] = [
|
||||||
default: 'android'
|
default: 'android'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'hdapi',
|
||||||
|
describe: 'Selects the API type for Hidive',
|
||||||
|
type: 'string',
|
||||||
|
group: 'dl',
|
||||||
|
service: ['hidive'],
|
||||||
|
docDescribe: 'If set to Old, it has lower quality, but Non-DRM streams, but some people can\'t use it,'
|
||||||
|
+ '\nIf set to New, it has a higher quality stream, but everything is DRM.',
|
||||||
|
usage: '',
|
||||||
|
choices: ['old', 'new'],
|
||||||
|
default: {
|
||||||
|
default: 'old'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'removeBumpers',
|
name: 'removeBumpers',
|
||||||
describe: 'Remove bumpers from final video',
|
describe: 'Remove bumpers from final video',
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ const stateFile = path.join(workingDir, 'config', 'guistate');
|
||||||
const tokenFile = {
|
const tokenFile = {
|
||||||
funi: path.join(workingDir, 'config', 'funi_token'),
|
funi: path.join(workingDir, 'config', 'funi_token'),
|
||||||
cr: path.join(workingDir, 'config', 'cr_token'),
|
cr: path.join(workingDir, 'config', 'cr_token'),
|
||||||
hd: path.join(workingDir, 'config', 'hd_token')
|
hd: path.join(workingDir, 'config', 'hd_token'),
|
||||||
|
hdNew: path.join(workingDir, 'config', 'hd_new_token')
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ensureConfig = () => {
|
export const ensureConfig = () => {
|
||||||
|
|
@ -242,7 +243,7 @@ const saveHDSession = (data: Record<string, unknown>) => {
|
||||||
|
|
||||||
|
|
||||||
const loadHDToken = () => {
|
const loadHDToken = () => {
|
||||||
let token = loadYamlCfgFile(tokenFile.cr, true);
|
let token = loadYamlCfgFile(tokenFile.hd, true);
|
||||||
if(typeof token !== 'object' || token === null || Array.isArray(token)){
|
if(typeof token !== 'object' || token === null || Array.isArray(token)){
|
||||||
token = {};
|
token = {};
|
||||||
}
|
}
|
||||||
|
|
@ -292,6 +293,25 @@ const loadHDProfile = () => {
|
||||||
return profile;
|
return profile;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadNewHDToken = () => {
|
||||||
|
let token = loadYamlCfgFile(tokenFile.hdNew, true);
|
||||||
|
if(typeof token !== 'object' || token === null || Array.isArray(token)){
|
||||||
|
token = {};
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveNewHDToken = (data: Record<string, unknown>) => {
|
||||||
|
const cfgFolder = path.dirname(tokenFile.hdNew);
|
||||||
|
try{
|
||||||
|
fs.ensureDirSync(cfgFolder);
|
||||||
|
fs.writeFileSync(`${tokenFile.hdNew}.yml`, yaml.stringify(data));
|
||||||
|
}
|
||||||
|
catch(e){
|
||||||
|
console.error('Can\'t save token file to disk!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadFuniToken = () => {
|
const loadFuniToken = () => {
|
||||||
const loadedToken = loadYamlCfgFile<{
|
const loadedToken = loadYamlCfgFile<{
|
||||||
token?: string
|
token?: string
|
||||||
|
|
@ -363,6 +383,8 @@ export {
|
||||||
loadHDSession,
|
loadHDSession,
|
||||||
saveHDToken,
|
saveHDToken,
|
||||||
loadHDToken,
|
loadHDToken,
|
||||||
|
saveNewHDToken,
|
||||||
|
loadNewHDToken,
|
||||||
saveHDProfile,
|
saveHDProfile,
|
||||||
loadHDProfile,
|
loadHDProfile,
|
||||||
getState,
|
getState,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
export type LanguageItem = {
|
export type LanguageItem = {
|
||||||
cr_locale?: string,
|
cr_locale?: string,
|
||||||
hd_locale?: string,
|
hd_locale?: string,
|
||||||
|
new_hd_locale?: string,
|
||||||
locale: string,
|
locale: string,
|
||||||
code: string,
|
code: string,
|
||||||
name: string,
|
name: string,
|
||||||
|
|
@ -13,12 +14,12 @@ export type LanguageItem = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const languages: LanguageItem[] = [
|
const languages: LanguageItem[] = [
|
||||||
{ cr_locale: 'en-US', hd_locale: 'English', funi_locale: 'enUS', locale: 'en', code: 'eng', name: 'English' },
|
{ cr_locale: 'en-US', new_hd_locale: 'en-US', hd_locale: 'English', funi_locale: 'enUS', locale: 'en', code: 'eng', name: 'English' },
|
||||||
{ cr_locale: 'en-IN', locale: 'en-IN', code: 'eng', name: 'English (India)', },
|
{ cr_locale: 'en-IN', locale: 'en-IN', code: 'eng', name: 'English (India)', },
|
||||||
{ cr_locale: 'es-LA', hd_locale: 'Spanish LatAm', funi_name: 'Spanish (LAS)', funi_name_lagacy: 'Spanish (Latin Am)', funi_locale: 'esLA', locale: 'es-419', code: 'spa', name: 'Spanish', language: 'Latin American Spanish' },
|
{ cr_locale: 'es-LA', new_hd_locale: 'es-MX', hd_locale: 'Spanish LatAm', funi_name: 'Spanish (LAS)', funi_name_lagacy: 'Spanish (Latin Am)', funi_locale: 'esLA', locale: 'es-419', code: 'spa', name: 'Spanish', language: 'Latin American Spanish' },
|
||||||
{ cr_locale: 'es-419',hd_locale: 'Spanish', locale: 'es-419', code: 'spa-419', name: 'Spanish', language: 'Latin American Spanish' },
|
{ cr_locale: 'es-419',hd_locale: 'Spanish', locale: 'es-419', code: 'spa-419', name: 'Spanish', language: 'Latin American Spanish' },
|
||||||
{ cr_locale: 'es-ES', hd_locale: 'Spanish Europe', locale: 'es-ES', code: 'spa-ES', name: 'Castilian', language: 'European Spanish' },
|
{ cr_locale: 'es-ES', hd_locale: 'Spanish Europe', locale: 'es-ES', code: 'spa-ES', name: 'Castilian', language: 'European Spanish' },
|
||||||
{ cr_locale: 'pt-BR', hd_locale: 'Portuguese', funi_name: 'Portuguese (Brazil)', funi_locale: 'ptBR', locale: 'pt-BR', code: 'por', name: 'Portuguese', language: 'Brazilian Portuguese' },
|
{ cr_locale: 'pt-BR', new_hd_locale: 'pt-BR', hd_locale: 'Portuguese', funi_name: 'Portuguese (Brazil)', funi_locale: 'ptBR', locale: 'pt-BR', code: 'por', name: 'Portuguese', language: 'Brazilian Portuguese' },
|
||||||
{ cr_locale: 'pt-PT', locale: 'pt-PT', code: 'por', name: 'Portuguese (Portugal)', language: 'Portugues (Portugal)' },
|
{ cr_locale: 'pt-PT', locale: 'pt-PT', code: 'por', name: 'Portuguese (Portugal)', language: 'Portugues (Portugal)' },
|
||||||
{ cr_locale: 'fr-FR', hd_locale: 'French', locale: 'fr', code: 'fra', name: 'French' },
|
{ cr_locale: 'fr-FR', hd_locale: 'French', locale: 'fr', code: 'fra', name: 'French' },
|
||||||
{ cr_locale: 'de-DE', hd_locale: 'German', locale: 'de', code: 'deu', name: 'German' },
|
{ cr_locale: 'de-DE', hd_locale: 'German', locale: 'de', code: 'deu', name: 'German' },
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,9 @@ class Req {
|
||||||
options.headers = {...options.headers, ...params.headers};
|
options.headers = {...options.headers, ...params.headers};
|
||||||
}
|
}
|
||||||
if(options.method == 'POST'){
|
if(options.method == 'POST'){
|
||||||
(options.headers as Headers)['Content-Type'] = 'application/x-www-form-urlencoded';
|
if (!(options.headers as Headers)['Content-Type']) {
|
||||||
|
(options.headers as Headers)['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(params.body){
|
if(params.body){
|
||||||
options.body = params.body;
|
options.body = params.body;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Playlist, parse as mpdParse } from 'mpd-parser';
|
import { parse as mpdParse } from 'mpd-parser';
|
||||||
import { LanguageItem } from './module.langsData';
|
import { LanguageItem, findLang, languages } from './module.langsData';
|
||||||
|
|
||||||
type Segment = {
|
type Segment = {
|
||||||
uri: string;
|
uri: string;
|
||||||
|
|
@ -20,7 +20,8 @@ export type PlaylistItem = {
|
||||||
|
|
||||||
|
|
||||||
type AudioPlayList = {
|
type AudioPlayList = {
|
||||||
language: LanguageItem
|
language: LanguageItem,
|
||||||
|
default: boolean
|
||||||
} & PlaylistItem
|
} & PlaylistItem
|
||||||
|
|
||||||
type VideoPlayList = {
|
type VideoPlayList = {
|
||||||
|
|
@ -37,9 +38,9 @@ export type MPDParsed = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parse(manifest: string, language: LanguageItem, url?: string) {
|
export function parse(manifest: string, language?: LanguageItem, url?: string) {
|
||||||
if (!manifest.includes('BaseURL') && url) {
|
if (!manifest.includes('BaseURL') && url) {
|
||||||
manifest = manifest.replace(/(<MPD[^]^[^]*?>)/gm, `$1<BaseURL>${url}</BaseURL>`);
|
manifest = manifest.replace(/(<MPD*\b[^>]*>)/gm, `$1<BaseURL>${url}</BaseURL>`);
|
||||||
}
|
}
|
||||||
const parsed = mpdParse(manifest);
|
const parsed = mpdParse(manifest);
|
||||||
const ret: MPDParsed = {};
|
const ret: MPDParsed = {};
|
||||||
|
|
@ -50,9 +51,18 @@ export function parse(manifest: string, language: LanguageItem, url?: string) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(ret, host))
|
if (!Object.prototype.hasOwnProperty.call(ret, host))
|
||||||
ret[host] = { audio: [], video: [] };
|
ret[host] = { audio: [], video: [] };
|
||||||
|
|
||||||
|
//Find and add audio language if it is found in the MPD
|
||||||
|
let audiolang: LanguageItem;
|
||||||
|
const foundlanguage = findLang(languages.find(a => a.code === item.language)?.cr_locale ?? 'unknown');
|
||||||
|
if (item.language) {
|
||||||
|
audiolang = foundlanguage;
|
||||||
|
} else {
|
||||||
|
audiolang = language ? language : foundlanguage;
|
||||||
|
}
|
||||||
const pItem: AudioPlayList = {
|
const pItem: AudioPlayList = {
|
||||||
bandwidth: playlist.attributes.BANDWIDTH,
|
bandwidth: playlist.attributes.BANDWIDTH,
|
||||||
language: language,
|
language: audiolang,
|
||||||
|
default: item.default,
|
||||||
segments: playlist.segments.map((segment): Segment => {
|
segments: playlist.segments.map((segment): Segment => {
|
||||||
const uri = segment.resolvedUri;
|
const uri = segment.resolvedUri;
|
||||||
const map_uri = segment.map.resolvedUri;
|
const map_uri = segment.map.resolvedUri;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "multi-downloader-nx",
|
"name": "multi-downloader-nx",
|
||||||
"short_name": "aniDL",
|
"short_name": "aniDL",
|
||||||
"version": "4.5.0",
|
"version": "4.5.0rc2",
|
||||||
"description": "Downloader for Crunchyroll, Funimation, or Hidive via CLI or GUI",
|
"description": "Downloader for Crunchyroll, Funimation, or Hidive via CLI or GUI",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"download",
|
"download",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue