mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-04-21 16:31:55 +00:00
Remove old HD API
This commit is contained in:
parent
23f185c877
commit
e9e14aef2f
4 changed files with 110 additions and 921 deletions
|
|
@ -13,12 +13,10 @@ class HidiveHandler extends Base implements MessageHandler {
|
||||||
constructor(ws: WebSocketHandler) {
|
constructor(ws: WebSocketHandler) {
|
||||||
super(ws);
|
super(ws);
|
||||||
this.hidive = new Hidive();
|
this.hidive = new Hidive();
|
||||||
this.hidive.doInit();
|
|
||||||
this.initState();
|
this.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async auth(data: AuthData) {
|
public async auth(data: AuthData) {
|
||||||
await this.getAPIVersion();
|
|
||||||
return this.hidive.doAuth(data);
|
return this.hidive.doAuth(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,13 +25,7 @@ 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) {
|
||||||
|
|
@ -69,46 +61,24 @@ 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)}`);
|
||||||
await this.getAPIVersion();
|
const res = await this.hidive.selectSeries(parseInt(data.id), data.e, data.but, data.all);
|
||||||
if (this.hidive.api == 'old') {
|
if (!res.isOk || !res.value)
|
||||||
const res = await this.hidive.getShow(parseInt(data.id), data.e, data.but, data.all);
|
return res.isOk;
|
||||||
if (!res.isOk || !res.value)
|
this.addToQueue(res.value.map(item => {
|
||||||
return res.isOk;
|
return {
|
||||||
this.addToQueue(res.value.map(item => {
|
...data,
|
||||||
return {
|
ids: [item.id],
|
||||||
...data,
|
title: item.title,
|
||||||
ids: [item.Id],
|
parent: {
|
||||||
title: item.Name,
|
title: item.seriesTitle,
|
||||||
parent: {
|
season: item.episodeInformation.seasonNumber+''
|
||||||
title: item.seriesTitle,
|
},
|
||||||
season: parseFloat(item.SeasonNumberValue+'')+''
|
image: item.thumbnailUrl,
|
||||||
},
|
e: item.episodeInformation.episodeNumber+'',
|
||||||
image: item.ScreenShotSmallUrl,
|
episode: item.episodeInformation.episodeNumber+'',
|
||||||
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> {
|
||||||
|
|
@ -116,73 +86,37 @@ class HidiveHandler extends Base implements MessageHandler {
|
||||||
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') };
|
||||||
|
|
||||||
await this.getAPIVersion();
|
const request = await this.hidive.listSeries(parse);
|
||||||
if (this.hidive.api == 'old') {
|
if (!request.isOk || !request.value)
|
||||||
const request = await this.hidive.listShow(parse);
|
return {isOk: false, reason: new Error('Unknown upstream error, check for additional logs')};
|
||||||
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) {
|
return { isOk: true, value: request.value.map(function(item) {
|
||||||
const language = item.Summary.match(/^Audio: (.*)/m);
|
const description = item.description.split('\r\n');
|
||||||
language?.shift();
|
return {
|
||||||
const description = item.Summary.split('\r\n');
|
e: item.episodeInformation.episodeNumber+'',
|
||||||
return {
|
lang: [],
|
||||||
e: parseFloat(item.EpisodeNumberValue+'')+'',
|
name: item.title,
|
||||||
lang: language ? language[0].split(', ') : [],
|
season: item.episodeInformation.seasonNumber+'',
|
||||||
name: item.Name,
|
seasonTitle: request.series.seasons[item.episodeInformation.seasonNumber-1].title,
|
||||||
season: parseFloat(item.SeasonNumberValue+'')+'',
|
episode: item.episodeInformation.episodeNumber+'',
|
||||||
seasonTitle: request.value.Name,
|
id: item.id+'',
|
||||||
episode: parseFloat(item.EpisodeNumberValue+'')+'',
|
img: item.thumbnailUrl,
|
||||||
id: item.Id+'',
|
description: description ? description[0] : '',
|
||||||
img: item.ScreenShotSmallUrl,
|
time: ''
|
||||||
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);
|
||||||
this.hidive.api = _default.hdapi;
|
const res = await this.hidive.selectSeries(parseInt(data.id), data.e, false, false);
|
||||||
if (this.hidive.api == 'old') {
|
if (!res.isOk || !res.showData)
|
||||||
const res = await this.hidive.getShow(parseInt(data.id), data.e, false, false);
|
return this.alertError(new Error('Download failed upstream, check for additional logs'));
|
||||||
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.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 });
|
||||||
}
|
|
||||||
} 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);
|
||||||
|
|
|
||||||
868
hidive.ts
868
hidive.ts
|
|
@ -1,7 +1,6 @@
|
||||||
// build-in
|
// build-in
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import crypto from 'crypto';
|
|
||||||
|
|
||||||
// package program
|
// package program
|
||||||
import packageJson from './package.json';
|
import packageJson from './package.json';
|
||||||
|
|
@ -9,7 +8,6 @@ import packageJson from './package.json';
|
||||||
// plugins
|
// plugins
|
||||||
import { console } from './modules/log';
|
import { console } from './modules/log';
|
||||||
import shlp from 'sei-helper';
|
import shlp from 'sei-helper';
|
||||||
import m3u8 from 'm3u8-parsed';
|
|
||||||
import streamdl, { M3U8Json } from './modules/hls-download';
|
import streamdl, { M3U8Json } from './modules/hls-download';
|
||||||
|
|
||||||
// custom modules
|
// custom modules
|
||||||
|
|
@ -23,8 +21,7 @@ import vtt2ass from './modules/module.vtt2ass';
|
||||||
// load req
|
// load req
|
||||||
import { domain, api } from './modules/module.api-urls';
|
import { domain, api } from './modules/module.api-urls';
|
||||||
import * as reqModule from './modules/module.req';
|
import * as reqModule from './modules/module.req';
|
||||||
import { HidiveEpisodeList, HidiveEpisodeExtra } from './@types/hidiveEpisodeList';
|
import { DownloadedMedia } from './@types/hidiveTypes';
|
||||||
import { HidiveVideoList, HidiveStreamInfo, DownloadedMedia, HidiveSubtitleInfo } from './@types/hidiveTypes';
|
|
||||||
import parseFileName, { Variable } from './modules/module.filename';
|
import parseFileName, { Variable } from './modules/module.filename';
|
||||||
import { downloaded } from './modules/module.downloadArchive';
|
import { downloaded } from './modules/module.downloadArchive';
|
||||||
import parseSelect from './modules/module.parseSelect';
|
import parseSelect from './modules/module.parseSelect';
|
||||||
|
|
@ -32,8 +29,6 @@ import { AvailableFilenameVars } from './modules/module.args';
|
||||||
import { AuthData, AuthResponse, SearchData, SearchResponse, SearchResponseItem } from './@types/messageHandler';
|
import { AuthData, AuthResponse, SearchData, SearchResponse, SearchResponseItem } from './@types/messageHandler';
|
||||||
import { ServiceClass } from './@types/serviceClassInterface';
|
import { ServiceClass } from './@types/serviceClassInterface';
|
||||||
import { sxItem } from './crunchy';
|
import { sxItem } from './crunchy';
|
||||||
import { HidiveSearch } from './@types/hidiveSearch';
|
|
||||||
import { HidiveDashboard } from './@types/hidiveDashboard';
|
|
||||||
import { Hit, NewHidiveSearch } from './@types/newHidiveSearch';
|
import { Hit, NewHidiveSearch } from './@types/newHidiveSearch';
|
||||||
import { NewHidiveSeries } from './@types/newHidiveSeries';
|
import { NewHidiveSeries } from './@types/newHidiveSeries';
|
||||||
import { Episode, NewHidiveEpisodeExtra, NewHidiveSeason, NewHidiveSeriesExtra } from './@types/newHidiveSeason';
|
import { Episode, NewHidiveEpisodeExtra, NewHidiveSeason, NewHidiveSeriesExtra } from './@types/newHidiveSeason';
|
||||||
|
|
@ -46,59 +41,27 @@ import { KeyContainer } from './modules/license';
|
||||||
|
|
||||||
export default class Hidive implements ServiceClass {
|
export default class Hidive implements ServiceClass {
|
||||||
public cfg: yamlCfg.ConfigObject;
|
public cfg: yamlCfg.ConfigObject;
|
||||||
private session: Record<string, any>;
|
|
||||||
private tokenOld: Record<string, any>;
|
|
||||||
private token: Record<string, any>;
|
private token: Record<string, any>;
|
||||||
private req: reqModule.Req;
|
private req: reqModule.Req;
|
||||||
public api: 'old' | 'new';
|
|
||||||
private client: {
|
|
||||||
// base
|
|
||||||
ipAddress: string,
|
|
||||||
xNonce: string,
|
|
||||||
xSignature: string,
|
|
||||||
// personal
|
|
||||||
visitId: string,
|
|
||||||
// profile data
|
|
||||||
profile: {
|
|
||||||
userId: number,
|
|
||||||
profileId: number,
|
|
||||||
deviceId : string,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(private debug = false) {
|
constructor(private debug = false) {
|
||||||
this.cfg = yamlCfg.loadCfg();
|
this.cfg = yamlCfg.loadCfg();
|
||||||
this.session = yamlCfg.loadHDSession();
|
|
||||||
this.tokenOld = yamlCfg.loadHDToken();
|
|
||||||
this.token = yamlCfg.loadNewHDToken();
|
this.token = yamlCfg.loadNewHDToken();
|
||||||
this.client = yamlCfg.loadHDProfile() as {ipAddress: string, xNonce: string, xSignature: string, visitId: string, profile: {userId: number, profileId: number, deviceId : string}};
|
|
||||||
this.req = new reqModule.Req(domain, debug, false, 'hd');
|
this.req = new reqModule.Req(domain, debug, false, 'hd');
|
||||||
this.api = 'old';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async cli() {
|
public async cli() {
|
||||||
console.info(`\n=== Multi Downloader NX ${packageJson.version} ===\n`);
|
console.info(`\n=== Multi Downloader NX ${packageJson.version} ===\n`);
|
||||||
const argv = yargs.appArgv(this.cfg.cli);
|
const argv = yargs.appArgv(this.cfg.cli);
|
||||||
this.api = argv.hdapi;
|
|
||||||
if (argv.debug)
|
if (argv.debug)
|
||||||
this.debug = true;
|
this.debug = true;
|
||||||
|
|
||||||
//below is for quickly testing API calls
|
//below is for quickly testing API calls
|
||||||
/*const searchItems = await this.reqData('GetTitles', {'Filter': 'recently-added', 'Pager': {'Number': 1, 'Size': 30}, 'Sort': 'Date', 'Verbose': false});
|
/*const apiTest = await this.apiReq('/v4/season/18871', '', 'auth', 'GET');
|
||||||
const searchItems = await this.reqData('GetTitles', {'Id': 492});
|
|
||||||
if(!searchItems.ok || !searchItems.res){return;}
|
|
||||||
console.info(searchItems.res.body);
|
|
||||||
fs.writeFileSync('apitest.json', JSON.stringify(JSON.parse(searchItems.res.body), null, 2));*/
|
|
||||||
|
|
||||||
//new api testing
|
|
||||||
/*if (this.api == 'new') {
|
|
||||||
await this.doInit();
|
|
||||||
const apiTest = await this.apiReq('/v4/season/18871', '', 'auth', 'GET');
|
|
||||||
if(!apiTest.ok || !apiTest.res){return;}
|
if(!apiTest.ok || !apiTest.res){return;}
|
||||||
console.info(apiTest.res.body);
|
console.info(apiTest.res.body);
|
||||||
fs.writeFileSync('apitest.json', JSON.stringify(JSON.parse(apiTest.res.body), null, 2));
|
fs.writeFileSync('apitest.json', JSON.stringify(JSON.parse(apiTest.res.body), null, 2));
|
||||||
return console.info('test done');
|
return console.info('test done');*/
|
||||||
}*/
|
|
||||||
|
|
||||||
// load binaries
|
// load binaries
|
||||||
this.cfg.bin = await yamlCfg.loadBinCfg();
|
this.cfg.bin = await yamlCfg.loadBinCfg();
|
||||||
|
|
@ -106,42 +69,21 @@ export default class Hidive implements ServiceClass {
|
||||||
argv.dubLang = langsData.dubLanguageCodes;
|
argv.dubLang = langsData.dubLanguageCodes;
|
||||||
}
|
}
|
||||||
if (argv.auth) {
|
if (argv.auth) {
|
||||||
//Initilize session
|
|
||||||
await this.doInit();
|
|
||||||
//Authenticate
|
//Authenticate
|
||||||
await this.doAuth({
|
await this.doAuth({
|
||||||
username: argv.username ?? await shlp.question('[Q] LOGIN/EMAIL'),
|
username: argv.username ?? await shlp.question('[Q] LOGIN/EMAIL'),
|
||||||
password: argv.password ?? await shlp.question('[Q] PASSWORD ')
|
password: argv.password ?? await shlp.question('[Q] PASSWORD ')
|
||||||
});
|
});
|
||||||
} else if (argv.search && argv.search.length > 2){
|
} else if (argv.search && argv.search.length > 2){
|
||||||
//Initilize session
|
|
||||||
await this.doInit();
|
|
||||||
//Search
|
|
||||||
await this.doSearch({ ...argv, search: argv.search as string });
|
await this.doSearch({ ...argv, search: argv.search as string });
|
||||||
} else if (argv.s && !isNaN(parseInt(argv.s,10)) && parseInt(argv.s,10) > 0) {
|
} else if (argv.s && !isNaN(parseInt(argv.s,10)) && parseInt(argv.s,10) > 0) {
|
||||||
if (this.api == 'old') {
|
const selected = await this.selectSeason(parseInt(argv.s), argv.e, argv.but, argv.all);
|
||||||
//Initilize session
|
if (selected.isOk && selected.showData) {
|
||||||
await this.doInit();
|
for (const select of selected.value) {
|
||||||
//get selected episodes
|
//download episode
|
||||||
const selected = await this.getShow(parseInt(argv.s), argv.e, argv.but, argv.all);
|
if (!(await this.downloadEpisode(select, {...argv}))) {
|
||||||
if (selected.isOk && selected.showData) {
|
console.error(`Unable to download selected episode ${select.episodeInformation.episodeNumber}`);
|
||||||
for (const select of selected.value) {
|
return false;
|
||||||
//download episode
|
|
||||||
if (!(await this.getEpisode(select, {...argv}))) {
|
|
||||||
console.error(`Unable to download selected episode ${parseFloat(select.EpisodeNumberValue+'')}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const selected = await this.selectSeason(parseInt(argv.s), argv.e, argv.but, argv.all);
|
|
||||||
if (selected.isOk && selected.showData) {
|
|
||||||
for (const select of selected.value) {
|
|
||||||
//download episode
|
|
||||||
if (!(await this.downloadEpisode(select, {...argv}))) {
|
|
||||||
console.error(`Unable to download selected episode ${select.episodeInformation.episodeNumber}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -158,209 +100,17 @@ export default class Hidive implements ServiceClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (argv.new) {
|
} else if (argv.new) {
|
||||||
if (this.api == 'old') {
|
console.error('--new is not yet implemented in the new API');
|
||||||
//Initilize session
|
|
||||||
await this.doInit();
|
|
||||||
//Get Newly Added
|
|
||||||
await this.getNewlyAdded(argv.page);
|
|
||||||
} else {
|
|
||||||
console.error('--new is not yet implemented in the new API');
|
|
||||||
}
|
|
||||||
} else if(argv.e) {
|
} else if(argv.e) {
|
||||||
if (this.api == 'new') {
|
if (!(await this.downloadSingleEpisode(parseInt(argv.e), {...argv}))) {
|
||||||
if (!(await this.downloadSingleEpisode(parseInt(argv.e), {...argv}))) {
|
console.error(`Unable to download selected episode ${argv.e}`);
|
||||||
console.error(`Unable to download selected episode ${argv.e}`);
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('-e is not supported in the old API');
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.info('No option selected or invalid value entered. Try --help.');
|
console.info('No option selected or invalid value entered. Try --help.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async doInit() {
|
|
||||||
if (this.api == 'old') {
|
|
||||||
//get client ip
|
|
||||||
const newIp = await this.reqData('Ping', '');
|
|
||||||
if (!newIp.ok || !newIp.res) return false;
|
|
||||||
this.client.ipAddress = JSON.parse(newIp.res.body).IPAddress;
|
|
||||||
//get device id
|
|
||||||
const newDevice = await this.reqData('InitDevice', { 'DeviceName': api.hd_devName });
|
|
||||||
if (!newDevice.ok || !newDevice.res) return false;
|
|
||||||
this.client.profile = Object.assign(this.client.profile, {
|
|
||||||
deviceId: JSON.parse(newDevice.res.body).Data.DeviceId,
|
|
||||||
});
|
|
||||||
//get visit id
|
|
||||||
const newVisitId = await this.reqData('InitVisit', {});
|
|
||||||
if (!newVisitId.ok || !newVisitId.res) return false;
|
|
||||||
this.client.visitId = JSON.parse(newVisitId.res.body).Data.VisitId;
|
|
||||||
//save client
|
|
||||||
yamlCfg.saveHDProfile(this.client);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
//this.refreshToken();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate Nonce
|
|
||||||
public generateNonce(){
|
|
||||||
const initDate = new Date();
|
|
||||||
const nonceDate = [
|
|
||||||
initDate.getUTCFullYear().toString().slice(-2), // yy
|
|
||||||
('0'+(initDate.getUTCMonth()+1)).slice(-2), // MM
|
|
||||||
('0'+initDate.getUTCDate()).slice(-2), // dd
|
|
||||||
('0'+initDate.getUTCHours()).slice(-2), // HH
|
|
||||||
('0'+initDate.getUTCMinutes()).slice(-2) // mm
|
|
||||||
].join(''); // => "yyMMddHHmm" (UTC)
|
|
||||||
const nonceCleanStr = nonceDate + api.hd_apikey;
|
|
||||||
const nonceHash = crypto.createHash('sha256').update(nonceCleanStr).digest('hex');
|
|
||||||
return nonceHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate Signature
|
|
||||||
public generateSignature(body: string|object, visitId: string, profile: Record<string, any>) {
|
|
||||||
const sigCleanStr = [
|
|
||||||
this.client.ipAddress,
|
|
||||||
api.hd_appId,
|
|
||||||
profile.deviceId,
|
|
||||||
visitId,
|
|
||||||
profile.userId,
|
|
||||||
profile.profileId,
|
|
||||||
body,
|
|
||||||
this.client.xNonce,
|
|
||||||
api.hd_apikey,
|
|
||||||
].join('');
|
|
||||||
return crypto.createHash('sha256').update(sigCleanStr).digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
public makeCookieList(data: Record<string, any>, keys: Array<string>) {
|
|
||||||
const res = [];
|
|
||||||
for (const key of keys) {
|
|
||||||
if (typeof data[key] !== 'object') continue;
|
|
||||||
res.push(`${key}=${data[key].value}`);
|
|
||||||
}
|
|
||||||
return res.join('; ');
|
|
||||||
}
|
|
||||||
|
|
||||||
public async reqData(method: string, body: string | object, type = 'POST') {
|
|
||||||
const options = {
|
|
||||||
headers: {} as Record<string, unknown>,
|
|
||||||
method: type as 'GET'|'POST',
|
|
||||||
url: '' as string,
|
|
||||||
body: body,
|
|
||||||
};
|
|
||||||
// get request type
|
|
||||||
const isGet = type == 'GET' ? true : false;
|
|
||||||
// set request type, url, user agent, referrer, and origin
|
|
||||||
options.method = isGet ? 'GET' : 'POST';
|
|
||||||
options.url = ( !isGet ? domain.hd_api + '/api/v1/' : '') + method;
|
|
||||||
options.headers['user-agent'] = isGet ? api.hd_clientExo : api.hd_clientWeb;
|
|
||||||
options.headers['referrer'] = 'https://www.hidive.com/';
|
|
||||||
options.headers['origin'] = 'https://www.hidive.com';
|
|
||||||
// set api data
|
|
||||||
if(!isGet){
|
|
||||||
options.body = body == '' ? body : JSON.stringify(body);
|
|
||||||
// set api headers
|
|
||||||
if(method != 'Ping'){
|
|
||||||
const visitId = this.client.visitId ? this.client.visitId : '';
|
|
||||||
const vprofile = {
|
|
||||||
userId: this.client.profile.userId || 0,
|
|
||||||
profileId: this.client.profile.profileId || 0,
|
|
||||||
deviceId: this.client.profile.deviceId || '',
|
|
||||||
};
|
|
||||||
this.client.xNonce = this.generateNonce();
|
|
||||||
this.client.xSignature = this.generateSignature(options.body, visitId, vprofile);
|
|
||||||
options.headers = Object.assign(options.headers, {
|
|
||||||
'X-VisitId' : visitId,
|
|
||||||
'X-UserId' : vprofile.userId,
|
|
||||||
'X-ProfileId' : vprofile.profileId,
|
|
||||||
'X-DeviceId' : vprofile.deviceId,
|
|
||||||
'X-Nonce' : this.client.xNonce,
|
|
||||||
'X-Signature' : this.client.xSignature,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
options.headers = Object.assign({
|
|
||||||
'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
||||||
'X-ApplicationId': api.hd_appId,
|
|
||||||
}, options.headers);
|
|
||||||
// cookies
|
|
||||||
const cookiesList = Object.keys(this.session);
|
|
||||||
if(cookiesList.length > 0 && method != 'Ping') {
|
|
||||||
options.headers.Cookie = this.makeCookieList(this.session, cookiesList);
|
|
||||||
}
|
|
||||||
} else if(isGet && !options.url.match(/\?/)){
|
|
||||||
this.client.xNonce = this.generateNonce();
|
|
||||||
this.client.xSignature = this.generateSignature(options.body, this.client.visitId, this.client.profile);
|
|
||||||
options.url = options.url + '?' + (new URLSearchParams({
|
|
||||||
'X-ApplicationId': api.hd_appId,
|
|
||||||
'X-DeviceId': this.client.profile.deviceId,
|
|
||||||
'X-VisitId': this.client.visitId,
|
|
||||||
'X-UserId': this.client.profile.userId+'',
|
|
||||||
'X-ProfileId': this.client.profile.profileId+'',
|
|
||||||
'X-Nonce': this.client.xNonce,
|
|
||||||
'X-Signature': this.client.xSignature,
|
|
||||||
})).toString();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (this.debug) {
|
|
||||||
console.debug('[DEBUG] Request params:');
|
|
||||||
console.debug(options);
|
|
||||||
}
|
|
||||||
const apiReqOpts: reqModule.Params = {
|
|
||||||
method: options.method,
|
|
||||||
headers: options.headers as Record<string, string>,
|
|
||||||
body: options.body as string
|
|
||||||
};
|
|
||||||
const apiReq = await this.req.getData(options.url, apiReqOpts);
|
|
||||||
if(!apiReq.ok || !apiReq.res){
|
|
||||||
console.error('API Request Failed!');
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
res: apiReq.res,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isGet && apiReq.res.headers && apiReq.res.headers['set-cookie']) {
|
|
||||||
const newReqCookies = shlp.cookie.parse(apiReq.res.headers['set-cookie'] as unknown as Record<string, string>);
|
|
||||||
this.session = Object.assign(this.session, newReqCookies);
|
|
||||||
yamlCfg.saveHDSession(this.session);
|
|
||||||
}
|
|
||||||
if (!isGet) {
|
|
||||||
const resJ = JSON.parse(apiReq.res.body);
|
|
||||||
if (resJ.Code > 0) {
|
|
||||||
console.error(`Code ${resJ.Code} (${resJ.Status}): ${resJ.Message}\n`);
|
|
||||||
if (resJ.Code == 81 || resJ.Code == 5) {
|
|
||||||
console.info('[NOTE] App was broken because of changes in official app.');
|
|
||||||
console.info('[NOTE] See: https://github.com/anidl/hidive-downloader-nx/issues/1\n');
|
|
||||||
}
|
|
||||||
if (resJ.Code == 55) {
|
|
||||||
console.info('[NOTE] You need premium account to view this video.');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
res: apiReq.res,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
res: apiReq.res,
|
|
||||||
};
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error.statusCode && error.statusMessage) {
|
|
||||||
console.error(`\n ${error.name} ${error.statusCode}: ${error.statusMessage}\n`);
|
|
||||||
} else {
|
|
||||||
console.error(`\n ${error.name}: ${error.code}\n`);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async apiReq(endpoint: string, body: string | object = '', authType: 'refresh' | 'auth' | 'both' | 'other' | 'none' = 'none', method: 'GET' | 'POST' = 'POST', authHeader?: string) {
|
public async apiReq(endpoint: string, body: string | object = '', authType: 'refresh' | 'auth' | 'both' | 'other' | 'none' = 'none', method: 'GET' | 'POST' = 'POST', authHeader?: string) {
|
||||||
const options = {
|
const options = {
|
||||||
|
|
@ -447,43 +197,25 @@ export default class Hidive implements ServiceClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async doAuth(data: AuthData): Promise<AuthResponse> {
|
public async doAuth(data: AuthData): Promise<AuthResponse> {
|
||||||
if (this.api == 'old') {
|
if (!this.token.refreshToken || !this.token.authorisationToken) {
|
||||||
const auth = await this.reqData('Authenticate', {'Email':data.username,'Password':data.password});
|
await this.doAnonymousAuth();
|
||||||
if(!auth.ok || !auth.res) {
|
|
||||||
console.error('Authentication failed!');
|
|
||||||
return { isOk: false, reason: new Error('Authentication failed') };
|
|
||||||
}
|
|
||||||
const authData = JSON.parse(auth.res.body).Data;
|
|
||||||
this.client.profile = Object.assign(this.client.profile, {
|
|
||||||
userId: authData.User.Id,
|
|
||||||
profileId: authData.Profiles[0].Id,
|
|
||||||
});
|
|
||||||
yamlCfg.saveHDProfile(this.client);
|
|
||||||
yamlCfg.saveHDToken(authData);
|
|
||||||
console.info('Auth complete!');
|
|
||||||
console.info(`Service level for "${data.username}" is ${authData.User.ServiceLevel}`);
|
|
||||||
return { isOk: true, value: undefined };
|
|
||||||
} else {
|
|
||||||
if (!this.token.refreshToken || !this.token.authorisationToken) {
|
|
||||||
await this.doAnonymousAuth();
|
|
||||||
}
|
|
||||||
const authReq = await this.apiReq('/v2/login', {
|
|
||||||
id: data.username,
|
|
||||||
secret: data.password
|
|
||||||
}, 'auth');
|
|
||||||
if(!authReq.ok || !authReq.res){
|
|
||||||
console.error('Authentication failed!');
|
|
||||||
return { isOk: false, reason: new Error('Authentication failed') };
|
|
||||||
}
|
|
||||||
const tokens: Record<string, string> = JSON.parse(authReq.res.body);
|
|
||||||
for (const token in tokens) {
|
|
||||||
this.token[token] = tokens[token];
|
|
||||||
}
|
|
||||||
this.token.guest = false;
|
|
||||||
yamlCfg.saveNewHDToken(this.token);
|
|
||||||
console.info('Auth complete!');
|
|
||||||
return { isOk: true, value: undefined };
|
|
||||||
}
|
}
|
||||||
|
const authReq = await this.apiReq('/v2/login', {
|
||||||
|
id: data.username,
|
||||||
|
secret: data.password
|
||||||
|
}, 'auth');
|
||||||
|
if(!authReq.ok || !authReq.res){
|
||||||
|
console.error('Authentication failed!');
|
||||||
|
return { isOk: false, reason: new Error('Authentication failed') };
|
||||||
|
}
|
||||||
|
const tokens: Record<string, string> = JSON.parse(authReq.res.body);
|
||||||
|
for (const token in tokens) {
|
||||||
|
this.token[token] = tokens[token];
|
||||||
|
}
|
||||||
|
this.token.guest = false;
|
||||||
|
yamlCfg.saveNewHDToken(this.token);
|
||||||
|
console.info('Auth complete!');
|
||||||
|
return { isOk: true, value: undefined };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async doAnonymousAuth() {
|
public async doAnonymousAuth() {
|
||||||
|
|
@ -540,110 +272,48 @@ export default class Hidive implements ServiceClass {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async genSubsUrl(type: string, file: string) {
|
|
||||||
return [
|
|
||||||
`${domain.hd_api}/caption/${type}/`,
|
|
||||||
( type == 'css' ? '?id=' : '' ),
|
|
||||||
`${file}.${type}`
|
|
||||||
].join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
public async doSearch(data: SearchData): Promise<SearchResponse> {
|
public async doSearch(data: SearchData): Promise<SearchResponse> {
|
||||||
if (this.api == 'old') {
|
const searchReq = await this.req.getData('https://h99xldr8mj-dsn.algolia.net/1/indexes/*/queries?x-algolia-agent=Algolia%20for%20JavaScript%20(3.35.1)%3B%20Browser&x-algolia-application-id=H99XLDR8MJ&x-algolia-api-key=e55ccb3db0399eabe2bfc37a0314c346', {
|
||||||
const searchReq = await this.reqData('Search', {'Query':data.search});
|
method: 'POST',
|
||||||
if(!searchReq.ok || !searchReq.res){
|
body: JSON.stringify({'requests':
|
||||||
console.error('Search FAILED!');
|
[
|
||||||
return { isOk: false, reason: new Error('Search failed. No more information provided') };
|
{'indexName':'prod-dce.hidive-livestreaming-events','params':'query='+encodeURIComponent(data.search)+'&facetFilters=%5B%22type%3ALIVE_EVENT%22%5D&hitsPerPage=25'+(data.page ? '&page='+(data.page-1) : '')},
|
||||||
}
|
{'indexName':'prod-dce.hidive-livestreaming-events','params':'query='+encodeURIComponent(data.search)+'&facetFilters=%5B%22type%3AVOD_VIDEO%22%5D&hitsPerPage=25'+(data.page ? '&page='+(data.page-1) : '')},
|
||||||
const searchData = JSON.parse(searchReq.res.body) as HidiveSearch;
|
{'indexName':'prod-dce.hidive-livestreaming-events','params':'query='+encodeURIComponent(data.search)+'&facetFilters=%5B%22type%3AVOD_PLAYLIST%22%5D&hitsPerPage=25'+(data.page ? '&page='+(data.page-1) : '')},
|
||||||
const searchItems = searchData.Data.TitleResults;
|
{'indexName':'prod-dce.hidive-livestreaming-events','params':'query='+encodeURIComponent(data.search)+'&facetFilters=%5B%22type%3AVOD_SERIES%22%5D&hitsPerPage=25'+(data.page ? '&page='+(data.page-1) : '')}
|
||||||
if(searchItems.length>0) {
|
]
|
||||||
console.info('[INFO] Search Results:');
|
})
|
||||||
for(let i=0;i<searchItems.length;i++){
|
});
|
||||||
console.info(`[#${searchItems[i].Id}] ${searchItems[i].Name} [${searchItems[i].ShowInfoTitle}]`);
|
if(!searchReq.ok || !searchReq.res){
|
||||||
}
|
console.error('Search FAILED!');
|
||||||
} else{
|
return { isOk: false, reason: new Error('Search failed. No more information provided') };
|
||||||
console.warn('Nothing found!');
|
|
||||||
}
|
|
||||||
return { isOk: true, value: searchItems.map((a): SearchResponseItem => {
|
|
||||||
return {
|
|
||||||
id: a.Id+'',
|
|
||||||
image: a.KeyArtUrl ?? '/notFound.png',
|
|
||||||
name: a.Name,
|
|
||||||
rating: a.OverallRating,
|
|
||||||
desc: a.LongSynopsis
|
|
||||||
};
|
|
||||||
})};
|
|
||||||
} else {
|
|
||||||
const searchReq = await this.req.getData('https://h99xldr8mj-dsn.algolia.net/1/indexes/*/queries?x-algolia-agent=Algolia%20for%20JavaScript%20(3.35.1)%3B%20Browser&x-algolia-application-id=H99XLDR8MJ&x-algolia-api-key=e55ccb3db0399eabe2bfc37a0314c346', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({'requests':
|
|
||||||
[
|
|
||||||
{'indexName':'prod-dce.hidive-livestreaming-events','params':'query='+encodeURIComponent(data.search)+'&facetFilters=%5B%22type%3ALIVE_EVENT%22%5D&hitsPerPage=25'+(data.page ? '&page='+(data.page-1) : '')},
|
|
||||||
{'indexName':'prod-dce.hidive-livestreaming-events','params':'query='+encodeURIComponent(data.search)+'&facetFilters=%5B%22type%3AVOD_VIDEO%22%5D&hitsPerPage=25'+(data.page ? '&page='+(data.page-1) : '')},
|
|
||||||
{'indexName':'prod-dce.hidive-livestreaming-events','params':'query='+encodeURIComponent(data.search)+'&facetFilters=%5B%22type%3AVOD_PLAYLIST%22%5D&hitsPerPage=25'+(data.page ? '&page='+(data.page-1) : '')},
|
|
||||||
{'indexName':'prod-dce.hidive-livestreaming-events','params':'query='+encodeURIComponent(data.search)+'&facetFilters=%5B%22type%3AVOD_SERIES%22%5D&hitsPerPage=25'+(data.page ? '&page='+(data.page-1) : '')}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
});
|
|
||||||
if(!searchReq.ok || !searchReq.res){
|
|
||||||
console.error('Search FAILED!');
|
|
||||||
return { isOk: false, reason: new Error('Search failed. No more information provided') };
|
|
||||||
}
|
|
||||||
const searchData = JSON.parse(searchReq.res.body) as NewHidiveSearch;
|
|
||||||
const searchItems: Hit[] = [];
|
|
||||||
console.info('Search Results:');
|
|
||||||
for (const category of searchData.results) {
|
|
||||||
for (const hit of category.hits) {
|
|
||||||
searchItems.push(hit);
|
|
||||||
let fullType: string;
|
|
||||||
if (hit.type == 'VOD_SERIES') {
|
|
||||||
fullType = `Z.${hit.id}`;
|
|
||||||
} else if (hit.type == 'VOD_VIDEO') {
|
|
||||||
fullType = `E.${hit.id}`;
|
|
||||||
} else {
|
|
||||||
fullType = `${hit.type} #${hit.id}`;
|
|
||||||
}
|
|
||||||
console.log(`[${fullType}] ${hit.name} ${hit.seasonsCount ? '('+hit.seasonsCount+' Seasons)' : ''}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { isOk: true, value: searchItems.filter(a => a.type == 'VOD_SERIES').flatMap((a): SearchResponseItem => {
|
|
||||||
return {
|
|
||||||
id: a.id+'',
|
|
||||||
image: a.coverUrl ?? '/notFound.png',
|
|
||||||
name: a.name,
|
|
||||||
rating: -1,
|
|
||||||
desc: a.description
|
|
||||||
};
|
|
||||||
})};
|
|
||||||
}
|
}
|
||||||
}
|
const searchData = JSON.parse(searchReq.res.body) as NewHidiveSearch;
|
||||||
|
const searchItems: Hit[] = [];
|
||||||
public async getNewlyAdded(page?: number) {
|
console.info('Search Results:');
|
||||||
const pageNum = page ? page : 1;
|
for (const category of searchData.results) {
|
||||||
const dashboardReq = await this.reqData('GetDashboard', {'Pager': {'Number': pageNum, 'Size': 30}, 'Verbose': false});
|
for (const hit of category.hits) {
|
||||||
if(!dashboardReq.ok || !dashboardReq.res) {
|
searchItems.push(hit);
|
||||||
console.error('Search for new episodes FAILED!');
|
let fullType: string;
|
||||||
return;
|
if (hit.type == 'VOD_SERIES') {
|
||||||
}
|
fullType = `Z.${hit.id}`;
|
||||||
|
} else if (hit.type == 'VOD_VIDEO') {
|
||||||
const dashboardData = JSON.parse(dashboardReq.res.body) as HidiveDashboard;
|
fullType = `E.${hit.id}`;
|
||||||
const dashboardItems = dashboardData.Data.TitleRows;
|
} else {
|
||||||
const recentlyAddedIndex = dashboardItems.findIndex(item => item.Name == 'Recently Added');
|
fullType = `${hit.type} #${hit.id}`;
|
||||||
const recentlyAdded = recentlyAddedIndex >= 0 ? dashboardItems[recentlyAddedIndex] : undefined;
|
|
||||||
if (recentlyAdded) {
|
|
||||||
const searchItems = recentlyAdded?.Titles;
|
|
||||||
if(searchItems.length>0) {
|
|
||||||
console.info('[INFO] Recently Added:');
|
|
||||||
for(let i=0;i<searchItems.length;i++){
|
|
||||||
console.info(`[#${searchItems[i].Id}] ${searchItems[i].Name} [${searchItems[i].ShowInfoTitle}]`);
|
|
||||||
}
|
}
|
||||||
} else{
|
console.log(`[${fullType}] ${hit.name} ${hit.seasonsCount ? '('+hit.seasonsCount+' Seasons)' : ''}`);
|
||||||
console.warn('No new episodes found!');
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.warn('New episode category not found!');
|
|
||||||
}
|
}
|
||||||
|
return { isOk: true, value: searchItems.filter(a => a.type == 'VOD_SERIES').flatMap((a): SearchResponseItem => {
|
||||||
|
return {
|
||||||
|
id: a.id+'',
|
||||||
|
image: a.coverUrl ?? '/notFound.png',
|
||||||
|
name: a.name,
|
||||||
|
rating: -1,
|
||||||
|
desc: a.description
|
||||||
|
};
|
||||||
|
})};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getSeries(id: number) {
|
public async getSeries(id: number) {
|
||||||
|
|
@ -827,152 +497,6 @@ export default class Hidive implements ServiceClass {
|
||||||
return { isOk: true, value: selEpsArr, showData: getShowData.series };
|
return { isOk: true, value: selEpsArr, showData: getShowData.series };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async listShow(id: number) {
|
|
||||||
const getShowData = await this.reqData('GetTitle', { 'Id': id });
|
|
||||||
if (!getShowData.ok || !getShowData.res) {
|
|
||||||
console.error('Failed to get show data');
|
|
||||||
return { isOk: false };
|
|
||||||
}
|
|
||||||
const rawShowData = JSON.parse(getShowData.res.body) as HidiveEpisodeList;
|
|
||||||
const showData = rawShowData.Data.Title;
|
|
||||||
console.info(`[#${showData.Id}] ${showData.Name} [${showData.ShowInfoTitle}]`);
|
|
||||||
return { isOk: true, value: showData };
|
|
||||||
}
|
|
||||||
|
|
||||||
async getShow(id: number, e: string | undefined, but: boolean, all: boolean) {
|
|
||||||
const getShowData = await this.listShow(id);
|
|
||||||
if (!getShowData.isOk || !getShowData.value) {
|
|
||||||
return { isOk: false, value: [] };
|
|
||||||
}
|
|
||||||
const showData = getShowData.value;
|
|
||||||
const doEpsFilter = parseSelect(e as string);
|
|
||||||
// build selected episodes
|
|
||||||
const selEpsArr: HidiveEpisodeExtra[] = []; let ovaSeq = 1; let movieSeq = 1;
|
|
||||||
for (let i = 0; i < showData.Episodes.length; i++) {
|
|
||||||
const titleId = showData.Episodes[i].TitleId;
|
|
||||||
const epKey = showData.Episodes[i].VideoKey;
|
|
||||||
const seriesTitle = showData.Name;
|
|
||||||
let nameLong = showData.Episodes[i].DisplayNameLong;
|
|
||||||
if (nameLong.match(/OVA/i)) {
|
|
||||||
nameLong = 'ova' + (('0' + ovaSeq).slice(-2)); ovaSeq++;
|
|
||||||
}
|
|
||||||
else if (nameLong.match(/Theatrical/i)) {
|
|
||||||
nameLong = 'movie' + (('0' + movieSeq).slice(-2)); movieSeq++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nameLong = epKey;
|
|
||||||
}
|
|
||||||
let sumDub: string | RegExpMatchArray | null = showData.Episodes[i].Summary.match(/^Audio: (.*)/m);
|
|
||||||
sumDub = sumDub ? `\n - ${sumDub[0]}` : '';
|
|
||||||
let sumSub: string | RegExpMatchArray | null = showData.Episodes[i].Summary.match(/^Subtitles: (.*)/m);
|
|
||||||
sumSub = sumSub ? `\n - ${sumSub[0]}` : '';
|
|
||||||
let selMark = '';
|
|
||||||
if (all ||
|
|
||||||
but && !doEpsFilter.isSelected([parseFloat(showData.Episodes[i].EpisodeNumberValue+'')+'', showData.Episodes[i].Id+'']) ||
|
|
||||||
!but && doEpsFilter.isSelected([parseFloat(showData.Episodes[i].EpisodeNumberValue+'')+'', showData.Episodes[i].Id+''])
|
|
||||||
) {
|
|
||||||
selEpsArr.push({ isSelected: true, titleId, epKey, nameLong, seriesTitle, ...showData.Episodes[i] });
|
|
||||||
selMark = '✓ ';
|
|
||||||
}
|
|
||||||
//const epKeyTitle = !epKey.match(/e(\d+)$/) ? nameLong : epKey;
|
|
||||||
//const titleIdStr = (titleId != id ? `#${titleId}|` : '') + epKeyTitle;
|
|
||||||
//console.info(`[${titleIdStr}] ${showData.Episodes[i].Name}${selMark}${sumDub}${sumSub}`);
|
|
||||||
console.info('%s[%s] %s%s%s',
|
|
||||||
selMark,
|
|
||||||
'S'+parseFloat(showData.Episodes[i].SeasonNumberValue+'')+'E'+parseFloat(showData.Episodes[i].EpisodeNumberValue+''),
|
|
||||||
showData.Episodes[i].Name,
|
|
||||||
sumDub,
|
|
||||||
sumSub
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return { isOk: true, value: selEpsArr, showData: showData };
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getEpisode(selectedEpisode: HidiveEpisodeExtra, options: Record<any, any>) {
|
|
||||||
const getVideoData = await this.reqData('GetVideos', { 'VideoKey': selectedEpisode.epKey, 'TitleId': selectedEpisode.titleId });
|
|
||||||
if (getVideoData.ok && getVideoData.res) {
|
|
||||||
const videoData = JSON.parse(getVideoData.res.body) as HidiveVideoList;
|
|
||||||
const showTitle = `${selectedEpisode.seriesTitle} S${parseFloat(selectedEpisode.SeasonNumberValue+'')}`;
|
|
||||||
console.info(`[INFO] ${showTitle} - ${parseFloat(selectedEpisode.EpisodeNumberValue+'')}`);
|
|
||||||
const videoList = videoData.Data.VideoLanguages;
|
|
||||||
const subsList = videoData.Data.CaptionLanguages;
|
|
||||||
console.info('[INFO] Available dubs and subtitles:');
|
|
||||||
console.info('\tVideos: ' + videoList.join('\n\t\t'));
|
|
||||||
console.info('\tSubs : ' + subsList.join('\n\t\t'));
|
|
||||||
console.info(`[INFO] Selected dub(s): ${options.dubLang.join(', ')}`);
|
|
||||||
const videoUrls = videoData.Data.VideoUrls;
|
|
||||||
const subsUrls = videoData.Data.CaptionVttUrls;
|
|
||||||
const fontSize = videoData.Data.FontSize ? videoData.Data.FontSize : options.fontSize;
|
|
||||||
const subsSel = subsList;
|
|
||||||
//Get Selected Video URLs
|
|
||||||
const videoSel = videoList.sort().filter(videoLanguage =>
|
|
||||||
langsData.languages.find(a =>
|
|
||||||
a.hd_locale ? videoLanguage.match(a.hd_locale) &&
|
|
||||||
options.dubLang.includes(a.code) : false
|
|
||||||
)
|
|
||||||
);
|
|
||||||
//Prioritize Home Video, unless simul is used
|
|
||||||
videoSel.forEach(function(video, index) {
|
|
||||||
if (index > 0) {
|
|
||||||
const video1 = video.split(', ');
|
|
||||||
const video2 = videoSel[index - 1].split(', ');
|
|
||||||
if (video1[0] == video2[0]) {
|
|
||||||
if (video1[1] == 'Home Video' && video2[1] == 'Broadcast') {
|
|
||||||
options.simul ? videoSel.splice(index, 1) : videoSel.splice(index - 1, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (videoSel.length === 0) {
|
|
||||||
console.error('No suitable videos(s) found for options!');
|
|
||||||
}
|
|
||||||
//Build video array
|
|
||||||
const selectedVideoUrls: HidiveStreamInfo[] = [];
|
|
||||||
videoSel.forEach(function(video, index) {
|
|
||||||
const videodetails = videoSel[index].split(', ');
|
|
||||||
const videoinfo: HidiveStreamInfo = videoUrls[video];
|
|
||||||
videoinfo.language = videodetails[0];
|
|
||||||
videoinfo.episodeTitle = selectedEpisode.Name;
|
|
||||||
videoinfo.seriesTitle = selectedEpisode.seriesTitle;
|
|
||||||
videoinfo.season = parseFloat(selectedEpisode.SeasonNumberValue+'');
|
|
||||||
videoinfo.episodeNumber = parseFloat(selectedEpisode.EpisodeNumberValue+'');
|
|
||||||
videoinfo.uncut = videodetails[0] == 'Home Video' ? true : false;
|
|
||||||
videoinfo.image = selectedEpisode.ScreenShotSmallUrl;
|
|
||||||
console.info(`[INFO] Selected release: ${videodetails[0]} ${videodetails[1]}`);
|
|
||||||
selectedVideoUrls.push(videoinfo);
|
|
||||||
});
|
|
||||||
//Build subtitle array
|
|
||||||
const selectedSubUrls: HidiveSubtitleInfo[] = [];
|
|
||||||
subsSel.forEach(function(sub, index) {
|
|
||||||
console.info(subsSel[index]);
|
|
||||||
const subinfo = {
|
|
||||||
url: subsUrls[sub],
|
|
||||||
cc: subsSel[index].includes('Caps'),
|
|
||||||
language: subsSel[index].replace(' Subs', '').replace(' Caps', '')
|
|
||||||
};
|
|
||||||
selectedSubUrls.push(subinfo);
|
|
||||||
});
|
|
||||||
//download media list
|
|
||||||
const res = await this.downloadMediaList(selectedVideoUrls, selectedSubUrls, fontSize, options);
|
|
||||||
if (res === undefined || res.error) {
|
|
||||||
console.error('Failed to download media list');
|
|
||||||
return { isOk: false, reason: new Error('Failed to download media list') };
|
|
||||||
} else {
|
|
||||||
if (!options.skipmux) {
|
|
||||||
await this.muxStreams(res.data, { ...options, output: res.fileName });
|
|
||||||
} else {
|
|
||||||
console.info('Skipping mux');
|
|
||||||
}
|
|
||||||
downloaded({
|
|
||||||
service: 'hidive',
|
|
||||||
type: 's'
|
|
||||||
}, selectedEpisode.titleId+'', [selectedEpisode.EpisodeNumberValue+'']);
|
|
||||||
return { isOk: res, value: undefined };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { isOk: false, reason: new Error('Unknown download error') };
|
|
||||||
}
|
|
||||||
|
|
||||||
public async downloadEpisode(selectedEpisode: NewHidiveEpisodeExtra, options: Record<any, any>) {
|
public async downloadEpisode(selectedEpisode: NewHidiveEpisodeExtra, options: Record<any, any>) {
|
||||||
//Get Episode data
|
//Get Episode data
|
||||||
const episodeDataReq = await this.apiReq(`/v4/vod/${selectedEpisode.id}?includePlaybackDetails=URL`, '', 'auth', 'GET');
|
const episodeDataReq = await this.apiReq(`/v4/vod/${selectedEpisode.id}?includePlaybackDetails=URL`, '', 'auth', 'GET');
|
||||||
|
|
@ -1454,260 +978,6 @@ export default class Hidive implements ServiceClass {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async downloadMediaList(videoUrls: HidiveStreamInfo[], subUrls: HidiveSubtitleInfo[], fontSize: number, options: Record<any, any>) {
|
|
||||||
let mediaName = '...';
|
|
||||||
let fileName;
|
|
||||||
const files: DownloadedMedia[] = [];
|
|
||||||
const variables: Variable[] = [];
|
|
||||||
let dlFailed = false;
|
|
||||||
//let dlVideoOnce = false; // Variable to save if best selected video quality was downloaded
|
|
||||||
let subsMargin = 0;
|
|
||||||
let videoIndex = 0;
|
|
||||||
const chosenFontSize = options.originalFontSize ? fontSize : options.fontSize;
|
|
||||||
for (const videoData of videoUrls) {
|
|
||||||
if(videoData.seriesTitle && videoData.episodeNumber && videoData.episodeTitle){
|
|
||||||
mediaName = `${videoData.seriesTitle} - ${videoData.episodeNumber} - ${videoData.episodeTitle}`;
|
|
||||||
}
|
|
||||||
if(!options.novids && !dlFailed) {
|
|
||||||
console.info(`Requesting: ${mediaName}`);
|
|
||||||
console.info('Playlists URL: %s', videoData.hls[0]);
|
|
||||||
const streamPlaylistsReq = await this.req.getData(videoData.hls[0]);
|
|
||||||
if(!streamPlaylistsReq.ok || !streamPlaylistsReq.res){
|
|
||||||
console.error('CAN\'T FETCH VIDEO PLAYLISTS!');
|
|
||||||
return { error: true, data: []};
|
|
||||||
}
|
|
||||||
|
|
||||||
variables.push(...([
|
|
||||||
['title', videoData.episodeTitle, true],
|
|
||||||
['episode', isNaN(parseFloat(videoData.episodeNumber+'')) ? videoData.episodeNumber : parseFloat(videoData.episodeNumber+''), false],
|
|
||||||
['service', 'HD', false],
|
|
||||||
['showTitle', videoData.seriesTitle, true],
|
|
||||||
['season', videoData.season, false]
|
|
||||||
] as [AvailableFilenameVars, string|number, boolean][]).map((a): Variable => {
|
|
||||||
return {
|
|
||||||
name: a[0],
|
|
||||||
replaceWith: a[1],
|
|
||||||
type: typeof a[1],
|
|
||||||
sanitize: a[2]
|
|
||||||
} as Variable;
|
|
||||||
}));
|
|
||||||
|
|
||||||
const streamPlaylists = m3u8(streamPlaylistsReq.res.body);
|
|
||||||
const plServerList: string[] = [],
|
|
||||||
plStreams: Record<string, Record<string, string>> = {},
|
|
||||||
plQuality: {
|
|
||||||
str: string,
|
|
||||||
dim: string,
|
|
||||||
CODECS: string,
|
|
||||||
RESOLUTION: {
|
|
||||||
width: number,
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
}[] = [];
|
|
||||||
for (const pl of streamPlaylists.playlists) {
|
|
||||||
// set quality
|
|
||||||
const plResolution = pl.attributes.RESOLUTION;
|
|
||||||
const plResolutionText = `${plResolution.width}x${plResolution.height}`;
|
|
||||||
// set codecs
|
|
||||||
const plCodecs = pl.attributes.CODECS;
|
|
||||||
// parse uri
|
|
||||||
const plUri = new URL(pl.uri);
|
|
||||||
let plServer = plUri.hostname;
|
|
||||||
// set server list
|
|
||||||
if (plUri.searchParams.get('cdn')) {
|
|
||||||
plServer += ` (${plUri.searchParams.get('cdn')})`;
|
|
||||||
}
|
|
||||||
if (!plServerList.includes(plServer)) {
|
|
||||||
plServerList.push(plServer);
|
|
||||||
}
|
|
||||||
// add to server
|
|
||||||
if (!Object.keys(plStreams).includes(plServer)) {
|
|
||||||
plStreams[plServer] = {};
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
plStreams[plServer][plResolutionText]
|
|
||||||
&& plStreams[plServer][plResolutionText] != pl.uri
|
|
||||||
&& typeof plStreams[plServer][plResolutionText] != 'undefined'
|
|
||||||
) {
|
|
||||||
console.error(`Non duplicate url for ${plServer} detected, please report to developer!`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
plStreams[plServer][plResolutionText] = pl.uri;
|
|
||||||
}
|
|
||||||
// set plQualityStr
|
|
||||||
const plBandwidth = Math.round(pl.attributes.BANDWIDTH / 1024);
|
|
||||||
const qualityStrAdd = `${plResolutionText} (${plBandwidth}KiB/s)`;
|
|
||||||
const qualityStrRegx = new RegExp(qualityStrAdd.replace(/(:|\(|\)|\/)/g, '\\$1'), 'm');
|
|
||||||
const qualityStrMatch = !plQuality.map(a => a.str).join('\r\n').match(qualityStrRegx);
|
|
||||||
if (qualityStrMatch) {
|
|
||||||
plQuality.push({
|
|
||||||
str: qualityStrAdd,
|
|
||||||
dim: plResolutionText,
|
|
||||||
CODECS: plCodecs,
|
|
||||||
RESOLUTION: plResolution
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.x = options.x > plServerList.length ? 1 : options.x;
|
|
||||||
|
|
||||||
const plSelectedServer = plServerList[options.x - 1];
|
|
||||||
const plSelectedList = plStreams[plSelectedServer];
|
|
||||||
plQuality.sort((a, b) => {
|
|
||||||
const aMatch: RegExpMatchArray | never[] = a.dim.match(/[0-9]+/) || [];
|
|
||||||
const bMatch: RegExpMatchArray | never[] = b.dim.match(/[0-9]+/) || [];
|
|
||||||
return parseInt(aMatch[0]) - parseInt(bMatch[0]);
|
|
||||||
});
|
|
||||||
let quality = options.q === 0 ? plQuality.length : options.q;
|
|
||||||
if(quality > plQuality.length) {
|
|
||||||
console.warn(`The requested quality of ${options.q} is greater than the maximun ${plQuality.length}.\n[WARN] Therefor the maximum will be capped at ${plQuality.length}.`);
|
|
||||||
quality = plQuality.length;
|
|
||||||
}
|
|
||||||
const selPlUrl = plSelectedList[plQuality.map(a => a.dim)[quality - 1]] ? plSelectedList[plQuality.map(a => a.dim)[quality - 1]] : '';
|
|
||||||
console.info(`Servers available:\n\t${plServerList.join('\n\t')}`);
|
|
||||||
console.info(`Available qualities:\n\t${plQuality.map((a, ind) => `[${ind+1}] ${a.str}`).join('\n\t')}`);
|
|
||||||
if(selPlUrl != '') {
|
|
||||||
variables.push({
|
|
||||||
name: 'height',
|
|
||||||
type: 'number',
|
|
||||||
replaceWith: quality === 0 ? plQuality[plQuality.length - 1].RESOLUTION.height as number : plQuality[quality - 1].RESOLUTION.height
|
|
||||||
}, {
|
|
||||||
name: 'width',
|
|
||||||
type: 'number',
|
|
||||||
replaceWith: quality === 0 ? plQuality[plQuality.length - 1].RESOLUTION.width as number : plQuality[quality - 1].RESOLUTION.width
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const lang = langsData.languages.find(a => a.hd_locale === videoData.language);
|
|
||||||
if (!lang) {
|
|
||||||
console.error(`Unable to find language for code ${videoData.language}`);
|
|
||||||
return { error: true, data: [] };
|
|
||||||
}
|
|
||||||
console.info(`Selected quality: ${Object.keys(plSelectedList).find(a => plSelectedList[a] === selPlUrl)} @ ${plSelectedServer}`);
|
|
||||||
console.info('Stream URL:', selPlUrl);
|
|
||||||
// TODO check filename
|
|
||||||
const outFile = parseFileName(options.fileName + '.' + lang.name + '.' + videoIndex, variables, options.numbers, options.override).join(path.sep);
|
|
||||||
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
|
|
||||||
console.info(`Output filename: ${outFile}`);
|
|
||||||
const chunkPage = await this.req.getData(selPlUrl);
|
|
||||||
if(!chunkPage.ok || !chunkPage.res){
|
|
||||||
console.error('CAN\'T FETCH VIDEO PLAYLIST!');
|
|
||||||
dlFailed = true;
|
|
||||||
} else {
|
|
||||||
const chunkPlaylist = m3u8(chunkPage.res.body);
|
|
||||||
//TODO: look into how to keep bumpers without the video being affected
|
|
||||||
if(chunkPlaylist.segments[0].uri.match(/\/bumpers\//) && options.removeBumpers){
|
|
||||||
subsMargin = chunkPlaylist.segments[0].duration;
|
|
||||||
chunkPlaylist.segments.splice(0, 1);
|
|
||||||
}
|
|
||||||
const totalParts = chunkPlaylist.segments.length;
|
|
||||||
const mathParts = Math.ceil(totalParts / options.partsize);
|
|
||||||
const mathMsg = `(${mathParts}*${options.partsize})`;
|
|
||||||
console.info('Total parts in stream:', totalParts, mathMsg);
|
|
||||||
const tsFile = path.isAbsolute(outFile as string) ? outFile : path.join(this.cfg.dir.content, outFile);
|
|
||||||
const split = outFile.split(path.sep).slice(0, -1);
|
|
||||||
split.forEach((val, ind, arr) => {
|
|
||||||
const isAbsolut = path.isAbsolute(outFile as string);
|
|
||||||
if (!fs.existsSync(path.join(isAbsolut ? '' : this.cfg.dir.content, ...arr.slice(0, ind), val)))
|
|
||||||
fs.mkdirSync(path.join(isAbsolut ? '' : this.cfg.dir.content, ...arr.slice(0, ind), val));
|
|
||||||
});
|
|
||||||
const dlStreamByPl = await new streamdl({
|
|
||||||
output: `${tsFile}.ts`,
|
|
||||||
timeout: options.timeout,
|
|
||||||
m3u8json: chunkPlaylist,
|
|
||||||
// baseurl: chunkPlaylist.baseUrl,
|
|
||||||
threads: options.partsize,
|
|
||||||
fsRetryTime: options.fsRetryTime * 1000,
|
|
||||||
override: options.force,
|
|
||||||
callback: options.callbackMaker ? options.callbackMaker({
|
|
||||||
fileName: `${path.isAbsolute(outFile) ? outFile.slice(this.cfg.dir.content.length) : outFile}`,
|
|
||||||
image: videoData.image,
|
|
||||||
parent: {
|
|
||||||
title: videoData.seriesTitle
|
|
||||||
},
|
|
||||||
title: videoData.episodeTitle,
|
|
||||||
language: lang
|
|
||||||
}) : undefined
|
|
||||||
}).download();
|
|
||||||
if (!dlStreamByPl.ok) {
|
|
||||||
console.error(`DL Stats: ${JSON.stringify(dlStreamByPl.parts)}\n`);
|
|
||||||
dlFailed = true;
|
|
||||||
}
|
|
||||||
files.push({
|
|
||||||
type: 'Video',
|
|
||||||
path: `${tsFile}.ts`,
|
|
||||||
lang: lang,
|
|
||||||
uncut: videoData.uncut
|
|
||||||
});
|
|
||||||
//dlVideoOnce = true;
|
|
||||||
}
|
|
||||||
} else if(options.novids){
|
|
||||||
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
|
|
||||||
console.info('Downloading skipped!');
|
|
||||||
}
|
|
||||||
videoIndex++;
|
|
||||||
await this.sleep(options.waittime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(options.dlsubs.indexOf('all') > -1){
|
|
||||||
options.dlsubs = ['all'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.nosubs) {
|
|
||||||
console.info('Subtitles downloading disabled from nosubs flag.');
|
|
||||||
options.skipsubs = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!options.skipsubs && options.dlsubs.indexOf('none') == -1) {
|
|
||||||
if(subUrls.length > 0) {
|
|
||||||
let subIndex = 0;
|
|
||||||
for(const sub of subUrls) {
|
|
||||||
const subLang = langsData.languages.find(a => a.hd_locale === sub.language);
|
|
||||||
if (!subLang) {
|
|
||||||
console.warn(`Language not found for subtitle language: ${sub.language}, Skipping`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const sxData: Partial<sxItem> = {};
|
|
||||||
sxData.file = langsData.subsFile(fileName as string, subIndex+'', subLang, sub.cc, options.ccTag);
|
|
||||||
sxData.path = path.join(this.cfg.dir.content, sxData.file);
|
|
||||||
sxData.language = subLang;
|
|
||||||
if(options.dlsubs.includes('all') || options.dlsubs.includes(subLang.locale)){
|
|
||||||
const subs4XUrl = sub.url.split('/');
|
|
||||||
const subsXUrl = subs4XUrl[subs4XUrl.length - 1].replace(/.vtt$/, '');
|
|
||||||
const getCssContent = await this.req.getData(await this.genSubsUrl('css', subsXUrl));
|
|
||||||
const getVttContent = await this.req.getData(await this.genSubsUrl('vtt', subsXUrl));
|
|
||||||
if (getCssContent.ok && getVttContent.ok && getCssContent.res && getVttContent.res) {
|
|
||||||
console.info(`Subtitle Downloaded: ${await this.genSubsUrl('vtt', subsXUrl)}`);
|
|
||||||
//vttConvert(getVttContent.res.body, false, subLang.name, fontSize);
|
|
||||||
const sBody = vtt2ass(undefined, chosenFontSize, getVttContent.res.body, getCssContent.res.body, subsMargin, options.fontName, options.combineLines);
|
|
||||||
sxData.title = `${subLang.language} / ${sxData.title}`;
|
|
||||||
sxData.fonts = fontsData.assFonts(sBody) as Font[];
|
|
||||||
fs.writeFileSync(sxData.path, sBody);
|
|
||||||
console.info(`Subtitle Converted: ${sxData.file}`);
|
|
||||||
files.push({
|
|
||||||
type: 'Subtitle',
|
|
||||||
...sxData as sxItem,
|
|
||||||
cc: sub.cc
|
|
||||||
});
|
|
||||||
} else{
|
|
||||||
console.warn(`Failed to download subtitle: ${sxData.file}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subIndex++;
|
|
||||||
}
|
|
||||||
} else{
|
|
||||||
console.warn('Can\'t find urls for subtitles!');
|
|
||||||
}
|
|
||||||
} else{
|
|
||||||
console.info('Subtitles downloading skipped!');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
error: dlFailed,
|
|
||||||
data: files,
|
|
||||||
fileName: fileName ? (path.isAbsolute(fileName) ? fileName : path.join(this.cfg.dir.content, fileName)) || './unknown' : './unknown'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async muxStreams(data: DownloadedMedia[], options: Record<any, any>, inverseTrackOrder: boolean = true) {
|
public async muxStreams(data: DownloadedMedia[], options: Record<any, any>, inverseTrackOrder: boolean = true) {
|
||||||
this.cfg.bin = await yamlCfg.loadBinCfg();
|
this.cfg.bin = await yamlCfg.loadBinCfg();
|
||||||
let hasAudioStreams = false;
|
let hasAudioStreams = false;
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,6 @@ 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,20 +230,6 @@ const args: TAppArg<boolean|number|string|unknown[]>[] = [
|
||||||
default: 'web'
|
default: 'web'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
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: 'new'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'removeBumpers',
|
name: 'removeBumpers',
|
||||||
describe: 'Remove bumpers from final video',
|
describe: 'Remove bumpers from final video',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue