New Logger + HLS-Download include
This commit is contained in:
parent
8f9a21c773
commit
42ac8d85c4
52 changed files with 1076 additions and 782 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
lib
|
lib
|
||||||
/videos/*.ts
|
/videos/*.ts
|
||||||
build
|
build
|
||||||
dev.js
|
dev.js
|
||||||
|
tsc.ts
|
||||||
|
|
@ -21,7 +21,17 @@
|
||||||
"react",
|
"react",
|
||||||
"@typescript-eslint"
|
"@typescript-eslint"
|
||||||
],
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["gui/react/**/*"],
|
||||||
|
"rules": {
|
||||||
|
"no-console": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"no-console": 2,
|
||||||
|
"react/prop-types": 0,
|
||||||
"react-hooks/exhaustive-deps": 0,
|
"react-hooks/exhaustive-deps": 0,
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"indent": [
|
"indent": [
|
||||||
|
|
|
||||||
32
@types/hls-download.d.ts
vendored
32
@types/hls-download.d.ts
vendored
|
|
@ -1,32 +0,0 @@
|
||||||
declare module 'hls-download' {
|
|
||||||
import type { ProgressData } from './messageHandler';
|
|
||||||
export type HLSCallback = (data: ProgressData) => unknown;
|
|
||||||
export type HLSOptions = {
|
|
||||||
m3u8json: {
|
|
||||||
segments: Record<string, unknown>[],
|
|
||||||
mediaSequence?: number,
|
|
||||||
},
|
|
||||||
output?: string,
|
|
||||||
threads?: number,
|
|
||||||
retries?: number,
|
|
||||||
offset?: number,
|
|
||||||
baseurl?: string,
|
|
||||||
proxy?: string,
|
|
||||||
skipInit?: boolean,
|
|
||||||
timeout?: number,
|
|
||||||
fsRetryTime?: number,
|
|
||||||
override?: 'Y'|'y'|'N'|'n'|'C'|'c'
|
|
||||||
callback?: HLSCallback
|
|
||||||
}
|
|
||||||
export default class hlsDownload {
|
|
||||||
constructor(options: HLSOptions)
|
|
||||||
async download() : Promise<{
|
|
||||||
ok: boolean,
|
|
||||||
parts: {
|
|
||||||
first: number,
|
|
||||||
total: number,
|
|
||||||
compleated: number
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
233
crunchy.ts
233
crunchy.ts
|
|
@ -5,9 +5,10 @@ import fs from 'fs-extra';
|
||||||
// package program
|
// package program
|
||||||
import packageJson from './package.json';
|
import packageJson from './package.json';
|
||||||
// plugins
|
// plugins
|
||||||
|
import { console } from './modules/log';
|
||||||
import shlp from 'sei-helper';
|
import shlp from 'sei-helper';
|
||||||
import m3u8 from 'm3u8-parsed';
|
import m3u8 from 'm3u8-parsed';
|
||||||
import streamdl from 'hls-download';
|
import streamdl from './modules/hls-download';
|
||||||
|
|
||||||
// custom modules
|
// custom modules
|
||||||
import * as fontsData from './modules/module.fontsData';
|
import * as fontsData from './modules/module.fontsData';
|
||||||
|
|
@ -60,7 +61,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async cli() {
|
public async cli() {
|
||||||
console.log(`\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);
|
||||||
|
|
||||||
// load binaries
|
// load binaries
|
||||||
|
|
@ -103,7 +104,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
if (selected.isOk) {
|
if (selected.isOk) {
|
||||||
for (const select of selected.value) {
|
for (const select of selected.value) {
|
||||||
if (!(await this.downloadEpisode(select, {...argv, skipsubs: false }))) {
|
if (!(await this.downloadEpisode(select, {...argv, skipsubs: false }))) {
|
||||||
console.log(`[ERROR] Unable to download selected episode ${select.episodeNumber}`);
|
console.error(`Unable to download selected episode ${select.episodeNumber}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,14 +118,14 @@ export default class Crunchy implements ServiceClass {
|
||||||
else if(argv.s && argv.s.match(/^[0-9A-Z]{9}$/)){
|
else if(argv.s && argv.s.match(/^[0-9A-Z]{9}$/)){
|
||||||
await this.refreshToken();
|
await this.refreshToken();
|
||||||
if (argv.dubLang.length > 1) {
|
if (argv.dubLang.length > 1) {
|
||||||
console.log('[INFO] One show can only be downloaded with one dub. Use --srz instead.');
|
console.info('One show can only be downloaded with one dub. Use --srz instead.');
|
||||||
}
|
}
|
||||||
argv.dubLang = [argv.dubLang[0]];
|
argv.dubLang = [argv.dubLang[0]];
|
||||||
const selected = await this.getSeasonById(argv.s, argv.numbers, argv.e, argv.but, argv.all);
|
const selected = await this.getSeasonById(argv.s, argv.numbers, argv.e, argv.but, argv.all);
|
||||||
if (selected.isOk) {
|
if (selected.isOk) {
|
||||||
for (const select of selected.value) {
|
for (const select of selected.value) {
|
||||||
if (!(await this.downloadEpisode(select, {...argv, skipsubs: false }))) {
|
if (!(await this.downloadEpisode(select, {...argv, skipsubs: false }))) {
|
||||||
console.log(`[ERROR] Unable to download selected episode ${select.episodeNumber}`);
|
console.error(`Unable to download selected episode ${select.episodeNumber}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -136,24 +137,24 @@ export default class Crunchy implements ServiceClass {
|
||||||
const selected = await this.getObjectById(argv.e, false);
|
const selected = await this.getObjectById(argv.e, false);
|
||||||
for (const select of selected as Partial<CrunchyEpMeta>[]) {
|
for (const select of selected as Partial<CrunchyEpMeta>[]) {
|
||||||
if (!(await this.downloadEpisode(select as CrunchyEpMeta, {...argv, skipsubs: false }))) {
|
if (!(await this.downloadEpisode(select as CrunchyEpMeta, {...argv, skipsubs: false }))) {
|
||||||
console.log(`[ERROR] Unable to download selected episode ${select.episodeNumber}`);
|
console.error(`Unable to download selected episode ${select.episodeNumber}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log('[INFO] No option selected or invalid value entered. Try --help.');
|
console.info('No option selected or invalid value entered. Try --help.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getFonts() {
|
public async getFonts() {
|
||||||
console.log('[INFO] Downloading fonts...');
|
console.info('Downloading fonts...');
|
||||||
const fonts = Object.values(fontsData.fontFamilies).reduce((pre, curr) => pre.concat(curr));
|
const fonts = Object.values(fontsData.fontFamilies).reduce((pre, curr) => pre.concat(curr));
|
||||||
for(const f of fonts) {
|
for(const f of fonts) {
|
||||||
const fontLoc = path.join(this.cfg.dir.fonts, f);
|
const fontLoc = path.join(this.cfg.dir.fonts, f);
|
||||||
if(fs.existsSync(fontLoc) && fs.statSync(fontLoc).size != 0){
|
if(fs.existsSync(fontLoc) && fs.statSync(fontLoc).size != 0){
|
||||||
console.log(`[INFO] ${f} already downloaded!`);
|
console.info(`${f} already downloaded!`);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
const fontFolder = path.dirname(fontLoc);
|
const fontFolder = path.dirname(fontLoc);
|
||||||
|
|
@ -164,20 +165,20 @@ export default class Crunchy implements ServiceClass {
|
||||||
fs.ensureDirSync(fontFolder);
|
fs.ensureDirSync(fontFolder);
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
console.log();
|
console.info('');
|
||||||
}
|
}
|
||||||
const fontUrl = fontsData.root + f;
|
const fontUrl = fontsData.root + f;
|
||||||
const getFont = await this.req.getData<Buffer>(fontUrl, { binary: true });
|
const getFont = await this.req.getData<Buffer>(fontUrl, { binary: true });
|
||||||
if(getFont.ok && getFont.res){
|
if(getFont.ok && getFont.res){
|
||||||
fs.writeFileSync(fontLoc, getFont.res.body);
|
fs.writeFileSync(fontLoc, getFont.res.body);
|
||||||
console.log(`[INFO] Downloaded: ${f}`);
|
console.info(`Downloaded: ${f}`);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log(`[WARN] Failed to download: ${f}`);
|
console.warn(`Failed to download: ${f}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('[INFO] All required fonts downloaded!');
|
console.info('All required fonts downloaded!');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async doAuth(data: AuthData): Promise<AuthResponse> {
|
public async doAuth(data: AuthData): Promise<AuthResponse> {
|
||||||
|
|
@ -194,14 +195,14 @@ export default class Crunchy implements ServiceClass {
|
||||||
};
|
};
|
||||||
const authReq = await this.req.getData(api.beta_auth, authReqOpts);
|
const authReq = await this.req.getData(api.beta_auth, authReqOpts);
|
||||||
if(!authReq.ok || !authReq.res){
|
if(!authReq.ok || !authReq.res){
|
||||||
console.log('[ERROR] Authentication failed!');
|
console.error('Authentication failed!');
|
||||||
return { isOk: false, reason: new Error('Authentication failed') };
|
return { isOk: false, reason: new Error('Authentication failed') };
|
||||||
}
|
}
|
||||||
this.token = JSON.parse(authReq.res.body);
|
this.token = JSON.parse(authReq.res.body);
|
||||||
this.token.expires = new Date(Date.now() + this.token.expires_in);
|
this.token.expires = new Date(Date.now() + this.token.expires_in);
|
||||||
yamlCfg.saveCRToken(this.token);
|
yamlCfg.saveCRToken(this.token);
|
||||||
await this.getProfile();
|
await this.getProfile();
|
||||||
console.log('[INFO] Your Country: %s', this.token.country);
|
console.info('Your Country: %s', this.token.country);
|
||||||
return { isOk: true, value: undefined };
|
return { isOk: true, value: undefined };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +218,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
};
|
};
|
||||||
const authReq = await this.req.getData(api.beta_auth, authReqOpts);
|
const authReq = await this.req.getData(api.beta_auth, authReqOpts);
|
||||||
if(!authReq.ok || !authReq.res){
|
if(!authReq.ok || !authReq.res){
|
||||||
console.log('[ERROR] Authentication failed!');
|
console.error('Authentication failed!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.token = JSON.parse(authReq.res.body);
|
this.token = JSON.parse(authReq.res.body);
|
||||||
|
|
@ -227,7 +228,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
public async getProfile() : Promise<boolean> {
|
public async getProfile() : Promise<boolean> {
|
||||||
if(!this.token.access_token){
|
if(!this.token.access_token){
|
||||||
console.log('[ERROR] No access token!');
|
console.error('No access token!');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const profileReqOptions = {
|
const profileReqOptions = {
|
||||||
|
|
@ -238,11 +239,11 @@ export default class Crunchy implements ServiceClass {
|
||||||
};
|
};
|
||||||
const profileReq = await this.req.getData(api.beta_profile, profileReqOptions);
|
const profileReq = await this.req.getData(api.beta_profile, profileReqOptions);
|
||||||
if(!profileReq.ok || !profileReq.res){
|
if(!profileReq.ok || !profileReq.res){
|
||||||
console.log('[ERROR] Get profile failed!');
|
console.error('Get profile failed!');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const profile = JSON.parse(profileReq.res.body);
|
const profile = JSON.parse(profileReq.res.body);
|
||||||
console.log('[INFO] USER: %s (%s)', profile.username, profile.email);
|
console.info('USER: %s (%s)', profile.username, profile.email);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,7 +257,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
if (!(Date.now() > new Date(this.token.expires).getTime()) && ifNeeded) {
|
if (!(Date.now() > new Date(this.token.expires).getTime()) && ifNeeded) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
//console.log('[WARN] The token has expired compleatly. I will try to refresh the token anyway, but you might have to reauth.');
|
//console.info('[WARN] The token has expired compleatly. I will try to refresh the token anyway, but you might have to reauth.');
|
||||||
}
|
}
|
||||||
const authData = new URLSearchParams({
|
const authData = new URLSearchParams({
|
||||||
'refresh_token': this.token.refresh_token,
|
'refresh_token': this.token.refresh_token,
|
||||||
|
|
@ -270,7 +271,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
};
|
};
|
||||||
const authReq = await this.req.getData(api.beta_auth, authReqOpts);
|
const authReq = await this.req.getData(api.beta_auth, authReqOpts);
|
||||||
if(!authReq.ok || !authReq.res){
|
if(!authReq.ok || !authReq.res){
|
||||||
console.log('[ERROR] Authentication failed!');
|
console.error('Authentication failed!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.token = JSON.parse(authReq.res.body);
|
this.token = JSON.parse(authReq.res.body);
|
||||||
|
|
@ -281,14 +282,14 @@ export default class Crunchy implements ServiceClass {
|
||||||
if (!silent)
|
if (!silent)
|
||||||
await this.getProfile();
|
await this.getProfile();
|
||||||
} else {
|
} else {
|
||||||
console.log('[INFO] USER: Anonymous');
|
console.info('USER: Anonymous');
|
||||||
}
|
}
|
||||||
await this.getCMStoken();
|
await this.getCMStoken();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getCMStoken(){
|
public async getCMStoken(){
|
||||||
if(!this.token.access_token){
|
if(!this.token.access_token){
|
||||||
console.log('[ERROR] No access token!');
|
console.error('No access token!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cmsTokenReqOpts = {
|
const cmsTokenReqOpts = {
|
||||||
|
|
@ -299,17 +300,17 @@ export default class Crunchy implements ServiceClass {
|
||||||
};
|
};
|
||||||
const cmsTokenReq = await this.req.getData(api.beta_cmsToken, cmsTokenReqOpts);
|
const cmsTokenReq = await this.req.getData(api.beta_cmsToken, cmsTokenReqOpts);
|
||||||
if(!cmsTokenReq.ok || !cmsTokenReq.res){
|
if(!cmsTokenReq.ok || !cmsTokenReq.res){
|
||||||
console.log('[ERROR] Authentication CMS token failed!');
|
console.error('Authentication CMS token failed!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.cmsToken = JSON.parse(cmsTokenReq.res.body);
|
this.cmsToken = JSON.parse(cmsTokenReq.res.body);
|
||||||
console.log('[INFO] Your Country: %s\n', this.cmsToken.cms?.bucket.split('/')[1]);
|
console.info('Your Country: %s\n', this.cmsToken.cms?.bucket.split('/')[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getCmsData(){
|
public async getCmsData(){
|
||||||
// check token
|
// check token
|
||||||
if(!this.cmsToken.cms){
|
if(!this.cmsToken.cms){
|
||||||
console.log('[ERROR] Authentication required!');
|
console.error('Authentication required!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// opts
|
// opts
|
||||||
|
|
@ -325,15 +326,15 @@ export default class Crunchy implements ServiceClass {
|
||||||
].join('');
|
].join('');
|
||||||
const indexReq = await this.req.getData(indexReqOpts);
|
const indexReq = await this.req.getData(indexReqOpts);
|
||||||
if(!indexReq.ok || ! indexReq.res){
|
if(!indexReq.ok || ! indexReq.res){
|
||||||
console.log('[ERROR] Get CMS index FAILED!');
|
console.error('Get CMS index FAILED!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(JSON.parse(indexReq.res.body));
|
console.info(JSON.parse(indexReq.res.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async doSearch(data: SearchData): Promise<SearchResponse>{
|
public async doSearch(data: SearchData): Promise<SearchResponse>{
|
||||||
if(!this.token.access_token){
|
if(!this.token.access_token){
|
||||||
console.log('[ERROR] Authentication required!');
|
console.error('Authentication required!');
|
||||||
return { isOk: false, reason: new Error('Not authenticated') };
|
return { isOk: false, reason: new Error('Not authenticated') };
|
||||||
}
|
}
|
||||||
const searchReqOpts = {
|
const searchReqOpts = {
|
||||||
|
|
@ -352,12 +353,12 @@ export default class Crunchy implements ServiceClass {
|
||||||
}).toString();
|
}).toString();
|
||||||
const searchReq = await this.req.getData(`${api.search}?${searchParams}`, searchReqOpts);
|
const searchReq = await this.req.getData(`${api.search}?${searchParams}`, searchReqOpts);
|
||||||
if(!searchReq.ok || ! searchReq.res){
|
if(!searchReq.ok || ! searchReq.res){
|
||||||
console.log('[ERROR] Search FAILED!');
|
console.error('Search FAILED!');
|
||||||
return { isOk: false, reason: new Error('Search failed. No more information provided') };
|
return { isOk: false, reason: new Error('Search failed. No more information provided') };
|
||||||
}
|
}
|
||||||
const searchResults = JSON.parse(searchReq.res.body) as CrunchySearch;
|
const searchResults = JSON.parse(searchReq.res.body) as CrunchySearch;
|
||||||
if(searchResults.total < 1){
|
if(searchResults.total < 1){
|
||||||
console.log('[INFO] Nothing Found!');
|
console.info('Nothing Found!');
|
||||||
return { isOk: true, value: [] };
|
return { isOk: true, value: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,23 +369,23 @@ export default class Crunchy implements ServiceClass {
|
||||||
'episode': 'Found episodes'
|
'episode': 'Found episodes'
|
||||||
};
|
};
|
||||||
for(const search_item of searchResults.data){
|
for(const search_item of searchResults.data){
|
||||||
console.log('[INFO] %s:', searchTypesInfo[search_item.type as keyof typeof searchTypesInfo]);
|
console.info('%s:', searchTypesInfo[search_item.type as keyof typeof searchTypesInfo]);
|
||||||
// calculate pages
|
// calculate pages
|
||||||
const pageCur = searchStart > 0 ? Math.ceil(searchStart/5) + 1 : 1;
|
const pageCur = searchStart > 0 ? Math.ceil(searchStart/5) + 1 : 1;
|
||||||
const pageMax = Math.ceil(search_item.count/5);
|
const pageMax = Math.ceil(search_item.count/5);
|
||||||
// pages per category
|
// pages per category
|
||||||
if(search_item.count < 1){
|
if(search_item.count < 1){
|
||||||
console.log(' [INFO] Nothing Found...');
|
console.info(' Nothing Found...');
|
||||||
}
|
}
|
||||||
if(search_item.count > 0){
|
if(search_item.count > 0){
|
||||||
if(pageCur > pageMax){
|
if(pageCur > pageMax){
|
||||||
console.log(' [INFO] Last page is %s...', pageMax);
|
console.info(' Last page is %s...', pageMax);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for(const item of search_item.items){
|
for(const item of search_item.items){
|
||||||
await this.logObject(item);
|
await this.logObject(item);
|
||||||
}
|
}
|
||||||
console.log(` [INFO] Total results: ${search_item.count} (Page: ${pageCur}/${pageMax})`);
|
console.info(` Total results: ${search_item.count} (Page: ${pageCur}/${pageMax})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const toSend = searchResults.data.filter(a => a.type === 'series' || a.type === 'movie_listing');
|
const toSend = searchResults.data.filter(a => a.type === 'series' || a.type === 'movie_listing');
|
||||||
|
|
@ -404,7 +405,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
public async logObject(item: ParseItem, pad?: number, getSeries?: boolean, getMovieListing?: boolean){
|
public async logObject(item: ParseItem, pad?: number, getSeries?: boolean, getMovieListing?: boolean){
|
||||||
if(this.debug){
|
if(this.debug){
|
||||||
console.log(item);
|
console.info(item);
|
||||||
}
|
}
|
||||||
pad = pad ?? 2;
|
pad = pad ?? 2;
|
||||||
getSeries = getSeries === undefined ? true : getSeries;
|
getSeries = getSeries === undefined ? true : getSeries;
|
||||||
|
|
@ -542,7 +543,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
// show entry
|
// show entry
|
||||||
console.log(
|
console.info(
|
||||||
'%s%s[%s] %s%s%s',
|
'%s%s[%s] %s%s%s',
|
||||||
''.padStart(item.isSelected ? pad-1 : pad, ' '),
|
''.padStart(item.isSelected ? pad-1 : pad, ' '),
|
||||||
item.isSelected ? '✓' : '',
|
item.isSelected ? '✓' : '',
|
||||||
|
|
@ -553,27 +554,27 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
);
|
);
|
||||||
if(item.last_public){
|
if(item.last_public){
|
||||||
console.log(''.padStart(pad+1, ' '), '- Last updated:', item.last_public);
|
console.info(''.padStart(pad+1, ' '), '- Last updated:', item.last_public);
|
||||||
}
|
}
|
||||||
if(item.subtitle_locales){
|
if(item.subtitle_locales){
|
||||||
iMetadata.subtitle_locales = item.subtitle_locales;
|
iMetadata.subtitle_locales = item.subtitle_locales;
|
||||||
}
|
}
|
||||||
if (item.versions && audio_languages.length > 0) {
|
if (item.versions && audio_languages.length > 0) {
|
||||||
console.log(
|
console.info(
|
||||||
'%s- Versions: %s',
|
'%s- Versions: %s',
|
||||||
''.padStart(pad + 2, ' '),
|
''.padStart(pad + 2, ' '),
|
||||||
langsData.parseSubtitlesArray(audio_languages)
|
langsData.parseSubtitlesArray(audio_languages)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if(iMetadata.subtitle_locales && iMetadata.subtitle_locales.length > 0){
|
if(iMetadata.subtitle_locales && iMetadata.subtitle_locales.length > 0){
|
||||||
console.log(
|
console.info(
|
||||||
'%s- Subtitles: %s',
|
'%s- Subtitles: %s',
|
||||||
''.padStart(pad + 2, ' '),
|
''.padStart(pad + 2, ' '),
|
||||||
langsData.parseSubtitlesArray(iMetadata.subtitle_locales)
|
langsData.parseSubtitlesArray(iMetadata.subtitle_locales)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if(item.availability_notes){
|
if(item.availability_notes){
|
||||||
console.log(
|
console.info(
|
||||||
'%s- Availability notes: %s',
|
'%s- Availability notes: %s',
|
||||||
''.padStart(pad + 2, ' '),
|
''.padStart(pad + 2, ' '),
|
||||||
item.availability_notes.replace(/\[[^\]]*\]?/gm, '')
|
item.availability_notes.replace(/\[[^\]]*\]?/gm, '')
|
||||||
|
|
@ -581,11 +582,11 @@ export default class Crunchy implements ServiceClass {
|
||||||
}
|
}
|
||||||
if(item.type == 'series' && getSeries){
|
if(item.type == 'series' && getSeries){
|
||||||
await this.logSeriesById(item.id, pad, true);
|
await this.logSeriesById(item.id, pad, true);
|
||||||
console.log();
|
console.info('');
|
||||||
}
|
}
|
||||||
if(item.type == 'movie_listing' && getMovieListing){
|
if(item.type == 'movie_listing' && getMovieListing){
|
||||||
await this.logMovieListingById(item.id, pad+2);
|
await this.logMovieListingById(item.id, pad+2);
|
||||||
console.log();
|
console.info('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -595,7 +596,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
hideSeriesTitle = hideSeriesTitle !== undefined ? hideSeriesTitle : false;
|
hideSeriesTitle = hideSeriesTitle !== undefined ? hideSeriesTitle : false;
|
||||||
// check token
|
// check token
|
||||||
if(!this.cmsToken.cms){
|
if(!this.cmsToken.cms){
|
||||||
console.log('[ERROR] Authentication required!');
|
console.error('Authentication required!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// opts
|
// opts
|
||||||
|
|
@ -609,7 +610,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
if(!hideSeriesTitle){
|
if(!hideSeriesTitle){
|
||||||
const seriesReq = await this.req.getData(`${api.cms}/series/${id}?preferred_audio_language=ja-JP`, AuthHeaders);
|
const seriesReq = await this.req.getData(`${api.cms}/series/${id}?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||||
if(!seriesReq.ok || !seriesReq.res){
|
if(!seriesReq.ok || !seriesReq.res){
|
||||||
console.log('[ERROR] Series Request FAILED!');
|
console.error('Series Request FAILED!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const seriesData = JSON.parse(seriesReq.res.body);
|
const seriesData = JSON.parse(seriesReq.res.body);
|
||||||
|
|
@ -618,13 +619,13 @@ export default class Crunchy implements ServiceClass {
|
||||||
// seasons list
|
// seasons list
|
||||||
const seriesSeasonListReq = await this.req.getData(`${api.cms}/series/${id}/seasons?preferred_audio_language=ja-JP`, AuthHeaders);
|
const seriesSeasonListReq = await this.req.getData(`${api.cms}/series/${id}/seasons?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||||
if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){
|
if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){
|
||||||
console.log('[ERROR] Series Request FAILED!');
|
console.error('Series Request FAILED!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// parse data
|
// parse data
|
||||||
const seasonsList = JSON.parse(seriesSeasonListReq.res.body) as SeriesSearch;
|
const seasonsList = JSON.parse(seriesSeasonListReq.res.body) as SeriesSearch;
|
||||||
if(seasonsList.total < 1){
|
if(seasonsList.total < 1){
|
||||||
console.log('[INFO] Series is empty!');
|
console.info('Series is empty!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for(const item of seasonsList.data){
|
for(const item of seasonsList.data){
|
||||||
|
|
@ -635,7 +636,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
public async logMovieListingById(id: string, pad?: number){
|
public async logMovieListingById(id: string, pad?: number){
|
||||||
pad = pad || 2;
|
pad = pad || 2;
|
||||||
if(!this.cmsToken.cms){
|
if(!this.cmsToken.cms){
|
||||||
console.log('[ERROR] Authentication required!');
|
console.error('Authentication required!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const movieListingReqOpts = [
|
const movieListingReqOpts = [
|
||||||
|
|
@ -651,12 +652,12 @@ export default class Crunchy implements ServiceClass {
|
||||||
].join('');
|
].join('');
|
||||||
const movieListingReq = await this.req.getData(movieListingReqOpts);
|
const movieListingReq = await this.req.getData(movieListingReqOpts);
|
||||||
if(!movieListingReq.ok || !movieListingReq.res){
|
if(!movieListingReq.ok || !movieListingReq.res){
|
||||||
console.log('[ERROR] Movie Listing Request FAILED!');
|
console.error('Movie Listing Request FAILED!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const movieListing = JSON.parse(movieListingReq.res.body);
|
const movieListing = JSON.parse(movieListingReq.res.body);
|
||||||
if(movieListing.total < 1){
|
if(movieListing.total < 1){
|
||||||
console.log('[INFO] Movie Listing is empty!');
|
console.info('Movie Listing is empty!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for(const item of movieListing.items){
|
for(const item of movieListing.items){
|
||||||
|
|
@ -666,7 +667,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
public async getNewlyAdded(page?: number){
|
public async getNewlyAdded(page?: number){
|
||||||
if(!this.token.access_token){
|
if(!this.token.access_token){
|
||||||
console.log('[ERROR] Authentication required!');
|
console.error('Authentication required!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newlyAddedReqOpts = {
|
const newlyAddedReqOpts = {
|
||||||
|
|
@ -682,11 +683,11 @@ export default class Crunchy implements ServiceClass {
|
||||||
}).toString();
|
}).toString();
|
||||||
const newlyAddedReq = await this.req.getData(`${api.beta_browse}?${newlyAddedParams}`, newlyAddedReqOpts);
|
const newlyAddedReq = await this.req.getData(`${api.beta_browse}?${newlyAddedParams}`, newlyAddedReqOpts);
|
||||||
if(!newlyAddedReq.ok || !newlyAddedReq.res){
|
if(!newlyAddedReq.ok || !newlyAddedReq.res){
|
||||||
console.log('[ERROR] Get newly added FAILED!');
|
console.error('Get newly added FAILED!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newlyAddedResults = JSON.parse(newlyAddedReq.res.body);
|
const newlyAddedResults = JSON.parse(newlyAddedReq.res.body);
|
||||||
console.log('[INFO] Newly added:');
|
console.info('Newly added:');
|
||||||
for(const i of newlyAddedResults.items){
|
for(const i of newlyAddedResults.items){
|
||||||
await this.logObject(i, 2);
|
await this.logObject(i, 2);
|
||||||
}
|
}
|
||||||
|
|
@ -694,12 +695,12 @@ export default class Crunchy implements ServiceClass {
|
||||||
const itemPad = parseInt(new URL(newlyAddedResults.__href__, domain.api_beta).searchParams.get('start') as string);
|
const itemPad = parseInt(new URL(newlyAddedResults.__href__, domain.api_beta).searchParams.get('start') as string);
|
||||||
const pageCur = itemPad > 0 ? Math.ceil(itemPad/25) + 1 : 1;
|
const pageCur = itemPad > 0 ? Math.ceil(itemPad/25) + 1 : 1;
|
||||||
const pageMax = Math.ceil(newlyAddedResults.total/25);
|
const pageMax = Math.ceil(newlyAddedResults.total/25);
|
||||||
console.log(` [INFO] Total results: ${newlyAddedResults.total} (Page: ${pageCur}/${pageMax})`);
|
console.info(` Total results: ${newlyAddedResults.total} (Page: ${pageCur}/${pageMax})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getSeasonById(id: string, numbers: number, e: string|undefined, but: boolean, all: boolean) : Promise<ResponseBase<CrunchyEpMeta[]>> {
|
public async getSeasonById(id: string, numbers: number, e: string|undefined, but: boolean, all: boolean) : Promise<ResponseBase<CrunchyEpMeta[]>> {
|
||||||
if(!this.cmsToken.cms){
|
if(!this.cmsToken.cms){
|
||||||
console.log('[ERROR] Authentication required!');
|
console.error('Authentication required!');
|
||||||
return { isOk: false, reason: new Error('Authentication required') };
|
return { isOk: false, reason: new Error('Authentication required') };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -714,7 +715,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
//get show info
|
//get show info
|
||||||
const showInfoReq = await this.req.getData(`${api.cms}/seasons/${id}?preferred_audio_language=ja-JP`, AuthHeaders);
|
const showInfoReq = await this.req.getData(`${api.cms}/seasons/${id}?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||||
if(!showInfoReq.ok || !showInfoReq.res){
|
if(!showInfoReq.ok || !showInfoReq.res){
|
||||||
console.log('[ERROR] Show Request FAILED!');
|
console.error('Show Request FAILED!');
|
||||||
return { isOk: false, reason: new Error('Show request failed. No more information provided.') };
|
return { isOk: false, reason: new Error('Show request failed. No more information provided.') };
|
||||||
}
|
}
|
||||||
const showInfo = JSON.parse(showInfoReq.res.body);
|
const showInfo = JSON.parse(showInfoReq.res.body);
|
||||||
|
|
@ -723,7 +724,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
//get episode info
|
//get episode info
|
||||||
const reqEpsList = await this.req.getData(`${api.cms}/seasons/${id}/episodes?preferred_audio_language=ja-JP`, AuthHeaders);
|
const reqEpsList = await this.req.getData(`${api.cms}/seasons/${id}/episodes?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||||
if(!reqEpsList.ok || !reqEpsList.res){
|
if(!reqEpsList.ok || !reqEpsList.res){
|
||||||
console.log('[ERROR] Episode List Request FAILED!');
|
console.error('Episode List Request FAILED!');
|
||||||
return { isOk: false, reason: new Error('Episode List request failed. No more information provided.') };
|
return { isOk: false, reason: new Error('Episode List request failed. No more information provided.') };
|
||||||
}
|
}
|
||||||
const episodeList = JSON.parse(reqEpsList.res.body) as CrunchyEpisodeList;
|
const episodeList = JSON.parse(reqEpsList.res.body) as CrunchyEpisodeList;
|
||||||
|
|
@ -735,7 +736,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
const epNumLen = numbers;
|
const epNumLen = numbers;
|
||||||
|
|
||||||
if(episodeList.total < 1){
|
if(episodeList.total < 1){
|
||||||
console.log(' [INFO] Season is empty!');
|
console.info(' Season is empty!');
|
||||||
return { isOk: true, value: [] };
|
return { isOk: true, value: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -808,10 +809,10 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
// display
|
// display
|
||||||
if(selectedMedia.length < 1){
|
if(selectedMedia.length < 1){
|
||||||
console.log('\n[INFO] Episodes not selected!\n');
|
console.info('\nEpisodes not selected!\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log();
|
console.info('');
|
||||||
return { isOk: true, value: selectedMedia };
|
return { isOk: true, value: selectedMedia };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -831,19 +832,19 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
public async getObjectById(e?: string, earlyReturn?: boolean): Promise<ObjectInfo|Partial<CrunchyEpMeta>[]|undefined> {
|
public async getObjectById(e?: string, earlyReturn?: boolean): Promise<ObjectInfo|Partial<CrunchyEpMeta>[]|undefined> {
|
||||||
if(!this.cmsToken.cms){
|
if(!this.cmsToken.cms){
|
||||||
console.log('[ERROR] Authentication required!');
|
console.error('Authentication required!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const doEpsFilter = parseSelect(e as string);
|
const doEpsFilter = parseSelect(e as string);
|
||||||
|
|
||||||
if(doEpsFilter.values.length < 1){
|
if(doEpsFilter.values.length < 1){
|
||||||
console.log('\n[INFO] Objects not selected!\n');
|
console.info('\nObjects not selected!\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// node index.js --service crunchy -e G6497Z43Y,GRZXCMN1W,G62PEZ2E6,G25FVGDEK,GZ7UVPVX5
|
// node index.js --service crunchy -e G6497Z43Y,GRZXCMN1W,G62PEZ2E6,G25FVGDEK,GZ7UVPVX5
|
||||||
console.log('[INFO] Requested object ID: %s', doEpsFilter.values.join(', '));
|
console.info('Requested object ID: %s', doEpsFilter.values.join(', '));
|
||||||
|
|
||||||
const AuthHeaders = {
|
const AuthHeaders = {
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -855,10 +856,10 @@ export default class Crunchy implements ServiceClass {
|
||||||
// reqs
|
// reqs
|
||||||
const objectReq = await this.req.getData(`${api.cms}/objects/${doEpsFilter.values.join(',')}?preferred_audio_language=ja-JP`, AuthHeaders);
|
const objectReq = await this.req.getData(`${api.cms}/objects/${doEpsFilter.values.join(',')}?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||||
if(!objectReq.ok || !objectReq.res){
|
if(!objectReq.ok || !objectReq.res){
|
||||||
console.log('[ERROR] Objects Request FAILED!');
|
console.error('Objects Request FAILED!');
|
||||||
if(objectReq.error && objectReq.error.res && objectReq.error.res.body){
|
if(objectReq.error && objectReq.error.res && objectReq.error.res.body){
|
||||||
const objectInfo = JSON.parse(objectReq.error.res.body as string);
|
const objectInfo = JSON.parse(objectReq.error.res.body as string);
|
||||||
console.log('[INFO] Body:', JSON.stringify(objectInfo, null, '\t'));
|
console.info('Body:', JSON.stringify(objectInfo, null, '\t'));
|
||||||
objectInfo.error = true;
|
objectInfo.error = true;
|
||||||
return objectInfo;
|
return objectInfo;
|
||||||
}
|
}
|
||||||
|
|
@ -913,7 +914,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
}
|
}
|
||||||
await this.logObject(item, 2);
|
await this.logObject(item, 2);
|
||||||
}
|
}
|
||||||
console.log();
|
console.info('');
|
||||||
return selectedMedia;
|
return selectedMedia;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -923,7 +924,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
error: boolean
|
error: boolean
|
||||||
} | undefined> {
|
} | undefined> {
|
||||||
if(!this.cmsToken.cms){
|
if(!this.cmsToken.cms){
|
||||||
console.log('[ERROR] Authentication required!');
|
console.error('Authentication required!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -937,7 +938,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
const files: DownloadedMedia[] = [];
|
const files: DownloadedMedia[] = [];
|
||||||
|
|
||||||
if(medias.data.every(a => !a.playback)){
|
if(medias.data.every(a => !a.playback)){
|
||||||
console.log('[WARN] Video not available!');
|
console.warn('Video not available!');
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -946,7 +947,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
|
|
||||||
for (const mMeta of medias.data) {
|
for (const mMeta of medias.data) {
|
||||||
console.log(`[INFO] Requesting: [${mMeta.mediaId}] ${mediaName}`);
|
console.info(`Requesting: [${mMeta.mediaId}] ${mediaName}`);
|
||||||
|
|
||||||
//Make sure token is up to date
|
//Make sure token is up to date
|
||||||
await this.refreshToken(true, true);
|
await this.refreshToken(true, true);
|
||||||
|
|
@ -962,7 +963,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
if (mMeta.versions && mMeta.lang) {
|
if (mMeta.versions && mMeta.lang) {
|
||||||
mediaId = mMeta.versions.find(a => a.audio_locale == mMeta.lang?.cr_locale)?.media_guid as string;
|
mediaId = mMeta.versions.find(a => a.audio_locale == mMeta.lang?.cr_locale)?.media_guid as string;
|
||||||
if (!mediaId) {
|
if (!mediaId) {
|
||||||
console.log('[ERROR] Selected language not found.');
|
console.error('Selected language not found.');
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -973,10 +974,10 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
let playbackReq = await this.req.getData(`${api.cms}/videos/${mediaId}/streams`, AuthHeaders);
|
let playbackReq = await this.req.getData(`${api.cms}/videos/${mediaId}/streams`, AuthHeaders);
|
||||||
if(!playbackReq.ok || !playbackReq.res){
|
if(!playbackReq.ok || !playbackReq.res){
|
||||||
console.log('[ERROR] Request Stream URLs FAILED! Attempting fallback');
|
console.error('Request Stream URLs FAILED! Attempting fallback');
|
||||||
playbackReq = await this.req.getData(`${domain.api_beta}${mMeta.playback}`, AuthHeaders);
|
playbackReq = await this.req.getData(`${domain.api_beta}${mMeta.playback}`, AuthHeaders);
|
||||||
if(!playbackReq.ok || !playbackReq.res){
|
if(!playbackReq.ok || !playbackReq.res){
|
||||||
console.log('[ERROR] Fallback Request Stream URLs FAILED!');
|
console.error('Fallback Request Stream URLs FAILED!');
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1021,7 +1022,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(streams.length < 1){
|
if(streams.length < 1){
|
||||||
console.log('[WARN] No full streams found!');
|
console.warn('No full streams found!');
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1044,7 +1045,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
if(options.hslang != 'none'){
|
if(options.hslang != 'none'){
|
||||||
if(hsLangs.indexOf(options.hslang) > -1){
|
if(hsLangs.indexOf(options.hslang) > -1){
|
||||||
console.log('[INFO] Selecting stream with %s hardsubs', langsData.locale2language(options.hslang).language);
|
console.info('Selecting stream with %s hardsubs', langsData.locale2language(options.hslang).language);
|
||||||
streams = streams.filter((s) => {
|
streams = streams.filter((s) => {
|
||||||
if(s.hardsub_lang == '-'){
|
if(s.hardsub_lang == '-'){
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1053,9 +1054,9 @@ export default class Crunchy implements ServiceClass {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log('[WARN] Selected stream with %s hardsubs not available', langsData.locale2language(options.hslang).language);
|
console.warn('Selected stream with %s hardsubs not available', langsData.locale2language(options.hslang).language);
|
||||||
if(hsLangs.length > 0){
|
if(hsLangs.length > 0){
|
||||||
console.log('[WARN] Try other hardsubs stream:', hsLangs.join(', '));
|
console.warn('Try other hardsubs stream:', hsLangs.join(', '));
|
||||||
}
|
}
|
||||||
dlFailed = true;
|
dlFailed = true;
|
||||||
}
|
}
|
||||||
|
|
@ -1068,13 +1069,13 @@ export default class Crunchy implements ServiceClass {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
if(streams.length < 1){
|
if(streams.length < 1){
|
||||||
console.log('[WARN] Raw streams not available!');
|
console.warn('Raw streams not available!');
|
||||||
if(hsLangs.length > 0){
|
if(hsLangs.length > 0){
|
||||||
console.log('[WARN] Try hardsubs stream:', hsLangs.join(', '));
|
console.warn('Try hardsubs stream:', hsLangs.join(', '));
|
||||||
}
|
}
|
||||||
dlFailed = true;
|
dlFailed = true;
|
||||||
}
|
}
|
||||||
console.log('[INFO] Selecting raw stream');
|
console.info('Selecting raw stream');
|
||||||
}
|
}
|
||||||
|
|
||||||
let curStream:
|
let curStream:
|
||||||
|
|
@ -1086,19 +1087,19 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
streams.forEach((s, i) => {
|
streams.forEach((s, i) => {
|
||||||
const isSelected = options.kstream == i + 1 ? '✓' : ' ';
|
const isSelected = options.kstream == i + 1 ? '✓' : ' ';
|
||||||
console.log('[INFO] Full stream found! (%s%s: %s )', isSelected, i + 1, s.type);
|
console.info('Full stream found! (%s%s: %s )', isSelected, i + 1, s.type);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[INFO] Downloading video...');
|
console.info('Downloading video...');
|
||||||
curStream = streams[options.kstream-1];
|
curStream = streams[options.kstream-1];
|
||||||
|
|
||||||
console.log('[INFO] Playlists URL: %s (%s)', curStream.url, curStream.type);
|
console.info('Playlists URL: %s (%s)', curStream.url, curStream.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!options.novids && !dlFailed && curStream !== undefined){
|
if(!options.novids && !dlFailed && curStream !== undefined){
|
||||||
const streamPlaylistsReq = await this.req.getData(curStream.url);
|
const streamPlaylistsReq = await this.req.getData(curStream.url);
|
||||||
if(!streamPlaylistsReq.ok || !streamPlaylistsReq.res){
|
if(!streamPlaylistsReq.ok || !streamPlaylistsReq.res){
|
||||||
console.log('[ERROR] CAN\'T FETCH VIDEO PLAYLISTS!');
|
console.error('CAN\'T FETCH VIDEO PLAYLISTS!');
|
||||||
dlFailed = true;
|
dlFailed = true;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
@ -1139,7 +1140,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
&& plStreams[plServer][plResolutionText] != pl.uri
|
&& plStreams[plServer][plResolutionText] != pl.uri
|
||||||
&& typeof plStreams[plServer][plResolutionText] != 'undefined'
|
&& typeof plStreams[plServer][plResolutionText] != 'undefined'
|
||||||
){
|
){
|
||||||
console.log(`[WARN] Non duplicate url for ${plServer} detected, please report to developer!`);
|
console.error(`Non duplicate url for ${plServer} detected, please report to developer!`);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
plStreams[plServer][plResolutionText] = pl.uri;
|
plStreams[plServer][plResolutionText] = pl.uri;
|
||||||
|
|
@ -1170,7 +1171,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
});
|
});
|
||||||
let quality = options.q === 0 ? plQuality.length : options.q;
|
let quality = options.q === 0 ? plQuality.length : options.q;
|
||||||
if(quality > plQuality.length) {
|
if(quality > plQuality.length) {
|
||||||
console.log(`[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}.`);
|
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;
|
quality = plQuality.length;
|
||||||
}
|
}
|
||||||
// When best selected video quality is already downloaded
|
// When best selected video quality is already downloaded
|
||||||
|
|
@ -1181,8 +1182,8 @@ export default class Crunchy implements ServiceClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const selPlUrl = plSelectedList[plQuality.map(a => a.dim)[quality - 1]] ? plSelectedList[plQuality.map(a => a.dim)[quality - 1]] : '';
|
const selPlUrl = plSelectedList[plQuality.map(a => a.dim)[quality - 1]] ? plSelectedList[plQuality.map(a => a.dim)[quality - 1]] : '';
|
||||||
console.log(`[INFO] Servers available:\n\t${plServerList.join('\n\t')}`);
|
console.info(`Servers available:\n\t${plServerList.join('\n\t')}`);
|
||||||
console.log(`[INFO] Available qualities:\n\t${plQuality.map((a, ind) => `[${ind+1}] ${a.str}`).join('\n\t')}`);
|
console.info(`Available qualities:\n\t${plQuality.map((a, ind) => `[${ind+1}] ${a.str}`).join('\n\t')}`);
|
||||||
|
|
||||||
if(selPlUrl != ''){
|
if(selPlUrl != ''){
|
||||||
variables.push({
|
variables.push({
|
||||||
|
|
@ -1196,18 +1197,18 @@ export default class Crunchy implements ServiceClass {
|
||||||
});
|
});
|
||||||
const lang = langsData.languages.find(a => a.code === curStream?.audio_lang);
|
const lang = langsData.languages.find(a => a.code === curStream?.audio_lang);
|
||||||
if (!lang) {
|
if (!lang) {
|
||||||
console.log(`[ERROR] Unable to find language for code ${curStream.audio_lang}`);
|
console.error(`Unable to find language for code ${curStream.audio_lang}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`[INFO] Selected quality: ${Object.keys(plSelectedList).find(a => plSelectedList[a] === selPlUrl)} @ ${plSelectedServer}`);
|
console.info(`Selected quality: ${Object.keys(plSelectedList).find(a => plSelectedList[a] === selPlUrl)} @ ${plSelectedServer}`);
|
||||||
console.log('[INFO] Stream URL:', selPlUrl);
|
console.info('Stream URL:', selPlUrl);
|
||||||
// TODO check filename
|
// TODO check filename
|
||||||
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
|
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
|
||||||
const outFile = parseFileName(options.fileName + '.' + (mMeta.lang?.name || lang.name), variables, options.numbers, options.override).join(path.sep);
|
const outFile = parseFileName(options.fileName + '.' + (mMeta.lang?.name || lang.name), variables, options.numbers, options.override).join(path.sep);
|
||||||
console.log(`[INFO] Output filename: ${outFile}`);
|
console.info(`Output filename: ${outFile}`);
|
||||||
const chunkPage = await this.req.getData(selPlUrl);
|
const chunkPage = await this.req.getData(selPlUrl);
|
||||||
if(!chunkPage.ok || !chunkPage.res){
|
if(!chunkPage.ok || !chunkPage.res){
|
||||||
console.log('[ERROR] CAN\'T FETCH VIDEO PLAYLIST!');
|
console.error('CAN\'T FETCH VIDEO PLAYLIST!');
|
||||||
dlFailed = true;
|
dlFailed = true;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
@ -1215,7 +1216,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
const totalParts = chunkPlaylist.segments.length;
|
const totalParts = chunkPlaylist.segments.length;
|
||||||
const mathParts = Math.ceil(totalParts / options.partsize);
|
const mathParts = Math.ceil(totalParts / options.partsize);
|
||||||
const mathMsg = `(${mathParts}*${options.partsize})`;
|
const mathMsg = `(${mathParts}*${options.partsize})`;
|
||||||
console.log('[INFO] Total parts in stream:', totalParts, mathMsg);
|
console.info('Total parts in stream:', totalParts, mathMsg);
|
||||||
const tsFile = path.isAbsolute(outFile as string) ? outFile : path.join(this.cfg.dir.content, outFile);
|
const tsFile = path.isAbsolute(outFile as string) ? outFile : path.join(this.cfg.dir.content, outFile);
|
||||||
const split = outFile.split(path.sep).slice(0, -1);
|
const split = outFile.split(path.sep).slice(0, -1);
|
||||||
split.forEach((val, ind, arr) => {
|
split.forEach((val, ind, arr) => {
|
||||||
|
|
@ -1242,7 +1243,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
}) : undefined
|
}) : undefined
|
||||||
}).download();
|
}).download();
|
||||||
if(!dlStreamByPl.ok){
|
if(!dlStreamByPl.ok){
|
||||||
console.log(`[ERROR] DL Stats: ${JSON.stringify(dlStreamByPl.parts)}\n`);
|
console.error(`DL Stats: ${JSON.stringify(dlStreamByPl.parts)}\n`);
|
||||||
dlFailed = true;
|
dlFailed = true;
|
||||||
}
|
}
|
||||||
files.push({
|
files.push({
|
||||||
|
|
@ -1254,14 +1255,14 @@ export default class Crunchy implements ServiceClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log('[ERROR] Quality not selected!\n');
|
console.error('Quality not selected!\n');
|
||||||
dlFailed = true;
|
dlFailed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(options.novids){
|
else if(options.novids){
|
||||||
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
|
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
|
||||||
console.log('[INFO] Downloading skipped!');
|
console.info('Downloading skipped!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1270,7 +1271,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.hslang != 'none'){
|
if(options.hslang != 'none'){
|
||||||
console.log('[WARN] Subtitles downloading disabled for hardsubs streams.');
|
console.warn('Subtitles downloading disabled for hardsubs streams.');
|
||||||
options.skipsubs = true;
|
options.skipsubs = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1307,7 +1308,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
sxData.title = `${langItem.language} / ${sxData.title}`;
|
sxData.title = `${langItem.language} / ${sxData.title}`;
|
||||||
sxData.fonts = fontsData.assFonts(sBody) as Font[];
|
sxData.fonts = fontsData.assFonts(sBody) as Font[];
|
||||||
fs.writeFileSync(sxData.path, sBody);
|
fs.writeFileSync(sxData.path, sBody);
|
||||||
console.log(`[INFO] Subtitle downloaded: ${sxData.file}`);
|
console.info(`Subtitle downloaded: ${sxData.file}`);
|
||||||
files.push({
|
files.push({
|
||||||
type: 'Subtitle',
|
type: 'Subtitle',
|
||||||
...sxData as sxItem,
|
...sxData as sxItem,
|
||||||
|
|
@ -1315,17 +1316,17 @@ export default class Crunchy implements ServiceClass {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log(`[WARN] Failed to download subtitle: ${sxData.file}`);
|
console.warn(`Failed to download subtitle: ${sxData.file}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log('[WARN] Can\'t find urls for subtitles!');
|
console.warn('Can\'t find urls for subtitles!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log('[INFO] Subtitles downloading skipped!');
|
console.info('Subtitles downloading skipped!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -1338,7 +1339,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
public async muxStreams(data: DownloadedMedia[], options: CrunchyMuxOptions) {
|
public async muxStreams(data: DownloadedMedia[], options: CrunchyMuxOptions) {
|
||||||
this.cfg.bin = await yamlCfg.loadBinCfg();
|
this.cfg.bin = await yamlCfg.loadBinCfg();
|
||||||
if (options.novids || data.filter(a => a.type === 'Video').length === 0)
|
if (options.novids || data.filter(a => a.type === 'Video').length === 0)
|
||||||
return console.log('[INFO] Skip muxing since no vids are downloaded');
|
return console.info('Skip muxing since no vids are downloaded');
|
||||||
const merger = new Merger({
|
const merger = new Merger({
|
||||||
onlyVid: [],
|
onlyVid: [],
|
||||||
skipSubMux: options.skipSubMux,
|
skipSubMux: options.skipSubMux,
|
||||||
|
|
@ -1385,7 +1386,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
await merger.merge('ffmpeg', bin.FFmpeg);
|
await merger.merge('ffmpeg', bin.FFmpeg);
|
||||||
isMuxed = true;
|
isMuxed = true;
|
||||||
} else{
|
} else{
|
||||||
console.log('\n[INFO] Done!\n');
|
console.info('\nDone!\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isMuxed && !options.nocleanup)
|
if (isMuxed && !options.nocleanup)
|
||||||
|
|
@ -1465,7 +1466,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
for (const key of Object.keys(episodes)) {
|
for (const key of Object.keys(episodes)) {
|
||||||
const item = episodes[key];
|
const item = episodes[key];
|
||||||
console.log(`[${key}] ${
|
console.info(`[${key}] ${
|
||||||
item.items.find(a => !a.season_title.match(/\(\w+ Dub\)/))?.season_title ?? item.items[0].season_title.replace(/\(\w+ Dub\)/g, '').trimEnd()
|
item.items.find(a => !a.season_title.match(/\(\w+ Dub\)/))?.season_title ?? item.items[0].season_title.replace(/\(\w+ Dub\)/g, '').trimEnd()
|
||||||
} - Season ${item.items[0].season_number} - ${item.items[0].title} [${
|
} - Season ${item.items[0].season_number} - ${item.items[0].title} [${
|
||||||
item.items.map((a, index) => {
|
item.items.map((a, index) => {
|
||||||
|
|
@ -1477,7 +1478,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
//TODO: Sort episodes to have specials at the end
|
//TODO: Sort episodes to have specials at the end
|
||||||
|
|
||||||
if (!serieshasversions) {
|
if (!serieshasversions) {
|
||||||
console.log('[WARN] Couldn\'t find versions on some episodes, fell back to old method.');
|
console.warn('Couldn\'t find versions on some episodes, fell back to old method.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { data: episodes, list: Object.entries(episodes).map(([key, value]) => {
|
return { data: episodes, list: Object.entries(episodes).map(([key, value]) => {
|
||||||
|
|
@ -1500,13 +1501,13 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
public async downloadFromSeriesID(id: string, data: CurnchyMultiDownload) : Promise<ResponseBase<CrunchyEpMeta[]>> {
|
public async downloadFromSeriesID(id: string, data: CurnchyMultiDownload) : Promise<ResponseBase<CrunchyEpMeta[]>> {
|
||||||
const { data: episodes } = await this.listSeriesID(id);
|
const { data: episodes } = await this.listSeriesID(id);
|
||||||
console.log();
|
console.info('');
|
||||||
console.log('-'.repeat(30));
|
console.info('-'.repeat(30));
|
||||||
console.log();
|
console.info('');
|
||||||
const selected = this.itemSelectMultiDub(episodes, data.dubLang, data.but, data.all, data.e);
|
const selected = this.itemSelectMultiDub(episodes, data.dubLang, data.but, data.all, data.e);
|
||||||
for (const key of Object.keys(selected)) {
|
for (const key of Object.keys(selected)) {
|
||||||
const item = selected[key];
|
const item = selected[key];
|
||||||
console.log(`[S${item.season}E${item.episodeNumber}] - ${item.episodeTitle} [${
|
console.info(`[S${item.season}E${item.episodeNumber}] - ${item.episodeTitle} [${
|
||||||
item.data.map(a => {
|
item.data.map(a => {
|
||||||
return `✓ ${a.lang?.name || 'Unknown Language'}`;
|
return `✓ ${a.lang?.name || 'Unknown Language'}`;
|
||||||
}).join(', ')
|
}).join(', ')
|
||||||
|
|
@ -1611,7 +1612,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
public async parseSeriesById(id: string) {
|
public async parseSeriesById(id: string) {
|
||||||
if(!this.cmsToken.cms){
|
if(!this.cmsToken.cms){
|
||||||
console.log('[ERROR] Authentication required!');
|
console.error('Authentication required!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1625,13 +1626,13 @@ export default class Crunchy implements ServiceClass {
|
||||||
// seasons list
|
// seasons list
|
||||||
const seriesSeasonListReq = await this.req.getData(`${api.cms}/series/${id}/seasons?preferred_audio_language=ja-JP`, AuthHeaders);
|
const seriesSeasonListReq = await this.req.getData(`${api.cms}/series/${id}/seasons?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||||
if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){
|
if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){
|
||||||
console.log('[ERROR] Series Request FAILED!');
|
console.error('Series Request FAILED!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// parse data
|
// parse data
|
||||||
const seasonsList = JSON.parse(seriesSeasonListReq.res.body) as SeriesSearch;
|
const seasonsList = JSON.parse(seriesSeasonListReq.res.body) as SeriesSearch;
|
||||||
if(seasonsList.total < 1){
|
if(seasonsList.total < 1){
|
||||||
console.log('[INFO] Series is empty!');
|
console.info('Series is empty!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return seasonsList;
|
return seasonsList;
|
||||||
|
|
@ -1639,7 +1640,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
|
|
||||||
public async getSeasonDataById(item: SeriesSearchItem, log = false){
|
public async getSeasonDataById(item: SeriesSearchItem, log = false){
|
||||||
if(!this.cmsToken.cms){
|
if(!this.cmsToken.cms){
|
||||||
console.log('[ERROR] Authentication required!');
|
console.error('Authentication required!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1653,7 +1654,7 @@ export default class Crunchy implements ServiceClass {
|
||||||
//get show info
|
//get show info
|
||||||
const showInfoReq = await this.req.getData(`${api.cms}/seasons/${item.id}?preferred_audio_language=ja-JP`, AuthHeaders);
|
const showInfoReq = await this.req.getData(`${api.cms}/seasons/${item.id}?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||||
if(!showInfoReq.ok || !showInfoReq.res){
|
if(!showInfoReq.ok || !showInfoReq.res){
|
||||||
console.log('[ERROR] Show Request FAILED!');
|
console.error('Show Request FAILED!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const showInfo = JSON.parse(showInfoReq.res.body);
|
const showInfo = JSON.parse(showInfoReq.res.body);
|
||||||
|
|
@ -1662,13 +1663,13 @@ export default class Crunchy implements ServiceClass {
|
||||||
//get episode info
|
//get episode info
|
||||||
const reqEpsList = await this.req.getData(`${api.cms}/seasons/${item.id}/episodes?preferred_audio_language=ja-JP`, AuthHeaders);
|
const reqEpsList = await this.req.getData(`${api.cms}/seasons/${item.id}/episodes?preferred_audio_language=ja-JP`, AuthHeaders);
|
||||||
if(!reqEpsList.ok || !reqEpsList.res){
|
if(!reqEpsList.ok || !reqEpsList.res){
|
||||||
console.log('[ERROR] Episode List Request FAILED!');
|
console.error('Episode List Request FAILED!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const episodeList = JSON.parse(reqEpsList.res.body) as CrunchyEpisodeList;
|
const episodeList = JSON.parse(reqEpsList.res.body) as CrunchyEpisodeList;
|
||||||
|
|
||||||
if(episodeList.total < 1){
|
if(episodeList.total < 1){
|
||||||
console.log(' [INFO] Season is empty!');
|
console.info(' Season is empty!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return episodeList;
|
return episodeList;
|
||||||
|
|
|
||||||
97
funi.ts
97
funi.ts
|
|
@ -6,9 +6,10 @@ import path from 'path';
|
||||||
import packageJson from './package.json';
|
import packageJson from './package.json';
|
||||||
|
|
||||||
// modules extra
|
// modules extra
|
||||||
|
import { console } from './modules/log';
|
||||||
import * as shlp from 'sei-helper';
|
import * as shlp from 'sei-helper';
|
||||||
import m3u8 from 'm3u8-parsed';
|
import m3u8 from 'm3u8-parsed';
|
||||||
import hlsDownload, { HLSCallback } from 'hls-download';
|
import hlsDownload, { HLSCallback } from './modules/hls-download';
|
||||||
|
|
||||||
// extra
|
// extra
|
||||||
import * as appYargs from './modules/module.app-args';
|
import * as appYargs from './modules/module.app-args';
|
||||||
|
|
@ -73,7 +74,7 @@ export default class Funi implements ServiceClass {
|
||||||
const argv = appYargs.appArgv(this.cfg.cli);
|
const argv = appYargs.appArgv(this.cfg.cli);
|
||||||
if (argv.debug)
|
if (argv.debug)
|
||||||
this.debug = true;
|
this.debug = true;
|
||||||
console.log(`\n=== Multi Downloader NX ${packageJson.version} ===\n`);
|
console.info(`\n=== Multi Downloader NX ${packageJson.version} ===\n`);
|
||||||
if (argv.allDubs) {
|
if (argv.allDubs) {
|
||||||
argv.dubLang = langsData.dubLanguageCodes;
|
argv.dubLang = langsData.dubLanguageCodes;
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +99,7 @@ export default class Funi implements ServiceClass {
|
||||||
else if(argv.s && !isNaN(parseInt(argv.s)) && parseInt(argv.s) > 0){
|
else if(argv.s && !isNaN(parseInt(argv.s)) && parseInt(argv.s) > 0){
|
||||||
const data = await this.getShow(true, { id: parseInt(argv.s), but: argv.but, all: argv.all, e: argv.e });
|
const data = await this.getShow(true, { id: parseInt(argv.s), but: argv.but, all: argv.all, e: argv.e });
|
||||||
if (!data.isOk) {
|
if (!data.isOk) {
|
||||||
console.log(`[ERROR] ${data.reason.message}`);
|
console.error(`${data.reason.message}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let ok = true;
|
let ok = true;
|
||||||
|
|
@ -112,7 +113,7 @@ export default class Funi implements ServiceClass {
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log('[INFO] No option selected or invalid value entered. Try --help.');
|
console.info('No option selected or invalid value entered. Try --help.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async auth(data: AuthData): Promise<AuthResponse> {
|
public async auth(data: AuthData): Promise<AuthResponse> {
|
||||||
|
|
@ -129,14 +130,14 @@ export default class Funi implements ServiceClass {
|
||||||
if(authData.ok && authData.res){
|
if(authData.ok && authData.res){
|
||||||
const resJSON = JSON.parse(authData.res.body);
|
const resJSON = JSON.parse(authData.res.body);
|
||||||
if(resJSON.token){
|
if(resJSON.token){
|
||||||
console.log('[INFO] Authentication success, your token: %s%s\n', resJSON.token.slice(0,8),'*'.repeat(32));
|
console.info('Authentication success, your token: %s%s\n', resJSON.token.slice(0,8),'*'.repeat(32));
|
||||||
yamlCfg.saveFuniToken({'token': resJSON.token});
|
yamlCfg.saveFuniToken({'token': resJSON.token});
|
||||||
this.token = resJSON.token;
|
this.token = resJSON.token;
|
||||||
return { isOk: true, value: undefined };
|
return { isOk: true, value: undefined };
|
||||||
} else {
|
} else {
|
||||||
console.log('[ERROR]%s\n', ' No token found');
|
console.info('[ERROR]%s\n', ' No token found');
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
console.log(resJSON);
|
console.info(resJSON);
|
||||||
}
|
}
|
||||||
return { isOk: false, reason: new Error(resJSON) };
|
return { isOk: false, reason: new Error(resJSON) };
|
||||||
}
|
}
|
||||||
|
|
@ -159,18 +160,18 @@ export default class Funi implements ServiceClass {
|
||||||
}
|
}
|
||||||
const searchDataJSON = JSON.parse(searchData.res.body);
|
const searchDataJSON = JSON.parse(searchData.res.body);
|
||||||
if(searchDataJSON.detail){
|
if(searchDataJSON.detail){
|
||||||
console.log(`[ERROR] ${searchDataJSON.detail}`);
|
console.error(`${searchDataJSON.detail}`);
|
||||||
return { isOk: false, reason: new Error(searchDataJSON.defail) };
|
return { isOk: false, reason: new Error(searchDataJSON.defail) };
|
||||||
}
|
}
|
||||||
if(searchDataJSON.items && searchDataJSON.items.hits && log){
|
if(searchDataJSON.items && searchDataJSON.items.hits && log){
|
||||||
const shows = searchDataJSON.items.hits;
|
const shows = searchDataJSON.items.hits;
|
||||||
console.log('[INFO] Search Results:');
|
console.info('Search Results:');
|
||||||
for(const ssn in shows){
|
for(const ssn in shows){
|
||||||
console.log(`[#${shows[ssn].id}] ${shows[ssn].title}` + (shows[ssn].tx_date?` (${shows[ssn].tx_date})`:''));
|
console.info(`[#${shows[ssn].id}] ${shows[ssn].title}` + (shows[ssn].tx_date?` (${shows[ssn].tx_date})`:''));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[INFO] Total shows found: %s\n',searchDataJSON.count);
|
console.info('Total shows found: %s\n',searchDataJSON.count);
|
||||||
return { isOk: true, value: searchDataJSON };
|
return { isOk: true, value: searchDataJSON };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,15 +187,15 @@ export default class Funi implements ServiceClass {
|
||||||
if(!showData.ok || !showData.res){ return { isOk: false, reason: new Error('ShowData is not ok') }; }
|
if(!showData.ok || !showData.res){ return { isOk: false, reason: new Error('ShowData is not ok') }; }
|
||||||
const showDataJSON = JSON.parse(showData.res.body);
|
const showDataJSON = JSON.parse(showData.res.body);
|
||||||
if(showDataJSON.status){
|
if(showDataJSON.status){
|
||||||
console.log('[ERROR] Error #%d: %s\n', showDataJSON.status, showDataJSON.data.errors[0].detail);
|
console.error('Error #%d: %s\n', showDataJSON.status, showDataJSON.data.errors[0].detail);
|
||||||
return { isOk: false, reason: new Error(showDataJSON.data.errors[0].detail) };
|
return { isOk: false, reason: new Error(showDataJSON.data.errors[0].detail) };
|
||||||
}
|
}
|
||||||
else if(!showDataJSON.items || showDataJSON.items.length<1){
|
else if(!showDataJSON.items || showDataJSON.items.length<1){
|
||||||
console.log('[ERROR] Show not found\n');
|
console.error('Show not found\n');
|
||||||
return { isOk: false, reason: new Error('Show not found') };
|
return { isOk: false, reason: new Error('Show not found') };
|
||||||
}
|
}
|
||||||
const showDataItem = showDataJSON.items[0];
|
const showDataItem = showDataJSON.items[0];
|
||||||
console.log('[#%s] %s (%s)',showDataItem.id,showDataItem.title,showDataItem.releaseYear);
|
console.info('[#%s] %s (%s)',showDataItem.id,showDataItem.title,showDataItem.releaseYear);
|
||||||
// show episodes
|
// show episodes
|
||||||
const qs: {
|
const qs: {
|
||||||
limit: number,
|
limit: number,
|
||||||
|
|
@ -219,7 +220,7 @@ export default class Funi implements ServiceClass {
|
||||||
const parseEpStr = (epStr: string) => {
|
const parseEpStr = (epStr: string) => {
|
||||||
const match = epStr.match(epNumRegex);
|
const match = epStr.match(epNumRegex);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
console.error('[ERROR] No match found');
|
console.error('No match found');
|
||||||
return ['', ''];
|
return ['', ''];
|
||||||
}
|
}
|
||||||
if(match.length > 2){
|
if(match.length > 2){
|
||||||
|
|
@ -241,7 +242,7 @@ export default class Funi implements ServiceClass {
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
Funi.typeIdLen = 3 > Funi.typeIdLen? 3 : Funi.typeIdLen;
|
Funi.typeIdLen = 3 > Funi.typeIdLen? 3 : Funi.typeIdLen;
|
||||||
console.log('[ERROR] FAILED TO PARSE: ', e.id);
|
console.error('FAILED TO PARSE: ', e.id);
|
||||||
e.id_split = [ 'ZZZ', 9999 ];
|
e.id_split = [ 'ZZZ', 9999 ];
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
|
|
@ -308,16 +309,16 @@ export default class Funi implements ServiceClass {
|
||||||
conOut += `(${rtm_str}) [${qua_str+aud_str}]`;
|
conOut += `(${rtm_str}) [${qua_str+aud_str}]`;
|
||||||
conOut += is_selected ? ' (selected)' : '';
|
conOut += is_selected ? ' (selected)' : '';
|
||||||
conOut += eps.length-1 == parseInt(e) ? '\n' : '';
|
conOut += eps.length-1 == parseInt(e) ? '\n' : '';
|
||||||
console.log(conOut);
|
console.info(conOut);
|
||||||
}
|
}
|
||||||
if(fnSlug.length < 1){
|
if(fnSlug.length < 1){
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[INFO] Episodes not selected!\n');
|
console.info('Episodes not selected!\n');
|
||||||
return { isOk: true, value: [] } ;
|
return { isOk: true, value: [] } ;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[INFO] Selected Episodes: %s\n',epSelEpsTxt.join(', '));
|
console.info('Selected Episodes: %s\n',epSelEpsTxt.join(', '));
|
||||||
return { isOk: true, value: fnSlug };
|
return { isOk: true, value: fnSlug };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -347,15 +348,15 @@ export default class Funi implements ServiceClass {
|
||||||
|
|
||||||
// end
|
// end
|
||||||
if (log) {
|
if (log) {
|
||||||
console.log(
|
console.info(
|
||||||
'[INFO] %s - S%sE%s - %s',
|
'%s - S%sE%s - %s',
|
||||||
ep.parent.title,
|
ep.parent.title,
|
||||||
(ep.parent.seasonNumber ? ep.parent.seasonNumber : '?'),
|
(ep.parent.seasonNumber ? ep.parent.seasonNumber : '?'),
|
||||||
(ep.number ? ep.number : '?'),
|
(ep.number ? ep.number : '?'),
|
||||||
ep.title
|
ep.title
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[INFO] Available streams (Non-Encrypted):');
|
console.info('Available streams (Non-Encrypted):');
|
||||||
}
|
}
|
||||||
// map medias
|
// map medias
|
||||||
const media = await Promise.all(ep.media.map(async (m) =>{
|
const media = await Promise.all(ep.media.map(async (m) =>{
|
||||||
|
|
@ -406,7 +407,7 @@ export default class Funi implements ServiceClass {
|
||||||
if (!subsToDisplay.includes(a.lang))
|
if (!subsToDisplay.includes(a.lang))
|
||||||
subsToDisplay.push(a.lang);
|
subsToDisplay.push(a.lang);
|
||||||
});
|
});
|
||||||
console.log(`[#${m.id}] ${dub_type} [${m.version}]${(selected?' (selected)':'')}${
|
console.info(`[#${m.id}] ${dub_type} [${m.version}]${(selected?' (selected)':'')}${
|
||||||
localSubs && localSubs.length > 0 && selected ? ` (using ${subsToDisplay.map(a => `'${a.name}'`).join(', ')} for subtitles)` : ''
|
localSubs && localSubs.length > 0 && selected ? ` (using ${subsToDisplay.map(a => `'${a.name}'`).join(', ')} for subtitles)` : ''
|
||||||
}`);
|
}`);
|
||||||
}
|
}
|
||||||
|
|
@ -424,7 +425,7 @@ export default class Funi implements ServiceClass {
|
||||||
});
|
});
|
||||||
if(streamIds.length < 1){
|
if(streamIds.length < 1){
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[ERROR] Track not selected\n');
|
console.error('Track not selected\n');
|
||||||
return { isOk: false, reason: new Error('Track not selected') };
|
return { isOk: false, reason: new Error('Track not selected') };
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
@ -442,7 +443,7 @@ export default class Funi implements ServiceClass {
|
||||||
const streamDataRes = JSON.parse(streamData.res.body) as StreamData;
|
const streamDataRes = JSON.parse(streamData.res.body) as StreamData;
|
||||||
if(streamDataRes.errors){
|
if(streamDataRes.errors){
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[ERROR] Error #%s: %s\n',streamDataRes.errors[0].code,streamDataRes.errors[0].detail);
|
console.info('Error #%s: %s\n',streamDataRes.errors[0].code,streamDataRes.errors[0].detail);
|
||||||
return { isOk: false, reason: new Error(streamDataRes.errors[0].detail) };
|
return { isOk: false, reason: new Error(streamDataRes.errors[0].detail) };
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
@ -459,7 +460,7 @@ export default class Funi implements ServiceClass {
|
||||||
}
|
}
|
||||||
if(tsDlPath.length < 1){
|
if(tsDlPath.length < 1){
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[ERROR] Unknown error\n');
|
console.error('Unknown error\n');
|
||||||
return { isOk: false, reason: new Error('Unknown error') };
|
return { isOk: false, reason: new Error('Unknown error') };
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
@ -525,7 +526,7 @@ export default class Funi implements ServiceClass {
|
||||||
if(plQualityLinkList.playlists[0].uri.match(vplReg)){
|
if(plQualityLinkList.playlists[0].uri.match(vplReg)){
|
||||||
const audioKey = Object.keys(plQualityLinkList.mediaGroups.AUDIO).pop();
|
const audioKey = Object.keys(plQualityLinkList.mediaGroups.AUDIO).pop();
|
||||||
if (!audioKey)
|
if (!audioKey)
|
||||||
return console.log('[ERROR] No audio key found');
|
return console.error('No audio key found');
|
||||||
if(plQualityLinkList.mediaGroups.AUDIO[audioKey]){
|
if(plQualityLinkList.mediaGroups.AUDIO[audioKey]){
|
||||||
const audioDataParts = plQualityLinkList.mediaGroups.AUDIO[audioKey],
|
const audioDataParts = plQualityLinkList.mediaGroups.AUDIO[audioKey],
|
||||||
audioEl = Object.keys(audioDataParts);
|
audioEl = Object.keys(audioDataParts);
|
||||||
|
|
@ -535,7 +536,7 @@ export default class Funi implements ServiceClass {
|
||||||
language = langsData.languages.find(a => a.funi_name_lagacy === audioEl[0] || ((a.funi_name ?? a.name) === audioEl[0]));
|
language = langsData.languages.find(a => a.funi_name_lagacy === audioEl[0] || ((a.funi_name ?? a.name) === audioEl[0]));
|
||||||
if (!language) {
|
if (!language) {
|
||||||
if (log)
|
if (log)
|
||||||
console.log(`[ERROR] Unable to find language for locale ${audioData.language} or name ${audioEl[0]}`);
|
console.error(`Unable to find language for locale ${audioData.language} or name ${audioEl[0]}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -547,7 +548,7 @@ export default class Funi implements ServiceClass {
|
||||||
plQualityLinkList.playlists.sort((a, b) => {
|
plQualityLinkList.playlists.sort((a, b) => {
|
||||||
const aMatch = a.uri.match(vplReg), bMatch = b.uri.match(vplReg);
|
const aMatch = a.uri.match(vplReg), bMatch = b.uri.match(vplReg);
|
||||||
if (!aMatch || !bMatch) {
|
if (!aMatch || !bMatch) {
|
||||||
console.log('[ERROR] Unable to match');
|
console.info('Unable to match');
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const av = parseInt(aMatch[3]);
|
const av = parseInt(aMatch[3]);
|
||||||
|
|
@ -584,7 +585,7 @@ export default class Funi implements ServiceClass {
|
||||||
plStreams[plServer] = {};
|
plStreams[plServer] = {};
|
||||||
}
|
}
|
||||||
if(plStreams[plServer][plLayerId] && plStreams[plServer][plLayerId] != plUrlDl){
|
if(plStreams[plServer][plLayerId] && plStreams[plServer][plLayerId] != plUrlDl){
|
||||||
console.log(`[WARN] Non duplicate url for ${plServer} detected, please report to developer!`);
|
console.warn(`Non duplicate url for ${plServer} detected, please report to developer!`);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
plStreams[plServer][plLayerId] = plUrlDl;
|
plStreams[plServer][plLayerId] = plUrlDl;
|
||||||
|
|
@ -604,7 +605,7 @@ export default class Funi implements ServiceClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log(s.uri);
|
console.info(s.uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -622,8 +623,8 @@ export default class Funi implements ServiceClass {
|
||||||
|
|
||||||
plLayersStr.sort();
|
plLayersStr.sort();
|
||||||
if (log) {
|
if (log) {
|
||||||
console.log(`[INFO] Servers available:\n\t${plServerList.join('\n\t')}`);
|
console.info(`Servers available:\n\t${plServerList.join('\n\t')}`);
|
||||||
console.log(`[INFO] Available qualities:\n\t${plLayersStr.join('\n\t')}`);
|
console.info(`Available qualities:\n\t${plLayersStr.join('\n\t')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedQuality = data.q === 0 || data.q > Object.keys(plLayersRes).length
|
const selectedQuality = data.q === 0 || data.q > Object.keys(plLayersRes).length
|
||||||
|
|
@ -633,8 +634,8 @@ export default class Funi implements ServiceClass {
|
||||||
|
|
||||||
if(videoUrl != ''){
|
if(videoUrl != ''){
|
||||||
if (log) {
|
if (log) {
|
||||||
console.log(`[INFO] Selected layer: ${selectedQuality} (${plLayersRes[selectedQuality].width}x${plLayersRes[selectedQuality].height}) @ ${plSelectedServer}`);
|
console.info(`Selected layer: ${selectedQuality} (${plLayersRes[selectedQuality].width}x${plLayersRes[selectedQuality].height}) @ ${plSelectedServer}`);
|
||||||
console.log('[INFO] Stream URL:',videoUrl);
|
console.info('Stream URL:',videoUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
fnOutput = parseFileName(data.fileName, ([
|
fnOutput = parseFileName(data.fileName, ([
|
||||||
|
|
@ -656,16 +657,16 @@ export default class Funi implements ServiceClass {
|
||||||
if (fnOutput.length < 1)
|
if (fnOutput.length < 1)
|
||||||
throw new Error(`Invalid path generated for input ${data.fileName}`);
|
throw new Error(`Invalid path generated for input ${data.fileName}`);
|
||||||
if (log)
|
if (log)
|
||||||
console.log(`[INFO] Output filename: ${fnOutput.join(path.sep)}.ts`);
|
console.info(`Output filename: ${fnOutput.join(path.sep)}.ts`);
|
||||||
}
|
}
|
||||||
else if(data.x > plServerList.length){
|
else if(data.x > plServerList.length){
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[ERROR] Server not selected!\n');
|
console.error('Server not selected!\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[ERROR] Layer not selected!\n');
|
console.error('Layer not selected!\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -715,7 +716,7 @@ export default class Funi implements ServiceClass {
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[INFO] Skip video downloading...\n');
|
console.info('Skip video downloading...\n');
|
||||||
}
|
}
|
||||||
audio: if (plAud && !data.noaudio) {
|
audio: if (plAud && !data.noaudio) {
|
||||||
// download audio
|
// download audio
|
||||||
|
|
@ -756,7 +757,7 @@ export default class Funi implements ServiceClass {
|
||||||
// download subtitles
|
// download subtitles
|
||||||
if(stDlPath.length > 0){
|
if(stDlPath.length > 0){
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[INFO] Downloading subtitles...');
|
console.info('Downloading subtitles...');
|
||||||
for (const subObject of stDlPath) {
|
for (const subObject of stDlPath) {
|
||||||
const subsSrc = await getData({
|
const subsSrc = await getData({
|
||||||
url: subObject.url,
|
url: subObject.url,
|
||||||
|
|
@ -769,24 +770,24 @@ export default class Funi implements ServiceClass {
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[ERROR] Failed to download subtitles!');
|
console.error('Failed to download subtitles!');
|
||||||
addSubs = false;
|
addSubs = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (addSubs && log)
|
if (addSubs && log)
|
||||||
console.log('[INFO] Subtitles downloaded!');
|
console.info('Subtitles downloaded!');
|
||||||
}
|
}
|
||||||
|
|
||||||
if((puraudio.length < 1 && audioAndVideo.length < 1) || (purvideo.length < 1 && audioAndVideo.length < 1)){
|
if((puraudio.length < 1 && audioAndVideo.length < 1) || (purvideo.length < 1 && audioAndVideo.length < 1)){
|
||||||
if (log)
|
if (log)
|
||||||
console.log('\n[INFO] Unable to locate a video AND audio file\n');
|
console.info('\nUnable to locate a video AND audio file\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(data.skipmux){
|
if(data.skipmux){
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[INFO] Skipping muxing...');
|
console.info('Skipping muxing...');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -796,7 +797,7 @@ export default class Funi implements ServiceClass {
|
||||||
|
|
||||||
if ( data.novids ){
|
if ( data.novids ){
|
||||||
if (log)
|
if (log)
|
||||||
console.log('[INFO] Video not downloaded. Skip muxing video.');
|
console.info('Video not downloaded. Skip muxing video.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const ffext = !data.mp4 ? 'mkv' : 'mp4';
|
const ffext = !data.mp4 ? 'mkv' : 'mp4';
|
||||||
|
|
@ -835,7 +836,7 @@ export default class Funi implements ServiceClass {
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (log)
|
if (log)
|
||||||
console.log('\n[INFO] Done!\n');
|
console.info('\nDone!\n');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (data.nocleanup) {
|
if (data.nocleanup) {
|
||||||
|
|
@ -844,7 +845,7 @@ export default class Funi implements ServiceClass {
|
||||||
|
|
||||||
mergeInstance.cleanUp();
|
mergeInstance.cleanUp();
|
||||||
if (log)
|
if (log)
|
||||||
console.log('\n[INFO] Done!\n');
|
console.info('\nDone!\n');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -878,7 +879,7 @@ export default class Funi implements ServiceClass {
|
||||||
querystring: { deviceType: 'web' }
|
querystring: { deviceType: 'web' }
|
||||||
});
|
});
|
||||||
if (!subs.ok || !subs.res || !subs.res.body) {
|
if (!subs.ok || !subs.res || !subs.res.body) {
|
||||||
console.log('[ERROR] Subtitle Request failed.');
|
console.error('Subtitle Request failed.');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const parsed: SubtitleRequest = JSON.parse(subs.res.body);
|
const parsed: SubtitleRequest = JSON.parse(subs.res.body);
|
||||||
|
|
|
||||||
2
gui.ts
2
gui.ts
|
|
@ -1 +1,3 @@
|
||||||
|
process.env.isGUI = 'true';
|
||||||
|
import './modules/log';
|
||||||
import './gui/server/index';
|
import './gui/server/index';
|
||||||
2
gui/react/src/@types/FC.d.ts
vendored
2
gui/react/src/@types/FC.d.ts
vendored
|
|
@ -1,3 +1,3 @@
|
||||||
type FCWithChildren<T = {}> = React.FC<{
|
type FCWithChildren<T = object> = React.FC<{
|
||||||
children?: React.ReactNode[]|React.ReactNode
|
children?: React.ReactNode[]|React.ReactNode
|
||||||
} & T>
|
} & T>
|
||||||
|
|
@ -5,6 +5,6 @@ const App: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Layout />
|
<Layout />
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import AuthButton from "./components/AuthButton";
|
import AuthButton from './components/AuthButton';
|
||||||
import { Box, Button } from "@mui/material";
|
import { Box, Button } from '@mui/material';
|
||||||
import MainFrame from "./components/MainFrame/MainFrame";
|
import MainFrame from './components/MainFrame/MainFrame';
|
||||||
import LogoutButton from "./components/LogoutButton";
|
import LogoutButton from './components/LogoutButton';
|
||||||
import AddToQueue from "./components/AddToQueue/AddToQueue";
|
import AddToQueue from './components/AddToQueue/AddToQueue';
|
||||||
import { messageChannelContext } from './provider/MessageChannel';
|
import { messageChannelContext } from './provider/MessageChannel';
|
||||||
import { ClearAll, Folder } from "@mui/icons-material";
|
import { ClearAll, Folder } from '@mui/icons-material';
|
||||||
import StartQueueButton from "./components/StartQueue";
|
import StartQueueButton from './components/StartQueue';
|
||||||
import MenuBar from "./components/MenuBar/MenuBar";
|
import MenuBar from './components/MenuBar/MenuBar';
|
||||||
|
|
||||||
const Layout: React.FC = () => {
|
const Layout: React.FC = () => {
|
||||||
|
|
||||||
|
|
@ -27,6 +27,6 @@ const Layout: React.FC = () => {
|
||||||
</Box>
|
</Box>
|
||||||
<MainFrame />
|
<MainFrame />
|
||||||
</Box>;
|
</Box>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Layout;
|
export default Layout;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Container, Box, ThemeProvider, createTheme, Theme } from "@mui/material";
|
import { Container, Box, ThemeProvider, createTheme, Theme } from '@mui/material';
|
||||||
|
|
||||||
const makeTheme = (mode: 'dark'|'light') : Partial<Theme> => {
|
const makeTheme = (mode: 'dark'|'light') : Partial<Theme> => {
|
||||||
return createTheme({
|
return createTheme({
|
||||||
|
|
@ -16,6 +16,6 @@ const Style: FCWithChildren = ({children}) => {
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
</ThemeProvider>;
|
</ThemeProvider>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Style;
|
export default Style;
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Add } from "@mui/icons-material";
|
import { Add } from '@mui/icons-material';
|
||||||
import { Box, Button, Dialog, Divider } from "@mui/material";
|
import { Box, Button, Dialog, Divider } from '@mui/material';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import DownloadSelector from "./DownloadSelector/DownloadSelector";
|
import DownloadSelector from './DownloadSelector/DownloadSelector';
|
||||||
import EpisodeListing from "./DownloadSelector/Listing/EpisodeListing";
|
import EpisodeListing from './DownloadSelector/Listing/EpisodeListing';
|
||||||
import SearchBox from "./SearchBox/SearchBox";
|
import SearchBox from './SearchBox/SearchBox';
|
||||||
|
|
||||||
const AddToQueue: React.FC = () => {
|
const AddToQueue: React.FC = () => {
|
||||||
const [isOpen, setOpen] = React.useState(false);
|
const [isOpen, setOpen] = React.useState(false);
|
||||||
|
|
@ -21,7 +21,7 @@ const AddToQueue: React.FC = () => {
|
||||||
<Add />
|
<Add />
|
||||||
Add to Queue
|
Add to Queue
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default AddToQueue;
|
export default AddToQueue;
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Box, Button, TextField } from "@mui/material";
|
import { Box, Button, TextField } from '@mui/material';
|
||||||
import useStore from "../../../hooks/useStore";
|
import useStore from '../../../hooks/useStore';
|
||||||
import MultiSelect from "../../reusable/MultiSelect";
|
import MultiSelect from '../../reusable/MultiSelect';
|
||||||
import { messageChannelContext } from "../../../provider/MessageChannel";
|
import { messageChannelContext } from '../../../provider/MessageChannel';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from 'notistack';
|
||||||
|
|
||||||
type DownloadSelectorProps = {
|
type DownloadSelectorProps = {
|
||||||
onFinish?: () => unknown
|
onFinish?: () => unknown
|
||||||
|
|
@ -54,7 +54,7 @@ const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (onFinish)
|
if (onFinish)
|
||||||
onFinish();
|
onFinish();
|
||||||
}
|
};
|
||||||
|
|
||||||
const listEpisodes = async () => {
|
const listEpisodes = async () => {
|
||||||
if (!store.downloadOptions.id) {
|
if (!store.downloadOptions.id) {
|
||||||
|
|
@ -76,7 +76,7 @@ const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
return <Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
return <Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<Box sx={{ m: 2, gap: 1, display: 'flex', justifyContent: 'center', alignItems: 'center', flexWrap: 'wrap' }}>
|
<Box sx={{ m: 2, gap: 1, display: 'flex', justifyContent: 'center', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
|
@ -84,7 +84,7 @@ const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'downloadOptions',
|
type: 'downloadOptions',
|
||||||
payload: { ...store.downloadOptions, id: e.target.value }
|
payload: { ...store.downloadOptions, id: e.target.value }
|
||||||
})
|
});
|
||||||
}} label='Item ID' />
|
}} label='Item ID' />
|
||||||
<TextField type='number' value={store.downloadOptions.q} required onChange={e => {
|
<TextField type='number' value={store.downloadOptions.q} required onChange={e => {
|
||||||
const parsed = parseInt(e.target.value);
|
const parsed = parseInt(e.target.value);
|
||||||
|
|
@ -93,13 +93,13 @@ const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'downloadOptions',
|
type: 'downloadOptions',
|
||||||
payload: { ...store.downloadOptions, q: parsed }
|
payload: { ...store.downloadOptions, q: parsed }
|
||||||
})
|
});
|
||||||
}} label='Quality Level (0 for max)' />
|
}} label='Quality Level (0 for max)' />
|
||||||
<TextField disabled={store.downloadOptions.all} value={store.downloadOptions.e} required onChange={e => {
|
<TextField disabled={store.downloadOptions.all} value={store.downloadOptions.e} required onChange={e => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'downloadOptions',
|
type: 'downloadOptions',
|
||||||
payload: { ...store.downloadOptions, e: e.target.value }
|
payload: { ...store.downloadOptions, e: e.target.value }
|
||||||
})
|
});
|
||||||
}} label='Episode Select' />
|
}} label='Episode Select' />
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
title='Dub Languages'
|
title='Dub Languages'
|
||||||
|
|
@ -128,7 +128,7 @@ const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'downloadOptions',
|
type: 'downloadOptions',
|
||||||
payload: { ...store.downloadOptions, fileName: e.target.value }
|
payload: { ...store.downloadOptions, fileName: e.target.value }
|
||||||
})
|
});
|
||||||
}} sx={{ width: '50%' }} label='Filename' />
|
}} sx={{ width: '50%' }} label='Filename' />
|
||||||
<Button onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, all: !store.downloadOptions.all } })} variant={store.downloadOptions.all ? 'contained' : 'outlined'}>Download all</Button>
|
<Button onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, all: !store.downloadOptions.all } })} variant={store.downloadOptions.all ? 'contained' : 'outlined'}>Download all</Button>
|
||||||
<Button onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, but: !store.downloadOptions.but } })} variant={store.downloadOptions.but ? 'contained' : 'outlined'}>Download all but</Button>
|
<Button onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, but: !store.downloadOptions.but } })} variant={store.downloadOptions.but ? 'contained' : 'outlined'}>Download all but</Button>
|
||||||
|
|
@ -140,7 +140,7 @@ const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => {
|
||||||
<LoadingButton loading={loading} onClick={listEpisodes} variant='contained'>List episodes</LoadingButton>
|
<LoadingButton loading={loading} onClick={listEpisodes} variant='contained'>List episodes</LoadingButton>
|
||||||
<LoadingButton loading={loading} onClick={addToQueue} variant='contained'>Add to Queue</LoadingButton>
|
<LoadingButton loading={loading} onClick={addToQueue} variant='contained'>Add to Queue</LoadingButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DownloadSelector;
|
export default DownloadSelector;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Box, List, ListItem, Typography, Divider, Dialog, Select, MenuItem, FormControl, InputLabel, Checkbox } from "@mui/material";
|
import { Box, List, ListItem, Typography, Divider, Dialog, Select, MenuItem, FormControl, InputLabel, Checkbox } from '@mui/material';
|
||||||
import { CheckBox, CheckBoxOutlineBlank } from '@mui/icons-material'
|
import { CheckBox, CheckBoxOutlineBlank } from '@mui/icons-material';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import useStore from "../../../../hooks/useStore";
|
import useStore from '../../../../hooks/useStore';
|
||||||
|
|
||||||
|
|
||||||
const EpisodeListing: React.FC = () => {
|
const EpisodeListing: React.FC = () => {
|
||||||
|
|
@ -17,13 +17,13 @@ const EpisodeListing: React.FC = () => {
|
||||||
s.push(season);
|
s.push(season);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}, [ store.episodeListing ])
|
}, [ store.episodeListing ]);
|
||||||
|
|
||||||
const [selected, setSelected] = React.useState<string[]>([]);
|
const [selected, setSelected] = React.useState<string[]>([]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setSelected(parseSelect(store.downloadOptions.e));
|
setSelected(parseSelect(store.downloadOptions.e));
|
||||||
}, [ store.episodeListing ])
|
}, [ store.episodeListing ]);
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
@ -36,96 +36,96 @@ const EpisodeListing: React.FC = () => {
|
||||||
...store.downloadOptions,
|
...store.downloadOptions,
|
||||||
e: `${([...new Set([...parseSelect(store.downloadOptions.e), ...selected])]).join(',')}`
|
e: `${([...new Set([...parseSelect(store.downloadOptions.e), ...selected])]).join(',')}`
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return <Dialog open={store.episodeListing.length > 0} onClose={close} scroll='paper' maxWidth='xl' sx={{ p: 2 }}>
|
return <Dialog open={store.episodeListing.length > 0} onClose={close} scroll='paper' maxWidth='xl' sx={{ p: 2 }}>
|
||||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 200px 20px' }}>
|
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 200px 20px' }}>
|
||||||
<Typography color='text.primary' variant="h5" sx={{ textAlign: 'center', alignItems: 'center', justifyContent: 'center', display: 'flex' }}>
|
<Typography color='text.primary' variant="h5" sx={{ textAlign: 'center', alignItems: 'center', justifyContent: 'center', display: 'flex' }}>
|
||||||
Episodes
|
Episodes
|
||||||
</Typography>
|
</Typography>
|
||||||
<FormControl sx={{ mr: 2, mt: 2 }}>
|
<FormControl sx={{ mr: 2, mt: 2 }}>
|
||||||
<InputLabel id='seasonSelectLabel'>Season</InputLabel>
|
<InputLabel id='seasonSelectLabel'>Season</InputLabel>
|
||||||
<Select labelId="seasonSelectLabel" label='Season' value={season} onChange={(e) => setSeason(e.target.value)}>
|
<Select labelId="seasonSelectLabel" label='Season' value={season} onChange={(e) => setSeason(e.target.value)}>
|
||||||
<MenuItem value='all'>Show all Epsiodes</MenuItem>
|
<MenuItem value='all'>Show all Epsiodes</MenuItem>
|
||||||
{seasons.map((a, index) => {
|
{seasons.map((a, index) => {
|
||||||
return <MenuItem value={a} key={`MenuItem_SeasonSelect_${index}`}>
|
return <MenuItem value={a} key={`MenuItem_SeasonSelect_${index}`}>
|
||||||
{a}
|
{a}
|
||||||
</MenuItem>
|
</MenuItem>;
|
||||||
})}
|
})}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Box>
|
</Box>
|
||||||
<List>
|
<List>
|
||||||
<ListItem sx={{ display: 'grid', gridTemplateColumns: '25px 1fr 5fr' }}>
|
<ListItem sx={{ display: 'grid', gridTemplateColumns: '25px 1fr 5fr' }}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
indeterminate={store.episodeListing.some(a => selected.includes(a.e)) && !store.episodeListing.every(a => selected.includes(a.e))}
|
indeterminate={store.episodeListing.some(a => selected.includes(a.e)) && !store.episodeListing.every(a => selected.includes(a.e))}
|
||||||
checked={store.episodeListing.every(a => selected.includes(a.e))}
|
checked={store.episodeListing.every(a => selected.includes(a.e))}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
if (selected.length > 0) {
|
if (selected.length > 0) {
|
||||||
setSelected([]);
|
setSelected([]);
|
||||||
} else {
|
} else {
|
||||||
setSelected(store.episodeListing.map(a => a.e));
|
setSelected(store.episodeListing.map(a => a.e));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{store.episodeListing.filter((a) => season === 'all' ? true : a.season === season).map((item, index, { length }) => {
|
{store.episodeListing.filter((a) => season === 'all' ? true : a.season === season).map((item, index, { length }) => {
|
||||||
const e = isNaN(parseInt(item.e)) ? item.e : parseInt(item.e);
|
const e = isNaN(parseInt(item.e)) ? item.e : parseInt(item.e);
|
||||||
const isSelected = selected.includes(e.toString());
|
const isSelected = selected.includes(e.toString());
|
||||||
return <Box {...{ mouseData: isSelected }} key={`Episode_List_Item_${index}`} sx={{
|
return <Box {...{ mouseData: isSelected }} key={`Episode_List_Item_${index}`} sx={{
|
||||||
backdropFilter: isSelected ? 'brightness(1.5)' : '',
|
backdropFilter: isSelected ? 'brightness(1.5)' : '',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backdropFilter: 'brightness(1.5)'
|
backdropFilter: 'brightness(1.5)'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let arr: string[] = [];
|
let arr: string[] = [];
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
arr = [...selected.filter(a => a !== e.toString())];
|
arr = [...selected.filter(a => a !== e.toString())];
|
||||||
} else {
|
} else {
|
||||||
arr = [...selected, e.toString()];
|
arr = [...selected, e.toString()];
|
||||||
}
|
}
|
||||||
setSelected(arr.filter(a => a.length > 0));
|
setSelected(arr.filter(a => a.length > 0));
|
||||||
}}>
|
}}>
|
||||||
<ListItem sx={{ display: 'grid', gridTemplateColumns: '25px 50px 1fr 5fr' }}>
|
<ListItem sx={{ display: 'grid', gridTemplateColumns: '25px 50px 1fr 5fr' }}>
|
||||||
{ isSelected ? <CheckBox /> : <CheckBoxOutlineBlank /> }
|
{ isSelected ? <CheckBox /> : <CheckBoxOutlineBlank /> }
|
||||||
<Typography color='text.primary' sx={{ textAlign: 'center' }}>
|
<Typography color='text.primary' sx={{ textAlign: 'center' }}>
|
||||||
{e}
|
{e}
|
||||||
</Typography>
|
</Typography>
|
||||||
<img style={{ width: 'inherit', maxHeight: '200px', minWidth: '150px' }} src={item.img} alt="thumbnail" />
|
<img style={{ width: 'inherit', maxHeight: '200px', minWidth: '150px' }} src={item.img} alt="thumbnail" />
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', pl: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', pl: 1 }}>
|
||||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr min-content' }}>
|
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr min-content' }}>
|
||||||
<Typography color='text.primary' variant="h5">
|
<Typography color='text.primary' variant="h5">
|
||||||
{item.name}
|
{item.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography color='text.primary'>
|
<Typography color='text.primary'>
|
||||||
{item.time.startsWith('00:') ? item.time.slice(3) : item.time}
|
{item.time.startsWith('00:') ? item.time.slice(3) : item.time}
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Typography color='text.primary'>
|
|
||||||
{item.description}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'fit-content 1fr' }}>
|
|
||||||
<Typography>
|
|
||||||
<br />
|
|
||||||
Available audio languages: {item.lang.join(', ')}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</ListItem>
|
<Typography color='text.primary'>
|
||||||
{index < length - 1 && <Divider />}
|
{item.description}
|
||||||
</Box>
|
</Typography>
|
||||||
})}
|
<Box sx={{ display: 'grid', gridTemplateColumns: 'fit-content 1fr' }}>
|
||||||
</List>
|
<Typography>
|
||||||
</Dialog>
|
<br />
|
||||||
}
|
Available audio languages: {item.lang.join(', ')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</ListItem>
|
||||||
|
{index < length - 1 && <Divider />}
|
||||||
|
</Box>;
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</Dialog>;
|
||||||
|
};
|
||||||
|
|
||||||
const parseSelect = (s: string): string[] => {
|
const parseSelect = (s: string): string[] => {
|
||||||
const ret: string[] = [];
|
const ret: string[] = [];
|
||||||
s.split(',').forEach(item => {
|
s.split(',').forEach(item => {
|
||||||
if (item.includes('-')) {
|
if (item.includes('-')) {
|
||||||
let split = item.split('-');
|
const split = item.split('-');
|
||||||
if (split.length !== 2)
|
if (split.length !== 2)
|
||||||
return;
|
return;
|
||||||
const match = split[0].match(/[A-Za-z]+/);
|
const match = split[0].match(/[A-Za-z]+/);
|
||||||
|
|
@ -156,8 +156,8 @@ const parseSelect = (s: string): string[] => {
|
||||||
} else {
|
} else {
|
||||||
ret.push(item);
|
ret.push(item);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return [...new Set(ret)];
|
return [...new Set(ret)];
|
||||||
}
|
};
|
||||||
|
|
||||||
export default EpisodeListing;
|
export default EpisodeListing;
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Box, ClickAwayListener, Divider, List, ListItem, Paper, TextField, Typography } from "@mui/material";
|
import { Box, ClickAwayListener, Divider, List, ListItem, Paper, TextField, Typography } from '@mui/material';
|
||||||
import { SearchResponse } from "../../../../../../@types/messageHandler";
|
import { SearchResponse } from '../../../../../../@types/messageHandler';
|
||||||
import useStore from "../../../hooks/useStore";
|
import useStore from '../../../hooks/useStore';
|
||||||
import { messageChannelContext } from "../../../provider/MessageChannel";
|
import { messageChannelContext } from '../../../provider/MessageChannel';
|
||||||
import './SearchBox.css';
|
import './SearchBox.css';
|
||||||
import ContextMenu from "../../reusable/ContextMenu";
|
import ContextMenu from '../../reusable/ContextMenu';
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from 'notistack';
|
||||||
|
|
||||||
const SearchBox: React.FC = () => {
|
const SearchBox: React.FC = () => {
|
||||||
const messageHandler = React.useContext(messageChannelContext);
|
const messageHandler = React.useContext(messageChannelContext);
|
||||||
|
|
@ -52,7 +52,7 @@ const SearchBox: React.FC = () => {
|
||||||
searchResult.value.map((a, ind, arr) => {
|
searchResult.value.map((a, ind, arr) => {
|
||||||
const imageRef = React.createRef<HTMLImageElement>();
|
const imageRef = React.createRef<HTMLImageElement>();
|
||||||
return <Box key={a.id}>
|
return <Box key={a.id}>
|
||||||
<ListItem className='listitem-hover' onClick={(e) => {
|
<ListItem className='listitem-hover' onClick={() => {
|
||||||
selectItem(a.id);
|
selectItem(a.id);
|
||||||
setFocus(false);
|
setFocus(false);
|
||||||
}}>
|
}}>
|
||||||
|
|
@ -83,13 +83,13 @@ const SearchBox: React.FC = () => {
|
||||||
});
|
});
|
||||||
}} ]} popupItem={imageRef} />
|
}} ]} popupItem={imageRef} />
|
||||||
{(ind < arr.length - 1) && <Divider />}
|
{(ind < arr.length - 1) && <Divider />}
|
||||||
</Box>
|
</Box>;
|
||||||
})
|
})
|
||||||
: <></>}
|
: <></>}
|
||||||
</List>
|
</List>
|
||||||
</Paper>}
|
</Paper>}
|
||||||
</Box>
|
</Box>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SearchBox;
|
export default SearchBox;
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from "@mui/material";
|
import { Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from '@mui/material';
|
||||||
import { Check, Close } from '@mui/icons-material'
|
import { Check, Close } from '@mui/icons-material';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { messageChannelContext } from "../provider/MessageChannel";
|
import { messageChannelContext } from '../provider/MessageChannel';
|
||||||
import Require from "./Require";
|
import Require from './Require';
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from 'notistack';
|
||||||
|
|
||||||
const AuthButton: React.FC = () => {
|
const AuthButton: React.FC = () => {
|
||||||
const snackbar = useSnackbar();
|
const snackbar = useSnackbar();
|
||||||
|
|
@ -24,9 +24,9 @@ const AuthButton: React.FC = () => {
|
||||||
|
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
setAuthed((await messageChannel?.checkToken())?.isOk ?? false);
|
setAuthed((await messageChannel?.checkToken())?.isOk ?? false);
|
||||||
}
|
};
|
||||||
|
|
||||||
React.useEffect(() => { checkAuth() }, []);
|
React.useEffect(() => { checkAuth(); }, []);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!messageChannel)
|
if (!messageChannel)
|
||||||
|
|
@ -52,7 +52,7 @@ const AuthButton: React.FC = () => {
|
||||||
}
|
}
|
||||||
await checkAuth();
|
await checkAuth();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
return <Require value={messageChannel}>
|
return <Require value={messageChannel}>
|
||||||
<Dialog open={open}>
|
<Dialog open={open}>
|
||||||
|
|
@ -106,7 +106,7 @@ const AuthButton: React.FC = () => {
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Button startIcon={authed ? <Check />: <Close />} variant="contained" onClick={() => setOpen(true)}>Authenticate</Button>
|
<Button startIcon={authed ? <Check />: <Close />} variant="contained" onClick={() => setOpen(true)}>Authenticate</Button>
|
||||||
</Require>
|
</Require>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default AuthButton;
|
export default AuthButton;
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { ExitToApp } from "@mui/icons-material";
|
import { ExitToApp } from '@mui/icons-material';
|
||||||
import { Button } from "@mui/material";
|
import { Button } from '@mui/material';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import useStore from "../hooks/useStore";
|
import useStore from '../hooks/useStore';
|
||||||
import { messageChannelContext } from "../provider/MessageChannel";
|
import { messageChannelContext } from '../provider/MessageChannel';
|
||||||
import Require from "./Require";
|
import Require from './Require';
|
||||||
|
|
||||||
const LogoutButton: React.FC = () => {
|
const LogoutButton: React.FC = () => {
|
||||||
const messageChannel = React.useContext(messageChannelContext);
|
const messageChannel = React.useContext(messageChannelContext);
|
||||||
|
|
@ -16,10 +16,10 @@ const LogoutButton: React.FC = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'service',
|
type: 'service',
|
||||||
payload: undefined
|
payload: undefined
|
||||||
})
|
});
|
||||||
else
|
else
|
||||||
alert('Unable to change service');
|
alert('Unable to change service');
|
||||||
}
|
};
|
||||||
|
|
||||||
return <Require value={messageChannel}>
|
return <Require value={messageChannel}>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -29,8 +29,8 @@ const LogoutButton: React.FC = () => {
|
||||||
>
|
>
|
||||||
Service select
|
Service select
|
||||||
</Button>
|
</Button>
|
||||||
</Require>
|
</Require>;
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
export default LogoutButton;
|
export default LogoutButton;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { ExtendedProgress, QueueItem } from "../../../../../../@types/messageHandler";
|
import { ExtendedProgress, QueueItem } from '../../../../../../@types/messageHandler';
|
||||||
import { RandomEvent } from "../../../../../../@types/randomEvents";
|
import { RandomEvent } from '../../../../../../@types/randomEvents';
|
||||||
import { messageChannelContext } from "../../../provider/MessageChannel";
|
import { messageChannelContext } from '../../../provider/MessageChannel';
|
||||||
|
|
||||||
const useDownloadManager = () => {
|
const useDownloadManager = () => {
|
||||||
const messageHandler = React.useContext(messageChannelContext);
|
const messageHandler = React.useContext(messageChannelContext);
|
||||||
|
|
@ -13,15 +13,15 @@ const useDownloadManager = () => {
|
||||||
const handler = (ev: RandomEvent<'progress'>) => {
|
const handler = (ev: RandomEvent<'progress'>) => {
|
||||||
console.log(ev.data);
|
console.log(ev.data);
|
||||||
setProgressData(ev.data);
|
setProgressData(ev.data);
|
||||||
}
|
};
|
||||||
|
|
||||||
const currentHandler = (ev: RandomEvent<'current'>) => {
|
const currentHandler = (ev: RandomEvent<'current'>) => {
|
||||||
setCurrent(ev.data);
|
setCurrent(ev.data);
|
||||||
}
|
};
|
||||||
|
|
||||||
const finishHandler = () => {
|
const finishHandler = () => {
|
||||||
setProgressData(undefined);
|
setProgressData(undefined);
|
||||||
}
|
};
|
||||||
|
|
||||||
messageHandler?.randomEvents.on('progress', handler);
|
messageHandler?.randomEvents.on('progress', handler);
|
||||||
messageHandler?.randomEvents.on('current', currentHandler);
|
messageHandler?.randomEvents.on('current', currentHandler);
|
||||||
|
|
@ -35,6 +35,6 @@ const useDownloadManager = () => {
|
||||||
}, [messageHandler]);
|
}, [messageHandler]);
|
||||||
|
|
||||||
return { data: progressData, current};
|
return { data: progressData, current};
|
||||||
}
|
};
|
||||||
|
|
||||||
export default useDownloadManager;
|
export default useDownloadManager;
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { Box } from "@mui/material";
|
import { Box } from '@mui/material';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import './MainFrame.css';
|
import './MainFrame.css';
|
||||||
import Queue from "./Queue/Queue";
|
import Queue from './Queue/Queue';
|
||||||
|
|
||||||
const MainFrame: React.FC = () => {
|
const MainFrame: React.FC = () => {
|
||||||
return <Box sx={{ marginLeft: 1 }}>
|
return <Box sx={{ marginLeft: 1 }}>
|
||||||
<Queue />
|
<Queue />
|
||||||
</Box>
|
</Box>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default MainFrame;
|
export default MainFrame;
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Box, Button, CircularProgress, Divider, LinearProgress, Skeleton, Typography } from "@mui/material";
|
import { Box, Button, CircularProgress, Divider, LinearProgress, Skeleton, Typography } from '@mui/material';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { messageChannelContext } from "../../../provider/MessageChannel";
|
import { messageChannelContext } from '../../../provider/MessageChannel';
|
||||||
import { queueContext } from "../../../provider/QueueProvider";
|
import { queueContext } from '../../../provider/QueueProvider';
|
||||||
|
|
||||||
import useDownloadManager from "../DownloadManager/DownloadManager";
|
import useDownloadManager from '../DownloadManager/DownloadManager';
|
||||||
|
|
||||||
const Queue: React.FC = () => {
|
const Queue: React.FC = () => {
|
||||||
const { data, current } = useDownloadManager();
|
const { data, current } = useDownloadManager();
|
||||||
|
|
@ -12,7 +12,7 @@ const Queue: React.FC = () => {
|
||||||
|
|
||||||
|
|
||||||
if (!msg)
|
if (!msg)
|
||||||
return <>Never</>
|
return <>Never</>;
|
||||||
|
|
||||||
return data || queue.length > 0 ? <>
|
return data || queue.length > 0 ? <>
|
||||||
{data && <>
|
{data && <>
|
||||||
|
|
@ -114,8 +114,8 @@ const Queue: React.FC = () => {
|
||||||
<Skeleton variant='text' height={'100%'} />
|
<Skeleton variant='text' height={'100%'} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>;
|
||||||
}
|
};
|
||||||
|
|
||||||
const formatTime = (time: number) => {
|
const formatTime = (time: number) => {
|
||||||
time = Math.floor(time / 1000);
|
time = Math.floor(time / 1000);
|
||||||
|
|
@ -123,6 +123,6 @@ const formatTime = (time: number) => {
|
||||||
time = time % 60;
|
time = time % 60;
|
||||||
|
|
||||||
return `${minutes.toFixed(0).length < 2 ? `0${minutes}` : minutes}m${time.toFixed(0).length < 2 ? `0${time}` : time}s`;
|
return `${minutes.toFixed(0).length < 2 ? `0${minutes}` : minutes}m${time.toFixed(0).length < 2 ? `0${time}` : time}s`;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Queue;
|
export default Queue;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Box, Button, Menu, MenuItem } from "@mui/material";
|
import { Box, Button, Menu, MenuItem } from '@mui/material';
|
||||||
import React from "react"
|
import React from 'react';
|
||||||
import { messageChannelContext } from "../../provider/MessageChannel";
|
import { messageChannelContext } from '../../provider/MessageChannel';
|
||||||
|
|
||||||
const MenuBar: React.FC = () => {
|
const MenuBar: React.FC = () => {
|
||||||
const [ openMenu, setMenuOpen ] = React.useState<'settings'|'help'|undefined>();
|
const [ openMenu, setMenuOpen ] = React.useState<'settings'|'help'|undefined>();
|
||||||
|
|
@ -18,7 +18,7 @@ const MenuBar: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!msg)
|
if (!msg)
|
||||||
return <></>
|
return <></>;
|
||||||
|
|
||||||
return <Box sx={{ width: '100%', display: 'flex' }}>
|
return <Box sx={{ width: '100%', display: 'flex' }}>
|
||||||
<Button onClick={(e) => handleClick(e, 'settings')}>
|
<Button onClick={(e) => handleClick(e, 'settings')}>
|
||||||
|
|
@ -80,6 +80,6 @@ const MenuBar: React.FC = () => {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Box>;
|
</Box>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default MenuBar;
|
export default MenuBar;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Box, Backdrop, CircularProgress } from "@mui/material";
|
import { Box, Backdrop, CircularProgress } from '@mui/material';
|
||||||
|
|
||||||
export type RequireType<T> = {
|
export type RequireType<T> = {
|
||||||
value?: T
|
value?: T
|
||||||
|
|
@ -8,7 +8,7 @@ export type RequireType<T> = {
|
||||||
const Require = <T, >(props: React.PropsWithChildren<RequireType<T>>) => {
|
const Require = <T, >(props: React.PropsWithChildren<RequireType<T>>) => {
|
||||||
return props.value === undefined ? <Backdrop open>
|
return props.value === undefined ? <Backdrop open>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
</Backdrop> : <Box>{props.children}</Box>
|
</Backdrop> : <Box>{props.children}</Box>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Require;
|
export default Require;
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { PauseCircleFilled, PlayCircleFilled } from "@mui/icons-material";
|
import { PauseCircleFilled, PlayCircleFilled } from '@mui/icons-material';
|
||||||
import { Button } from "@mui/material";
|
import { Button } from '@mui/material';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { messageChannelContext } from "../provider/MessageChannel";
|
import { messageChannelContext } from '../provider/MessageChannel';
|
||||||
import Require from "./Require";
|
import Require from './Require';
|
||||||
|
|
||||||
const StartQueueButton: React.FC = () => {
|
const StartQueueButton: React.FC = () => {
|
||||||
const messageChannel = React.useContext(messageChannelContext);
|
const messageChannel = React.useContext(messageChannelContext);
|
||||||
|
|
@ -19,10 +19,10 @@ const StartQueueButton: React.FC = () => {
|
||||||
|
|
||||||
const change = async () => {
|
const change = async () => {
|
||||||
if (await messageChannel?.isDownloading())
|
if (await messageChannel?.isDownloading())
|
||||||
alert("The current download will be finished before the queue stops")
|
alert('The current download will be finished before the queue stops');
|
||||||
msg?.setDownloadQueue(!start);
|
msg?.setDownloadQueue(!start);
|
||||||
setStart(!start);
|
setStart(!start);
|
||||||
}
|
};
|
||||||
|
|
||||||
return <Require value={messageChannel}>
|
return <Require value={messageChannel}>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -34,8 +34,8 @@ const StartQueueButton: React.FC = () => {
|
||||||
start ? 'Stop Queue' : 'Start Queue'
|
start ? 'Stop Queue' : 'Start Queue'
|
||||||
}
|
}
|
||||||
</Button>
|
</Button>
|
||||||
</Require>
|
</Require>;
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
export default StartQueueButton;
|
export default StartQueueButton;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Box, Button, Divider, List, SxProps } from "@mui/material";
|
import { Box, Button, Divider, List, SxProps } from '@mui/material';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
|
|
||||||
export type Option = {
|
export type Option = {
|
||||||
text: string,
|
text: string,
|
||||||
|
|
@ -34,32 +34,32 @@ function ContextMenu<T extends HTMLElement, >(props: ContextMenuProps<T>) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
setAnchor({ x: ev.x + 10, y: ev.y + 10 });
|
setAnchor({ x: ev.x + 10, y: ev.y + 10 });
|
||||||
setShow(true);
|
setShow(true);
|
||||||
}
|
};
|
||||||
ref.current.addEventListener('contextmenu', listener);
|
ref.current.addEventListener('contextmenu', listener);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (ref.current)
|
if (ref.current)
|
||||||
ref.current.removeEventListener('contextmenu', listener)
|
ref.current.removeEventListener('contextmenu', listener);
|
||||||
};
|
};
|
||||||
}, [ props.popupItem ])
|
}, [ props.popupItem ]);
|
||||||
|
|
||||||
return show ? <Box sx={{ zIndex: 9999, p: 1, background: 'rgba(0, 0, 0, 0.75)', backdropFilter: 'blur(5px)', position: 'fixed', left: anchor.x, top: anchor.y }}>
|
return show ? <Box sx={{ zIndex: 9999, p: 1, background: 'rgba(0, 0, 0, 0.75)', backdropFilter: 'blur(5px)', position: 'fixed', left: anchor.x, top: anchor.y }}>
|
||||||
<List sx={{ p: 0, m: 0 }}>
|
<List sx={{ p: 0, m: 0 }}>
|
||||||
{props.options.map((item, i) => {
|
{props.options.map((item, i) => {
|
||||||
return item === 'divider' ? <Divider key={`ContextMenu_Divider_${i}_${item}`}/> :
|
return item === 'divider' ? <Divider key={`ContextMenu_Divider_${i}_${item}`}/> :
|
||||||
<Button color='inherit' key={`ContextMenu_Value_${i}_${item}`} onClick={() => {
|
<Button color='inherit' key={`ContextMenu_Value_${i}_${item}`} onClick={() => {
|
||||||
item.onClick();
|
item.onClick();
|
||||||
setShow(false);
|
setShow(false);
|
||||||
}} sx={buttonSx}>
|
}} sx={buttonSx}>
|
||||||
{item.text}
|
{item.text}
|
||||||
</Button>
|
</Button>;
|
||||||
})}
|
})}
|
||||||
<Divider />
|
<Divider />
|
||||||
<Button fullWidth color='inherit' onClick={() => setShow(false)} sx={buttonSx} >
|
<Button fullWidth color='inherit' onClick={() => setShow(false)} sx={buttonSx} >
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
</List>
|
</List>
|
||||||
</Box> : <></>
|
</Box> : <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ContextMenu;
|
export default ContextMenu;
|
||||||
|
|
@ -19,6 +19,6 @@ const LinearProgressWithLabel: React.FC<LinearProgressWithLabelProps> = (props)
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default LinearProgressWithLabel;
|
export default LinearProgressWithLabel;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select, Theme, useTheme } from "@mui/material";
|
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select, Theme, useTheme } from '@mui/material';
|
||||||
|
|
||||||
export type MultiSelectProps = {
|
export type MultiSelectProps = {
|
||||||
values: string[],
|
values: string[],
|
||||||
|
|
@ -41,7 +41,7 @@ const MultiSelect: React.FC<MultiSelectProps> = (props) => {
|
||||||
multiple
|
multiple
|
||||||
value={(props.selected ?? [])}
|
value={(props.selected ?? [])}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
const val = typeof e.target.value === "string" ? e.target.value.split(",") : e.target.value;
|
const val = typeof e.target.value === 'string' ? e.target.value.split(',') : e.target.value;
|
||||||
if (props.allOption && val.includes('all')) {
|
if (props.allOption && val.includes('all')) {
|
||||||
if (props.values.length === val.length - 1)
|
if (props.values.length === val.length - 1)
|
||||||
props.onChange([]);
|
props.onChange([]);
|
||||||
|
|
@ -68,7 +68,7 @@ const MultiSelect: React.FC<MultiSelectProps> = (props) => {
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default MultiSelect;
|
export default MultiSelect;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { StoreAction, StoreContext, StoreState } from "../provider/Store";
|
import { StoreAction, StoreContext, StoreState } from '../provider/Store';
|
||||||
|
|
||||||
const useStore = () => {
|
const useStore = () => {
|
||||||
const context = React.useContext(StoreContext as unknown as React.Context<[StoreState, React.Dispatch<StoreAction<keyof StoreState>>]>);
|
const context = React.useContext(StoreContext as unknown as React.Context<[StoreState, React.Dispatch<StoreAction<keyof StoreState>>]>);
|
||||||
|
|
@ -7,6 +7,6 @@ const useStore = () => {
|
||||||
throw new Error('useStore must be used under Store');
|
throw new Error('useStore must be used under Store');
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default useStore;
|
export default useStore;
|
||||||
|
|
@ -4,8 +4,8 @@ import App from './App';
|
||||||
import ServiceProvider from './provider/ServiceProvider';
|
import ServiceProvider from './provider/ServiceProvider';
|
||||||
import Style from './Style';
|
import Style from './Style';
|
||||||
import MessageChannel from './provider/MessageChannel';
|
import MessageChannel from './provider/MessageChannel';
|
||||||
import { IconButton } from "@mui/material";
|
import { IconButton } from '@mui/material';
|
||||||
import { CloseOutlined } from "@mui/icons-material";
|
import { CloseOutlined } from '@mui/icons-material';
|
||||||
import { SnackbarProvider, SnackbarKey } from 'notistack';
|
import { SnackbarProvider, SnackbarKey } from 'notistack';
|
||||||
import Store from './provider/Store';
|
import Store from './provider/Store';
|
||||||
import ErrorHandler from './provider/ErrorHandler';
|
import ErrorHandler from './provider/ErrorHandler';
|
||||||
|
|
@ -18,7 +18,7 @@ const onClickDismiss = (key: SnackbarKey | undefined) => () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const container = document.getElementById('root');
|
const container = document.getElementById('root');
|
||||||
const root = createRoot(container!);
|
const root = createRoot(container as HTMLElement);
|
||||||
root.render(
|
root.render(
|
||||||
<ErrorHandler>
|
<ErrorHandler>
|
||||||
<Store>
|
<Store>
|
||||||
|
|
@ -29,7 +29,7 @@ root.render(
|
||||||
<CloseOutlined />
|
<CloseOutlined />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Style>
|
<Style>
|
||||||
<MessageChannel>
|
<MessageChannel>
|
||||||
<ServiceProvider>
|
<ServiceProvider>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from '@mui/material';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
|
|
||||||
export default class ErrorHandler extends React.Component<{
|
export default class ErrorHandler extends React.Component<{
|
||||||
children: React.ReactNode|React.ReactNode[]
|
children: React.ReactNode|React.ReactNode[]
|
||||||
|
|
@ -14,26 +14,26 @@ export default class ErrorHandler extends React.Component<{
|
||||||
children: React.ReactNode|React.ReactNode[]
|
children: React.ReactNode|React.ReactNode[]
|
||||||
}) {
|
}) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { error: undefined }
|
this.state = { error: undefined };
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(er: Error, stack: React.ErrorInfo) {
|
componentDidCatch(er: Error, stack: React.ErrorInfo) {
|
||||||
this.setState({ error: { er, stack } })
|
this.setState({ error: { er, stack } });
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
return this.state.error ?
|
return this.state.error ?
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', p: 2 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', p: 2 }}>
|
||||||
<Typography variant='body1' color='red'>
|
<Typography variant='body1' color='red'>
|
||||||
{`${this.state.error.er.name}: ${this.state.error.er.message}`}
|
{`${this.state.error.er.name}: ${this.state.error.er.message}`}
|
||||||
<br/>
|
<br/>
|
||||||
{this.state.error.stack.componentStack.split('\n').map(a => {
|
{this.state.error.stack.componentStack.split('\n').map(a => {
|
||||||
return <>
|
return <>
|
||||||
{a}
|
{a}
|
||||||
<br/>
|
<br/>
|
||||||
</>
|
</>;
|
||||||
})}
|
})}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box> : this.props.children;
|
</Box> : this.props.children;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,9 +4,9 @@ import useStore from '../hooks/useStore';
|
||||||
import type { MessageTypes, WSMessage, WSMessageWithID } from '../../../../@types/ws';
|
import type { MessageTypes, WSMessage, WSMessageWithID } from '../../../../@types/ws';
|
||||||
import type { Handler, RandomEvent, RandomEvents } from '../../../../@types/randomEvents';
|
import type { Handler, RandomEvent, RandomEvents } from '../../../../@types/randomEvents';
|
||||||
import { Avatar, Box, Button, TextField, Typography } from '@mui/material';
|
import { Avatar, Box, Button, TextField, Typography } from '@mui/material';
|
||||||
import { v4 } from "uuid";
|
import { v4 } from 'uuid';
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from 'notistack';
|
||||||
import { LockOutlined, PowerSettingsNew } from '@mui/icons-material'
|
import { LockOutlined, PowerSettingsNew } from '@mui/icons-material';
|
||||||
import { GUIConfig } from '../../../../modules/module.cfg-loader';
|
import { GUIConfig } from '../../../../modules/module.cfg-loader';
|
||||||
|
|
||||||
export type FrontEndMessanges = (MessageHandler & { randomEvents: RandomEventHandler, logout: () => Promise<boolean> });
|
export type FrontEndMessanges = (MessageHandler & { randomEvents: RandomEventHandler, logout: () => Promise<boolean> });
|
||||||
|
|
@ -49,7 +49,7 @@ async function messageAndResponse<T extends keyof MessageTypes>(socket: WebSocke
|
||||||
socket.removeEventListener('message', handler);
|
socket.removeEventListener('message', handler);
|
||||||
resolve(parsed);
|
resolve(parsed);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
socket.addEventListener('message', handler);
|
socket.addEventListener('message', handler);
|
||||||
});
|
});
|
||||||
const toSend = msg as WSMessageWithID<T>;
|
const toSend = msg as WSMessageWithID<T>;
|
||||||
|
|
@ -109,11 +109,11 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
||||||
setSocket(wws);
|
setSocket(wws);
|
||||||
});
|
});
|
||||||
wws.addEventListener('error', (er) => {
|
wws.addEventListener('error', (er) => {
|
||||||
console.error(`[ERROR] [WS]`, er);
|
console.error('[ERROR] [WS]', er);
|
||||||
enqueueSnackbar('Unable to connect to server. Please check the password and try again.', {
|
enqueueSnackbar('Unable to connect to server. Please check the password and try again.', {
|
||||||
variant: 'error'
|
variant: 'error'
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setup = async (ev: React.FormEvent<HTMLFormElement>) => {
|
const setup = async (ev: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
|
@ -126,7 +126,7 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
||||||
port: parseInt(formData.get('port')?.toString() ?? '') ?? 3000,
|
port: parseInt(formData.get('port')?.toString() ?? '') ?? 3000,
|
||||||
password: password ? password.toString() : undefined
|
password: password ? password.toString() : undefined
|
||||||
} as GUIConfig;
|
} as GUIConfig;
|
||||||
await messageAndResponse(socket!, { name: 'setupServer', data });
|
await messageAndResponse(socket, { name: 'setupServer', data });
|
||||||
enqueueSnackbar(`The following settings have been set: Port=${data.port}, Password=${data.password ?? 'noPasswordRequired'}`, {
|
enqueueSnackbar(`The following settings have been set: Port=${data.port}, Password=${data.password ?? 'noPasswordRequired'}`, {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
persist: true
|
persist: true
|
||||||
|
|
@ -135,7 +135,7 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
||||||
variant: 'info',
|
variant: 'info',
|
||||||
persist: true
|
persist: true
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const randomEventHandler = React.useMemo(() => new RandomEventHandler(), []);
|
const randomEventHandler = React.useMemo(() => new RandomEventHandler(), []);
|
||||||
|
|
||||||
|
|
@ -149,7 +149,7 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
||||||
if (store.service !== currentService.data)
|
if (store.service !== currentService.data)
|
||||||
messageAndResponse(socket, { name: 'setup', data: store.service });
|
messageAndResponse(socket, { name: 'setup', data: store.service });
|
||||||
})();
|
})();
|
||||||
}, [store.service, dispatch, socket])
|
}, [store.service, dispatch, socket]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!socket)
|
if (!socket)
|
||||||
|
|
@ -158,7 +158,7 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
||||||
const listener = (initalData: MessageEvent<string>) => {
|
const listener = (initalData: MessageEvent<string>) => {
|
||||||
const data = JSON.parse(initalData.data) as RandomEvent<'finish'>;
|
const data = JSON.parse(initalData.data) as RandomEvent<'finish'>;
|
||||||
randomEventHandler.emit(data.name, data);
|
randomEventHandler.emit(data.name, data);
|
||||||
}
|
};
|
||||||
socket.addEventListener('message', listener);
|
socket.addEventListener('message', listener);
|
||||||
return () => {
|
return () => {
|
||||||
socket.removeEventListener('message', listener);
|
socket.removeEventListener('message', listener);
|
||||||
|
|
@ -170,7 +170,7 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
||||||
|
|
||||||
if (socket === undefined) {
|
if (socket === undefined) {
|
||||||
if (usePassword === 'no') {
|
if (usePassword === 'no') {
|
||||||
connect(undefined)
|
connect(undefined);
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
return <Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', justifyItems: 'center', alignItems: 'center' }}>
|
return <Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', justifyItems: 'center', alignItems: 'center' }}>
|
||||||
|
|
@ -233,7 +233,7 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
||||||
clearQueue: async () => await messageAndResponse(socket, { name: 'clearQueue', data: undefined }),
|
clearQueue: async () => await messageAndResponse(socket, { name: 'clearQueue', data: undefined }),
|
||||||
setDownloadQueue: async (data) => await messageAndResponse(socket, { name: 'setDownloadQueue', data }),
|
setDownloadQueue: async (data) => await messageAndResponse(socket, { name: 'setDownloadQueue', data }),
|
||||||
getDownloadQueue: async () => (await messageAndResponse(socket, { name: 'getDownloadQueue', data: undefined })).data,
|
getDownloadQueue: async () => (await messageAndResponse(socket, { name: 'getDownloadQueue', data: undefined })).data,
|
||||||
}
|
};
|
||||||
|
|
||||||
return <messageChannelContext.Provider value={messageHandler}>
|
return <messageChannelContext.Provider value={messageHandler}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,11 @@ const QueueProvider: FCWithChildren = ({ children }) => {
|
||||||
}
|
}
|
||||||
const listener = (ev: RandomEvent<'queueChange'>) => {
|
const listener = (ev: RandomEvent<'queueChange'>) => {
|
||||||
setQueue(ev.data);
|
setQueue(ev.data);
|
||||||
}
|
};
|
||||||
msg?.randomEvents.on('queueChange', listener);
|
msg?.randomEvents.on('queueChange', listener);
|
||||||
return () => {
|
return () => {
|
||||||
msg?.randomEvents.removeListener('queueChange', listener);
|
msg?.randomEvents.removeListener('queueChange', listener);
|
||||||
}
|
};
|
||||||
}, [ msg ]);
|
}, [ msg ]);
|
||||||
|
|
||||||
return <queueContext.Provider value={queue}>
|
return <queueContext.Provider value={queue}>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const ServiceProvider: FCWithChildren = ({ children }) => {
|
||||||
type: 'service',
|
type: 'service',
|
||||||
payload: s
|
payload: s
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return service === undefined ?
|
return service === undefined ?
|
||||||
<Box>
|
<Box>
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ export type StoreAction<T extends (keyof StoreState)> = {
|
||||||
|
|
||||||
const Reducer = <T extends keyof StoreState,>(state: StoreState, action: StoreAction<T>): StoreState => {
|
const Reducer = <T extends keyof StoreState,>(state: StoreState, action: StoreAction<T>): StoreState => {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
default:
|
default:
|
||||||
return { ...state, [action.type]: action.payload }
|
return { ...state, [action.type]: action.payload };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import ServiceHandler from './serviceHandler';
|
||||||
import open from 'open';
|
import open from 'open';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { PublicWebSocket } from './websocket';
|
import { PublicWebSocket } from './websocket';
|
||||||
|
import { console } from '../../modules/log';
|
||||||
|
|
||||||
process.title = 'AniDL';
|
process.title = 'AniDL';
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ app.use(cors());
|
||||||
app.use(express.static(path.join(workingDir, 'gui', 'server', 'build'), { maxAge: 1000 * 60 * 20 }));
|
app.use(express.static(path.join(workingDir, 'gui', 'server', 'build'), { maxAge: 1000 * 60 * 20 }));
|
||||||
|
|
||||||
const server = app.listen(cfg.gui.port, () => {
|
const server = app.listen(cfg.gui.port, () => {
|
||||||
console.log(`[INFO] GUI server started on port ${cfg.gui.port}`);
|
console.info(`GUI server started on port ${cfg.gui.port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
new PublicWebSocket(server);
|
new PublicWebSocket(server);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import copy from 'copy-to-clipboard';
|
||||||
import open from 'open';
|
import open from 'open';
|
||||||
import { cfg } from '..';
|
import { cfg } from '..';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { console } from '../../../modules/log';
|
||||||
|
|
||||||
export default class Base {
|
export default class Base {
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ export default class Base {
|
||||||
}
|
}
|
||||||
|
|
||||||
alertError(error: Error) {
|
alertError(error: Error) {
|
||||||
console.log(`[ERROR] ${error}`);
|
console.error(`${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeProgressHandler(videoInfo: DownloadInfo) {
|
makeProgressHandler(videoInfo: DownloadInfo) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { buildDefault, getDefault } from '../../../modules/module.args';
|
||||||
import { languages, subtitleLanguagesFilter } from '../../../modules/module.langsData';
|
import { languages, subtitleLanguagesFilter } from '../../../modules/module.langsData';
|
||||||
import WebSocketHandler from '../websocket';
|
import WebSocketHandler from '../websocket';
|
||||||
import Base from './base';
|
import Base from './base';
|
||||||
|
import { console } from '../../../modules/log';
|
||||||
|
|
||||||
class CrunchyHandler extends Base implements MessageHandler {
|
class CrunchyHandler extends Base implements MessageHandler {
|
||||||
private crunchy: Crunchy;
|
private crunchy: Crunchy;
|
||||||
|
|
@ -38,7 +39,7 @@ class CrunchyHandler extends Base implements MessageHandler {
|
||||||
|
|
||||||
public async resolveItems(data: ResolveItemsData): Promise<boolean> {
|
public async resolveItems(data: ResolveItemsData): Promise<boolean> {
|
||||||
await this.crunchy.refreshToken(true);
|
await this.crunchy.refreshToken(true);
|
||||||
console.log(`[DEBUG] Got resolve options: ${JSON.stringify(data)}`);
|
console.debug(`Got resolve options: ${JSON.stringify(data)}`);
|
||||||
const res = await this.crunchy.downloadFromSeriesID(data.id, data);
|
const res = await this.crunchy.downloadFromSeriesID(data.id, data);
|
||||||
if (!res.isOk)
|
if (!res.isOk)
|
||||||
return res.isOk;
|
return res.isOk;
|
||||||
|
|
@ -62,7 +63,7 @@ class CrunchyHandler extends Base implements MessageHandler {
|
||||||
|
|
||||||
public async search(data: SearchData): Promise<SearchResponse> {
|
public async search(data: SearchData): Promise<SearchResponse> {
|
||||||
await this.crunchy.refreshToken(true);
|
await this.crunchy.refreshToken(true);
|
||||||
console.log(`[DEBUG] Got search options: ${JSON.stringify(data)}`);
|
console.debug(`Got search options: ${JSON.stringify(data)}`);
|
||||||
const crunchySearch = await this.crunchy.doSearch(data);
|
const crunchySearch = await this.crunchy.doSearch(data);
|
||||||
if (!crunchySearch.isOk) {
|
if (!crunchySearch.isOk) {
|
||||||
this.crunchy.refreshToken();
|
this.crunchy.refreshToken();
|
||||||
|
|
@ -85,7 +86,7 @@ class CrunchyHandler extends Base implements MessageHandler {
|
||||||
|
|
||||||
public async downloadItem(data: DownloadData) {
|
public async downloadItem(data: DownloadData) {
|
||||||
await this.crunchy.refreshToken(true);
|
await this.crunchy.refreshToken(true);
|
||||||
console.log(`[DEBUG] Got download options: ${JSON.stringify(data)}`);
|
console.debug(`Got download options: ${JSON.stringify(data)}`);
|
||||||
this.setDownloading(true);
|
this.setDownloading(true);
|
||||||
const _default = buildDefault() as ArgvType;
|
const _default = buildDefault() as ArgvType;
|
||||||
const res = await this.crunchy.downloadFromSeriesID(data.id, {
|
const res = await this.crunchy.downloadFromSeriesID(data.id, {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { buildDefault, getDefault } from '../../../modules/module.args';
|
||||||
import { languages, subtitleLanguagesFilter } from '../../../modules/module.langsData';
|
import { languages, subtitleLanguagesFilter } from '../../../modules/module.langsData';
|
||||||
import WebSocketHandler from '../websocket';
|
import WebSocketHandler from '../websocket';
|
||||||
import Base from './base';
|
import Base from './base';
|
||||||
|
import { console } from '../../../modules/log';
|
||||||
|
|
||||||
class FunimationHandler extends Base implements MessageHandler {
|
class FunimationHandler extends Base implements MessageHandler {
|
||||||
private funi: Funimation;
|
private funi: Funimation;
|
||||||
|
|
@ -52,7 +53,7 @@ class FunimationHandler extends Base implements MessageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async resolveItems(data: ResolveItemsData): Promise<boolean> {
|
public async resolveItems(data: ResolveItemsData): Promise<boolean> {
|
||||||
console.log(`[DEBUG] Got resolve options: ${JSON.stringify(data)}`);
|
console.debug(`Got resolve options: ${JSON.stringify(data)}`);
|
||||||
const res = await this.funi.getShow(false, { ...data, id: parseInt(data.id) });
|
const res = await this.funi.getShow(false, { ...data, id: parseInt(data.id) });
|
||||||
if (!res.isOk)
|
if (!res.isOk)
|
||||||
return res.isOk;
|
return res.isOk;
|
||||||
|
|
@ -74,7 +75,7 @@ class FunimationHandler extends Base implements MessageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async search(data: SearchData): Promise<SearchResponse> {
|
public async search(data: SearchData): Promise<SearchResponse> {
|
||||||
console.log(`[DEBUG] Got search options: ${JSON.stringify(data)}`);
|
console.debug(`Got search options: ${JSON.stringify(data)}`);
|
||||||
const funiSearch = await this.funi.searchShow(false, data);
|
const funiSearch = await this.funi.searchShow(false, data);
|
||||||
if (!funiSearch.isOk)
|
if (!funiSearch.isOk)
|
||||||
return funiSearch;
|
return funiSearch;
|
||||||
|
|
@ -98,7 +99,7 @@ class FunimationHandler extends Base implements MessageHandler {
|
||||||
|
|
||||||
public async downloadItem(data: QueueItem) {
|
public async downloadItem(data: QueueItem) {
|
||||||
this.setDownloading(true);
|
this.setDownloading(true);
|
||||||
console.log(`[DEBUG] Got download options: ${JSON.stringify(data)}`);
|
console.debug(`Got download options: ${JSON.stringify(data)}`);
|
||||||
const res = await this.funi.getShow(false, { all: false, but: false, id: parseInt(data.id), e: data.e });
|
const res = await this.funi.getShow(false, { all: false, but: false, id: parseInt(data.id), e: data.e });
|
||||||
const _default = buildDefault() as ArgvType;
|
const _default = buildDefault() as ArgvType;
|
||||||
if (!res.isOk)
|
if (!res.isOk)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { MessageTypes, UnknownWSMessage, WSMessage } from '../../@types/ws';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { cfg } from '.';
|
import { cfg } from '.';
|
||||||
import { isSetuped } from '../../modules/module.cfg-loader';
|
import { isSetuped } from '../../modules/module.cfg-loader';
|
||||||
|
import { console } from '../../modules/log';
|
||||||
|
|
||||||
declare interface ExternalEvent {
|
declare interface ExternalEvent {
|
||||||
on<T extends keyof MessageTypes>(event: T, listener: (msg: WSMessage<T>, respond: (data: MessageTypes[T][1]) => void) => void): this;
|
on<T extends keyof MessageTypes>(event: T, listener: (msg: WSMessage<T>, respond: (data: MessageTypes[T][1]) => void) => void): this;
|
||||||
|
|
@ -23,8 +24,8 @@ export default class WebSocketHandler {
|
||||||
this.wsServer = new ws.WebSocketServer({ noServer: true, path: '/ws' });
|
this.wsServer = new ws.WebSocketServer({ noServer: true, path: '/ws' });
|
||||||
|
|
||||||
this.wsServer.on('connection', (socket, req) => {
|
this.wsServer.on('connection', (socket, req) => {
|
||||||
console.log(`[INFO] [WS] Connection from '${req.socket.remoteAddress}'`);
|
console.info(`[WS] Connection from '${req.socket.remoteAddress}'`);
|
||||||
socket.on('error', (er) => console.log(`[ERROR] [WS] ${er}`));
|
socket.on('error', (er) => console.error(`[WS] ${er}`));
|
||||||
socket.on('message', (data) => {
|
socket.on('message', (data) => {
|
||||||
const json = JSON.parse(data.toString()) as UnknownWSMessage;
|
const json = JSON.parse(data.toString()) as UnknownWSMessage;
|
||||||
this.events.emit(json.name, json as any, (data) => {
|
this.events.emit(json.name, json as any, (data) => {
|
||||||
|
|
@ -37,7 +38,7 @@ export default class WebSocketHandler {
|
||||||
name: json.name
|
name: json.name
|
||||||
}), (er) => {
|
}), (er) => {
|
||||||
if (er)
|
if (er)
|
||||||
console.log(`[ERROR] [WS] ${er}`);
|
console.error(`[WS] ${er}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -50,7 +51,7 @@ export default class WebSocketHandler {
|
||||||
if (!this.authenticate(request)) {
|
if (!this.authenticate(request)) {
|
||||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
console.log(`[INFO] [WS] ${request.socket.remoteAddress} tried to connect but used a wrong password.`);
|
console.info(`[WS] ${request.socket.remoteAddress} tried to connect but used a wrong password.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.wsServer.handleUpgrade(request, socket, head, socket => {
|
this.wsServer.handleUpgrade(request, socket, head, socket => {
|
||||||
|
|
@ -65,7 +66,7 @@ export default class WebSocketHandler {
|
||||||
return;
|
return;
|
||||||
client.send(JSON.stringify(data), (er) => {
|
client.send(JSON.stringify(data), (er) => {
|
||||||
if (er)
|
if (er)
|
||||||
console.log(`[ERROR] [WS] ${er}`);
|
console.error(`[WS] ${er}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -84,8 +85,8 @@ export class PublicWebSocket {
|
||||||
this.wsServer = new ws.WebSocketServer({ noServer: true, path: '/public' });
|
this.wsServer = new ws.WebSocketServer({ noServer: true, path: '/public' });
|
||||||
|
|
||||||
this.wsServer.on('connection', (socket, req) => {
|
this.wsServer.on('connection', (socket, req) => {
|
||||||
console.log(`[INFO] [WS] Connection to public ws from '${req.socket.remoteAddress}'`);
|
console.info(`[WS] Connection to public ws from '${req.socket.remoteAddress}'`);
|
||||||
socket.on('error', (er) => console.log(`[ERROR] [WS] ${er}`));
|
socket.on('error', (er) => console.error(`[WS] ${er}`));
|
||||||
socket.on('message', (msg) => {
|
socket.on('message', (msg) => {
|
||||||
const data = JSON.parse(msg.toString()) as UnknownWSMessage;
|
const data = JSON.parse(msg.toString()) as UnknownWSMessage;
|
||||||
switch (data.name) {
|
switch (data.name) {
|
||||||
|
|
@ -115,7 +116,7 @@ export class PublicWebSocket {
|
||||||
name
|
name
|
||||||
}), (er) => {
|
}), (er) => {
|
||||||
if (er)
|
if (er)
|
||||||
console.log(`[ERROR] [WS] ${er}`);
|
console.error(`[WS] ${er}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
13
index.ts
13
index.ts
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { console } from './modules/log';
|
||||||
import { ServiceClass } from './@types/serviceClassInterface';
|
import { ServiceClass } from './@types/serviceClassInterface';
|
||||||
import { appArgv, overrideArguments } from './modules/module.app-args';
|
import { appArgv, overrideArguments } from './modules/module.app-args';
|
||||||
import * as yamlCfg from './modules/module.cfg-loader';
|
import * as yamlCfg from './modules/module.cfg-loader';
|
||||||
|
|
@ -12,29 +13,29 @@ import update from './modules/module.updater';
|
||||||
await update(argv.update);
|
await update(argv.update);
|
||||||
|
|
||||||
if (argv.all && argv.but) {
|
if (argv.all && argv.but) {
|
||||||
console.log('[ERROR] --all and --but exclude each other!');
|
console.error('--all and --but exclude each other!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.addArchive) {
|
if (argv.addArchive) {
|
||||||
if (argv.service === 'funi') {
|
if (argv.service === 'funi') {
|
||||||
if (argv.s === undefined)
|
if (argv.s === undefined)
|
||||||
return console.log('[ERROR] `-s` not found');
|
return console.error('`-s` not found');
|
||||||
addToArchive({
|
addToArchive({
|
||||||
service: 'funi',
|
service: 'funi',
|
||||||
type: 's'
|
type: 's'
|
||||||
}, argv.s);
|
}, argv.s);
|
||||||
console.log('[INFO] Added %s to the downloadArchive list', argv.s);
|
console.info('Added %s to the downloadArchive list', argv.s);
|
||||||
} else if (argv.service === 'crunchy') {
|
} else if (argv.service === 'crunchy') {
|
||||||
if (argv.s === undefined && argv.series === undefined)
|
if (argv.s === undefined && argv.series === undefined)
|
||||||
return console.log('[ERROR] `-s` or `--srz` not found');
|
return console.error('`-s` or `--srz` not found');
|
||||||
if (argv.s && argv.series)
|
if (argv.s && argv.series)
|
||||||
return console.log('[ERROR] Both `-s` and `--srz` found');
|
return console.error('Both `-s` and `--srz` found');
|
||||||
addToArchive({
|
addToArchive({
|
||||||
service: 'crunchy',
|
service: 'crunchy',
|
||||||
type: argv.s === undefined ? 'srz' : 's'
|
type: argv.s === undefined ? 'srz' : 's'
|
||||||
}, (argv.s === undefined ? argv.series : argv.s) as string);
|
}, (argv.s === undefined ? argv.series : argv.s) as string);
|
||||||
console.log('[INFO] Added %s to the downloadArchive list', (argv.s === undefined ? argv.series : argv.s));
|
console.info('Added %s to the downloadArchive list', (argv.s === undefined ? argv.series : argv.s));
|
||||||
}
|
}
|
||||||
} else if (argv.downloadArchive) {
|
} else if (argv.downloadArchive) {
|
||||||
const ids = makeCommand(argv.service);
|
const ids = makeCommand(argv.service);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import pkg from '../package.json';
|
||||||
import modulesCleanup from 'removeNPMAbsolutePaths';
|
import modulesCleanup from 'removeNPMAbsolutePaths';
|
||||||
import { exec } from 'pkg';
|
import { exec } from 'pkg';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
|
import { console } from './log';
|
||||||
|
|
||||||
const buildsDir = './_builds';
|
const buildsDir = './_builds';
|
||||||
const nodeVer = 'node16-';
|
const nodeVer = 'node16-';
|
||||||
|
|
@ -40,12 +41,12 @@ async function buildBinary(buildType: BuildTypes, gui: boolean) {
|
||||||
'--target', nodeVer + getTarget(buildType),
|
'--target', nodeVer + getTarget(buildType),
|
||||||
'--output', `${buildDir}/${pkg.short_name}`,
|
'--output', `${buildDir}/${pkg.short_name}`,
|
||||||
];
|
];
|
||||||
console.log(`[Build] Build configuration: ${buildFull}`);
|
console.info(`[Build] Build configuration: ${buildFull}`);
|
||||||
try {
|
try {
|
||||||
await exec(buildConfig);
|
await exec(buildConfig);
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
console.log(e);
|
console.info(e);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
fs.mkdirSync(`${buildDir}/config`);
|
fs.mkdirSync(`${buildDir}/config`);
|
||||||
|
|
|
||||||
441
modules/hls-download.ts
Normal file
441
modules/hls-download.ts
Normal file
|
|
@ -0,0 +1,441 @@
|
||||||
|
// build-in
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import fs from 'fs';
|
||||||
|
import url from 'url';
|
||||||
|
|
||||||
|
// extra
|
||||||
|
import shlp from 'sei-helper';
|
||||||
|
import got, { Response } from 'got';
|
||||||
|
|
||||||
|
import { console } from './log';
|
||||||
|
import { ProgressData } from '../@types/messageHandler';
|
||||||
|
|
||||||
|
// The following function should fix an issue with downloading. For more information see https://github.com/sindresorhus/got/issues/1489
|
||||||
|
const fixMiddleWare = (res: Response) => {
|
||||||
|
const isResponseOk = (response: Response) => {
|
||||||
|
const {statusCode} = response;
|
||||||
|
const limitStatusCode = response.request.options.followRedirect ? 299 : 399;
|
||||||
|
|
||||||
|
return (statusCode >= 200 && statusCode <= limitStatusCode) || statusCode === 304;
|
||||||
|
};
|
||||||
|
if (isResponseOk(res)) {
|
||||||
|
res.request.destroy();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HLSCallback = (data: ProgressData) => unknown;
|
||||||
|
|
||||||
|
type M3U8Json = {
|
||||||
|
segments: Record<string, unknown>[],
|
||||||
|
mediaSequence?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Segment = {
|
||||||
|
uri: string
|
||||||
|
key: Key,
|
||||||
|
byterange?: {
|
||||||
|
offset: number,
|
||||||
|
length: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Key = {
|
||||||
|
uri: string,
|
||||||
|
iv: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HLSOptions = {
|
||||||
|
m3u8json: M3U8Json,
|
||||||
|
output?: string,
|
||||||
|
threads?: number,
|
||||||
|
retries?: number,
|
||||||
|
offset?: number,
|
||||||
|
baseurl?: string,
|
||||||
|
skipInit?: boolean,
|
||||||
|
timeout?: number,
|
||||||
|
fsRetryTime?: number,
|
||||||
|
override?: 'Y'|'y'|'N'|'n'|'C'|'c'
|
||||||
|
callback?: HLSCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
type Data = {
|
||||||
|
parts: {
|
||||||
|
first: number,
|
||||||
|
total: number,
|
||||||
|
completed: number
|
||||||
|
},
|
||||||
|
m3u8json: M3U8Json,
|
||||||
|
outputFile: string,
|
||||||
|
threads: number,
|
||||||
|
retries: number,
|
||||||
|
offset: number,
|
||||||
|
baseurl?: string
|
||||||
|
skipInit?: boolean,
|
||||||
|
keys: {
|
||||||
|
[uri: string]: Buffer|string
|
||||||
|
},
|
||||||
|
timeout: number,
|
||||||
|
checkPartLength: boolean,
|
||||||
|
isResume: boolean,
|
||||||
|
bytesDownloaded: number,
|
||||||
|
waitTime: number,
|
||||||
|
callback?: HLSCallback,
|
||||||
|
override?: string,
|
||||||
|
dateStart: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// hls class
|
||||||
|
class hlsDownload {
|
||||||
|
private data: Data;
|
||||||
|
constructor(options: HLSOptions){
|
||||||
|
// check playlist
|
||||||
|
if(
|
||||||
|
!options
|
||||||
|
|| !options.m3u8json
|
||||||
|
|| !options.m3u8json.segments
|
||||||
|
|| options.m3u8json.segments.length === 0
|
||||||
|
){
|
||||||
|
throw new Error('Playlist is empty!');
|
||||||
|
}
|
||||||
|
// init options
|
||||||
|
this.data = {
|
||||||
|
parts: {
|
||||||
|
first: options.m3u8json.mediaSequence || 0,
|
||||||
|
total: options.m3u8json.segments.length,
|
||||||
|
completed: 0,
|
||||||
|
},
|
||||||
|
m3u8json: options.m3u8json,
|
||||||
|
outputFile: options.output || 'stream.ts',
|
||||||
|
threads: options.threads || 5,
|
||||||
|
retries: options.retries || 4,
|
||||||
|
offset: options.offset || 0,
|
||||||
|
baseurl: options.baseurl,
|
||||||
|
skipInit: options.skipInit,
|
||||||
|
keys: {},
|
||||||
|
timeout: options.timeout ? options.timeout : 60 * 1000,
|
||||||
|
checkPartLength: true,
|
||||||
|
isResume: options.offset ? options.offset > 0 : false,
|
||||||
|
bytesDownloaded: 0,
|
||||||
|
waitTime: options.fsRetryTime ?? 1000 * 5,
|
||||||
|
callback: options.callback,
|
||||||
|
override: options.override,
|
||||||
|
dateStart: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async download(){
|
||||||
|
// set output
|
||||||
|
const fn = this.data.outputFile;
|
||||||
|
// try load resume file
|
||||||
|
if(fs.existsSync(fn) && fs.existsSync(`${fn}.resume`) && this.data.offset < 1){
|
||||||
|
try{
|
||||||
|
console.info('Resume data found! Trying to resume...');
|
||||||
|
const resumeData = JSON.parse(fs.readFileSync(`${fn}.resume`, 'utf-8'));
|
||||||
|
if(
|
||||||
|
resumeData.total == this.data.m3u8json.segments.length
|
||||||
|
&& resumeData.completed != resumeData.total
|
||||||
|
&& !isNaN(resumeData.completed)
|
||||||
|
){
|
||||||
|
console.info('Resume data is ok!');
|
||||||
|
this.data.offset = resumeData.completed;
|
||||||
|
this.data.isResume = true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
console.warn(' Resume data is wrong!');
|
||||||
|
console.warn({
|
||||||
|
resume: { total: resumeData.total, dled: resumeData.completed },
|
||||||
|
current: { total: this.data.m3u8json.segments.length },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(e){
|
||||||
|
console.error('Resume failed, downloading will be not resumed!');
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ask before rewrite file
|
||||||
|
if (fs.existsSync(`${fn}`) && !this.data.isResume) {
|
||||||
|
let rwts = this.data.override ?? await shlp.question(`[Q] File «${fn}» already exists! Rewrite? ([y]es/[N]o/[c]ontinue)`);
|
||||||
|
rwts = rwts || 'N';
|
||||||
|
if (['Y', 'y'].includes(rwts[0])) {
|
||||||
|
console.info(`Deleting «${fn}»...`);
|
||||||
|
fs.unlinkSync(fn);
|
||||||
|
}
|
||||||
|
else if (['C', 'c'].includes(rwts[0])) {
|
||||||
|
return { ok: true, parts: this.data.parts };
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return { ok: false, parts: this.data.parts };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// show output filename
|
||||||
|
if (fs.existsSync(fn) && this.data.isResume) {
|
||||||
|
console.info(`Adding content to «${fn}»...`);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
console.info(`Saving stream to «${fn}»...`);
|
||||||
|
}
|
||||||
|
// start time
|
||||||
|
this.data.dateStart = Date.now();
|
||||||
|
let segments = this.data.m3u8json.segments;
|
||||||
|
// download init part
|
||||||
|
if (segments[0].map && this.data.offset === 0 && !this.data.skipInit) {
|
||||||
|
console.info('Download and save init part...');
|
||||||
|
const initSeg = segments[0].map as Segment;
|
||||||
|
if(segments[0].key){
|
||||||
|
initSeg.key = segments[0].key as Key;
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
const initDl = await this.downloadPart(initSeg, 0, 0);
|
||||||
|
fs.writeFileSync(fn, initDl.dec, { flag: 'a' });
|
||||||
|
fs.writeFileSync(`${fn}.resume`, JSON.stringify({
|
||||||
|
completed: 0,
|
||||||
|
total: this.data.m3u8json.segments.length
|
||||||
|
}));
|
||||||
|
console.info('Init part downloaded.');
|
||||||
|
}
|
||||||
|
catch(e: any){
|
||||||
|
console.error(`Part init download error:\n\t${e.message}`);
|
||||||
|
return { ok: false, parts: this.data.parts };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(segments[0].map && this.data.offset === 0 && this.data.skipInit){
|
||||||
|
console.warn('Skipping init part can lead to broken video!');
|
||||||
|
}
|
||||||
|
// resuming ...
|
||||||
|
if(this.data.offset > 0){
|
||||||
|
segments = segments.slice(this.data.offset);
|
||||||
|
console.info(`Resuming download from part ${this.data.offset+1}...`);
|
||||||
|
this.data.parts.completed = this.data.offset;
|
||||||
|
}
|
||||||
|
// dl process
|
||||||
|
for (let p = 0; p < segments.length / this.data.threads; p++) {
|
||||||
|
// set offsets
|
||||||
|
const offset = p * this.data.threads;
|
||||||
|
const dlOffset = offset + this.data.threads;
|
||||||
|
// map download threads
|
||||||
|
const krq = new Map(), prq = new Map();
|
||||||
|
const res = [], errcnt = 0;
|
||||||
|
for (let px = offset; px < dlOffset && px < segments.length; px++){
|
||||||
|
const curp = segments[px];
|
||||||
|
const key = curp.key as Key;
|
||||||
|
if(key && !krq.has(key.uri) && !this.data.keys[key.uri as string]){
|
||||||
|
krq.set(key.uri, this.downloadKey(key, px, this.data.offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await Promise.all(krq.values());
|
||||||
|
} catch (er: any) {
|
||||||
|
console.error(`Key ${er.p + 1} download error:\n\t${er.message}`);
|
||||||
|
return { ok: false, parts: this.data.parts };
|
||||||
|
}
|
||||||
|
for (let px = offset; px < dlOffset && px < segments.length; px++){
|
||||||
|
const curp = segments[px] as Segment;
|
||||||
|
prq.set(px, this.downloadPart(curp, px, this.data.offset));
|
||||||
|
}
|
||||||
|
for (let i = prq.size; i--;) {
|
||||||
|
try {
|
||||||
|
const r = await Promise.race(prq.values());
|
||||||
|
prq.delete(r.p);
|
||||||
|
res[r.p - offset] = r.dec;
|
||||||
|
}
|
||||||
|
catch (error: any) {
|
||||||
|
console.error('Part %s download error:\n\t%s',
|
||||||
|
error.p + 1 + this.data.offset, error.message);
|
||||||
|
prq.delete(error.p);
|
||||||
|
errcnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// catch error
|
||||||
|
if (errcnt > 0) {
|
||||||
|
console.error(`${errcnt} parts not downloaded`);
|
||||||
|
return { ok: false, parts: this.data.parts };
|
||||||
|
}
|
||||||
|
// write downloaded
|
||||||
|
for (const r of res) {
|
||||||
|
let error = 0;
|
||||||
|
while (error < 3) {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(fn, r, { flag: 'a' });
|
||||||
|
break;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
console.error(`Unable to write to file '${fn}' (Attempt ${error+1}/3)`);
|
||||||
|
console.info(`Waiting ${Math.round(this.data.waitTime / 1000)}s before retrying`);
|
||||||
|
await new Promise<void>((resolve) => setTimeout(() => resolve(), this.data.waitTime));
|
||||||
|
}
|
||||||
|
error++;
|
||||||
|
}
|
||||||
|
if (error === 3) {
|
||||||
|
console.error(`Unable to write content to '${fn}'.`);
|
||||||
|
return { ok: false, parts: this.data.parts };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// log downloaded
|
||||||
|
const totalSeg = segments.length + this.data.offset; // Add the sliced lenght back so the resume data will be correct even if an resumed download fails
|
||||||
|
const downloadedSeg = dlOffset < totalSeg ? dlOffset : totalSeg;
|
||||||
|
this.data.parts.completed = downloadedSeg + this.data.offset;
|
||||||
|
const data = extFn.getDownloadInfo(
|
||||||
|
this.data.dateStart, downloadedSeg, totalSeg,
|
||||||
|
this.data.bytesDownloaded
|
||||||
|
);
|
||||||
|
fs.writeFileSync(`${fn}.resume`, JSON.stringify({
|
||||||
|
completed: this.data.parts.completed,
|
||||||
|
total: totalSeg
|
||||||
|
}));
|
||||||
|
console.info(`${downloadedSeg} of ${totalSeg} parts downloaded [${data.percent}%] (${shlp.formatTime(parseInt((data.time / 1000).toFixed(0)))} | ${(data.downloadSpeed / 1000000).toPrecision(2)}Mb/s)`);
|
||||||
|
if (this.data.callback)
|
||||||
|
this.data.callback({ total: this.data.parts.total, cur: this.data.parts.completed, bytes: this.data.bytesDownloaded, percent: data.percent, time: data.time, downloadSpeed: data.downloadSpeed });
|
||||||
|
}
|
||||||
|
// return result
|
||||||
|
fs.unlinkSync(`${fn}.resume`);
|
||||||
|
return { ok: true, parts: this.data.parts };
|
||||||
|
}
|
||||||
|
async downloadPart(seg: Segment, segIndex: number, segOffset: number){
|
||||||
|
const sURI = extFn.getURI(seg.uri, this.data.baseurl);
|
||||||
|
let decipher, part, dec;
|
||||||
|
const p = segIndex;
|
||||||
|
try {
|
||||||
|
if (seg.key != undefined) {
|
||||||
|
decipher = await this.getKey(seg.key, p, segOffset);
|
||||||
|
}
|
||||||
|
part = await extFn.getData(p, sURI, {
|
||||||
|
...(seg.byterange ? {
|
||||||
|
Range: `bytes=${seg.byterange.offset}-${seg.byterange.offset+seg.byterange.length-1}`
|
||||||
|
} : {})
|
||||||
|
}, segOffset, false, this.data.timeout, this.data.retries, [
|
||||||
|
(res, retryWithMergedOptions) => {
|
||||||
|
if(this.data.checkPartLength && res.headers['content-length']){
|
||||||
|
if(!res.body || (res.body as any).length != res.headers['content-length']){
|
||||||
|
// 'Part not fully downloaded'
|
||||||
|
return retryWithMergedOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
if(this.data.checkPartLength && !(part as any).headers['content-length']){
|
||||||
|
this.data.checkPartLength = false;
|
||||||
|
console.warn(`Part ${segIndex+segOffset+1}: can't check parts size!`);
|
||||||
|
}
|
||||||
|
if (decipher == undefined) {
|
||||||
|
this.data.bytesDownloaded += (part.body as Buffer).byteLength;
|
||||||
|
return { dec: part.body, p };
|
||||||
|
}
|
||||||
|
dec = decipher.update(part.body as Buffer);
|
||||||
|
dec = Buffer.concat([dec, decipher.final()]);
|
||||||
|
this.data.bytesDownloaded += dec.byteLength;
|
||||||
|
}
|
||||||
|
catch (error: any) {
|
||||||
|
error.p = p;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return { dec, p };
|
||||||
|
}
|
||||||
|
async downloadKey(key: Key, segIndex: number, segOffset: number){
|
||||||
|
const kURI = extFn.getURI(key.uri, this.data.baseurl);
|
||||||
|
if (!this.data.keys[kURI]) {
|
||||||
|
try {
|
||||||
|
const rkey = await extFn.getData(segIndex, kURI, {}, segOffset, true, this.data.timeout, this.data.retries, [
|
||||||
|
(res, retryWithMergedOptions) => {
|
||||||
|
if (!res || !res.body) {
|
||||||
|
// 'Key get error'
|
||||||
|
return retryWithMergedOptions();
|
||||||
|
}
|
||||||
|
if((res.body as any).length != 16){
|
||||||
|
// 'Key not fully downloaded'
|
||||||
|
return retryWithMergedOptions();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
return rkey;
|
||||||
|
}
|
||||||
|
catch (error: any) {
|
||||||
|
error.p = segIndex;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getKey(key: Key, segIndex: number, segOffset: number){
|
||||||
|
const kURI = extFn.getURI(key.uri, this.data.baseurl);
|
||||||
|
const p = segIndex;
|
||||||
|
if (!this.data.keys[kURI]) {
|
||||||
|
try{
|
||||||
|
const rkey = await this.downloadKey(key, segIndex, segOffset);
|
||||||
|
if (!rkey)
|
||||||
|
throw new Error();
|
||||||
|
this.data.keys[kURI] = rkey.body;
|
||||||
|
}
|
||||||
|
catch (error: any) {
|
||||||
|
error.p = p;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// get ivs
|
||||||
|
const iv = Buffer.alloc(16);
|
||||||
|
const ivs = key.iv ? key.iv : [0, 0, 0, p + 1];
|
||||||
|
for (let i = 0; i < ivs.length; i++) {
|
||||||
|
iv.writeUInt32BE(ivs[i], i * 4);
|
||||||
|
}
|
||||||
|
return crypto.createDecipheriv('aes-128-cbc', this.data.keys[kURI], iv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const extFn = {
|
||||||
|
getURI: (uri: string, baseurl?: string) => {
|
||||||
|
const httpURI = /^https{0,1}:/.test(uri);
|
||||||
|
if (!baseurl && !httpURI) {
|
||||||
|
throw new Error('No base and not http(s) uri');
|
||||||
|
}
|
||||||
|
else if (httpURI) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
return baseurl + uri;
|
||||||
|
},
|
||||||
|
getDownloadInfo: (dateStart: number, partsDL: number, partsTotal: number, downloadedBytes: number) => {
|
||||||
|
const dateElapsed = Date.now() - dateStart;
|
||||||
|
const percentFxd = parseInt((partsDL / partsTotal * 100).toFixed());
|
||||||
|
const percent = percentFxd < 100 ? percentFxd : (partsTotal == partsDL ? 100 : 99);
|
||||||
|
const revParts = dateElapsed * (partsTotal / partsDL - 1);
|
||||||
|
const downloadSpeed = downloadedBytes / (dateElapsed / 1000); //Bytes per second
|
||||||
|
return { percent, time: revParts, downloadSpeed };
|
||||||
|
},
|
||||||
|
getData: (partIndex: number, uri: string, headers: Record<string, string>, segOffset: number, isKey: boolean, timeout: number, retry: number, afterResponse: ((res: Response, retryWithMergedOptions: () => Response) => Response)[]) => {
|
||||||
|
// get file if uri is local
|
||||||
|
if (uri.startsWith('file://')) {
|
||||||
|
return {
|
||||||
|
body: fs.readFileSync(url.fileURLToPath(uri)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// base options
|
||||||
|
headers = headers && typeof headers == 'object' ? headers : {};
|
||||||
|
const options = { headers, retry, responseType: 'buffer', hooks: {
|
||||||
|
beforeRequest: [
|
||||||
|
(options: Record<string, Record<string, unknown>>) => {
|
||||||
|
if(!options.headers['user-agent']){
|
||||||
|
options.headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0';
|
||||||
|
}
|
||||||
|
// console.log(' - Req:', options.url.pathname);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
afterResponse: [(fixMiddleWare as (r: Response, s: () => Response) => Response)].concat(afterResponse || []),
|
||||||
|
beforeRetry: [
|
||||||
|
(_: any, error: Error, retryCount: number) => {
|
||||||
|
if(error){
|
||||||
|
const partType = isKey ? 'Key': 'Part';
|
||||||
|
const partIndx = partIndex + 1 + segOffset;
|
||||||
|
console.warn('%s %s: %d attempt to retrieve data', partType, partIndx, retryCount + 1);
|
||||||
|
console.error(`\t${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}} as Record<string, unknown>;
|
||||||
|
// proxy
|
||||||
|
options.timeout = timeout;
|
||||||
|
// do request
|
||||||
|
return got(uri, options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default hlsDownload;
|
||||||
66
modules/log.ts
Normal file
66
modules/log.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { workingDir } from './module.cfg-loader';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
|
||||||
|
const logFolder = path.join(workingDir, 'logs');
|
||||||
|
const latest = path.join(logFolder, 'latest.log');
|
||||||
|
|
||||||
|
const makeLogFolder = () => {
|
||||||
|
if (!fs.existsSync(logFolder))
|
||||||
|
fs.mkdirSync(logFolder);
|
||||||
|
if (fs.existsSync(latest)) {
|
||||||
|
const stats = fs.statSync(latest);
|
||||||
|
fs.renameSync(latest, path.join(logFolder, `${stats.mtimeMs}.log`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeLogger = () => {
|
||||||
|
const oldLog = global.console.log;
|
||||||
|
global.console.log = (data) => {
|
||||||
|
oldLog(`Unexpected use of console.log. Use the log4js logger instead. ${data}`);
|
||||||
|
};
|
||||||
|
makeLogFolder();
|
||||||
|
log4js.configure({
|
||||||
|
appenders: {
|
||||||
|
console: {
|
||||||
|
type: 'console', layout: {
|
||||||
|
type: 'pattern',
|
||||||
|
pattern: process.env.isGUI === 'true' ? '%[%x{info}%m%]' : '%x{info}%m',
|
||||||
|
tokens: {
|
||||||
|
info: (ev) => {
|
||||||
|
return ev.level.levelStr === 'INFO' ? '' : `[${ev.level.levelStr}] `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
type: 'file',
|
||||||
|
filename: latest,
|
||||||
|
layout: {
|
||||||
|
type: 'pattern',
|
||||||
|
pattern: '%x{info}%m',
|
||||||
|
tokens: {
|
||||||
|
info: (ev) => {
|
||||||
|
return ev.level.levelStr === 'INFO' ? '' : `[${ev.level.levelStr}] `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
default: {
|
||||||
|
appenders: ['console', 'file'],
|
||||||
|
level: 'all',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLogger = () => {
|
||||||
|
if (!log4js.isConfigured())
|
||||||
|
makeLogger();
|
||||||
|
return log4js.getLogger();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const console = getLogger();
|
||||||
|
|
@ -2,6 +2,7 @@ import path from 'path';
|
||||||
import yaml from 'yaml';
|
import yaml from 'yaml';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { lookpath } from 'lookpath';
|
import { lookpath } from 'lookpath';
|
||||||
|
import { console } from './log';
|
||||||
|
|
||||||
// new-cfg
|
// new-cfg
|
||||||
const workingDir = (process as NodeJS.Process & {
|
const workingDir = (process as NodeJS.Process & {
|
||||||
|
|
@ -41,7 +42,7 @@ const loadYamlCfgFile = <T extends Record<string, any>>(file: string, isSess?: b
|
||||||
return yaml.parse(fs.readFileSync(file, 'utf8'));
|
return yaml.parse(fs.readFileSync(file, 'utf8'));
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
console.log('[ERROR]', e);
|
console.error('[ERROR]', e);
|
||||||
return {} as T;
|
return {} as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +121,7 @@ const loadCfg = () : ConfigObject => {
|
||||||
fs.ensureDirSync(defaultCfg.dir.content);
|
fs.ensureDirSync(defaultCfg.dir.content);
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
console.log('[ERROR] Content directory not accessible!');
|
console.error('Content directory not accessible!');
|
||||||
return defaultCfg;
|
return defaultCfg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +181,7 @@ const saveCRSession = (data: Record<string, unknown>) => {
|
||||||
fs.writeFileSync(`${sessCfgFile}.yml`, yaml.stringify(data));
|
fs.writeFileSync(`${sessCfgFile}.yml`, yaml.stringify(data));
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
console.log('[ERROR] Can\'t save session file to disk!');
|
console.error('Can\'t save session file to disk!');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -199,7 +200,7 @@ const saveCRToken = (data: Record<string, unknown>) => {
|
||||||
fs.writeFileSync(`${tokenFile.cr}.yml`, yaml.stringify(data));
|
fs.writeFileSync(`${tokenFile.cr}.yml`, yaml.stringify(data));
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
console.log('[ERROR] Can\'t save token file to disk!');
|
console.error('Can\'t save token file to disk!');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -212,7 +213,7 @@ const loadFuniToken = () => {
|
||||||
token = loadedToken.token;
|
token = loadedToken.token;
|
||||||
// info if token not set
|
// info if token not set
|
||||||
if(!token){
|
if(!token){
|
||||||
console.log('[INFO] Token not set!\n');
|
console.info('[INFO] Token not set!\n');
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
};
|
};
|
||||||
|
|
@ -226,7 +227,7 @@ const saveFuniToken = (data: {
|
||||||
fs.writeFileSync(`${tokenFile.funi}.yml`, yaml.stringify(data));
|
fs.writeFileSync(`${tokenFile.funi}.yml`, yaml.stringify(data));
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
console.log('[ERROR] Can\'t save token file to disk!');
|
console.error('Can\'t save token file to disk!');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import child_process from 'child_process';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { Headers } from 'got';
|
import { Headers } from 'got';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { console } from './log';
|
||||||
|
|
||||||
export type CurlOptions = {
|
export type CurlOptions = {
|
||||||
headers?: Headers,
|
headers?: Headers,
|
||||||
|
|
@ -73,7 +74,7 @@ const curlReq = async (curlBin: string, url: string, options: CurlOptions, cache
|
||||||
|
|
||||||
try{
|
try{
|
||||||
if(options.curlDebug){
|
if(options.curlDebug){
|
||||||
console.log(curlComm, '\n');
|
console.info(curlComm, '\n');
|
||||||
}
|
}
|
||||||
child_process.execSync(curlComm, { stdio: 'inherit', windowsHide: true });
|
child_process.execSync(curlComm, { stdio: 'inherit', windowsHide: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import * as shlp from 'sei-helper';
|
import * as shlp from 'sei-helper';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { AvailableFilenameVars } from './module.args';
|
import { AvailableFilenameVars } from './module.args';
|
||||||
|
import { console } from './log';
|
||||||
|
|
||||||
export type Variable<T extends string = AvailableFilenameVars> = ({
|
export type Variable<T extends string = AvailableFilenameVars> = ({
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
|
@ -25,7 +25,7 @@ const parseFileName = (input: string, variables: Variable[], numbers: number, ov
|
||||||
const varName = type.slice(2, -1);
|
const varName = type.slice(2, -1);
|
||||||
const use = overridenVars.find(a => a.name === varName);
|
const use = overridenVars.find(a => a.name === varName);
|
||||||
if (use === undefined) {
|
if (use === undefined) {
|
||||||
console.log(`[ERROR] Found variable '${type}' in fileName but no values was internally found!`);
|
console.info(`[ERROR] Found variable '${type}' in fileName but no values was internally found!`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import got, { OptionsOfUnknownResponseBody, ReadError, Response, ResponseType } from 'got';
|
import got, { OptionsOfUnknownResponseBody, ReadError, Response, ResponseType } from 'got';
|
||||||
|
import { console } from './log';
|
||||||
|
|
||||||
// Used for future updates
|
// Used for future updates
|
||||||
// const argv = require('../funi').argv;
|
// const argv = require('../funi').argv;
|
||||||
|
|
@ -83,8 +84,8 @@ const getData = async <T = string>(options: Options) => {
|
||||||
beforeRequest: [
|
beforeRequest: [
|
||||||
(gotOpts) => {
|
(gotOpts) => {
|
||||||
if(options.debug){
|
if(options.debug){
|
||||||
console.log('[DEBUG] GOT OPTIONS:');
|
console.debug('GOT OPTIONS:');
|
||||||
console.log(gotOpts);
|
console.debug(gotOpts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -109,17 +110,17 @@ const getData = async <T = string>(options: Options) => {
|
||||||
res: Response<unknown>
|
res: Response<unknown>
|
||||||
};
|
};
|
||||||
if(options.debug){
|
if(options.debug){
|
||||||
console.log(error);
|
console.debug(error);
|
||||||
}
|
}
|
||||||
if(error.response && error.response.statusCode && error.response.statusMessage){
|
if(error.response && error.response.statusCode && error.response.statusMessage){
|
||||||
console.log(`[ERROR] ${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
console.error(`${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
||||||
}
|
}
|
||||||
else if(error.name && error.name == 'HTMLError' && error.res && error.res.body){
|
else if(error.name && error.name == 'HTMLError' && error.res && error.res.body){
|
||||||
console.log(`[ERROR] ${error.name}:`);
|
console.error(`${error.name}:`);
|
||||||
console.log(error.res.body);
|
console.error(error.res.body);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log(`[ERROR] ${error.name}: ${error.code||error.message}`);
|
console.error(`${error.name}: ${error.code||error.message}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import fs from 'fs';
|
||||||
import { LanguageItem } from './module.langsData';
|
import { LanguageItem } from './module.langsData';
|
||||||
import { AvailableMuxer } from './module.args';
|
import { AvailableMuxer } from './module.args';
|
||||||
import { exec } from './sei-helper-fixes';
|
import { exec } from './sei-helper-fixes';
|
||||||
|
import { console } from './log';
|
||||||
|
|
||||||
export type MergerInput = {
|
export type MergerInput = {
|
||||||
path: string,
|
path: string,
|
||||||
|
|
@ -224,7 +225,6 @@ class Merger {
|
||||||
}
|
}
|
||||||
if (this.options.fonts && this.options.fonts.length > 0) {
|
if (this.options.fonts && this.options.fonts.length > 0) {
|
||||||
for (const f of this.options.fonts) {
|
for (const f of this.options.fonts) {
|
||||||
console.log(f.path);
|
|
||||||
args.push('--attachment-name', f.name);
|
args.push('--attachment-name', f.name);
|
||||||
args.push('--attachment-mime-type', f.mime);
|
args.push('--attachment-mime-type', f.mime);
|
||||||
args.push('--attach-file', `"${f.path}"`);
|
args.push('--attach-file', `"${f.path}"`);
|
||||||
|
|
@ -261,9 +261,9 @@ class Merger {
|
||||||
FFmpeg: bin.ffmpeg
|
FFmpeg: bin.ffmpeg
|
||||||
};
|
};
|
||||||
} else if (useMP4format) {
|
} else if (useMP4format) {
|
||||||
console.log('[WARN] FFmpeg not found, skip muxing...');
|
console.warn('FFmpeg not found, skip muxing...');
|
||||||
} else if (!bin.mkvmerge) {
|
} else if (!bin.mkvmerge) {
|
||||||
console.log('[WARN] MKVMerge not found, skip muxing...');
|
console.warn('MKVMerge not found, skip muxing...');
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -279,11 +279,11 @@ class Merger {
|
||||||
}
|
}
|
||||||
fontsNameList = [...new Set(fontsNameList)];
|
fontsNameList = [...new Set(fontsNameList)];
|
||||||
if(subsList.length > 0){
|
if(subsList.length > 0){
|
||||||
console.log('\n[INFO] Subtitles: %s (Total: %s)', subsList.join(', '), subsList.length);
|
console.info('\nSubtitles: %s (Total: %s)', subsList.join(', '), subsList.length);
|
||||||
isNstr = false;
|
isNstr = false;
|
||||||
}
|
}
|
||||||
if(fontsNameList.length > 0){
|
if(fontsNameList.length > 0){
|
||||||
console.log((isNstr ? '\n' : '') + '[INFO] Required fonts: %s (Total: %s)', fontsNameList.join(', '), fontsNameList.length);
|
console.info((isNstr ? '\n' : '') + 'Required fonts: %s (Total: %s)', fontsNameList.join(', '), fontsNameList.length);
|
||||||
}
|
}
|
||||||
for(const f of fontsNameList){
|
for(const f of fontsNameList){
|
||||||
const fontFiles = fontFamilies[f];
|
const fontFiles = fontFamilies[f];
|
||||||
|
|
@ -315,18 +315,18 @@ class Merger {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (command === undefined) {
|
if (command === undefined) {
|
||||||
console.log('[WARN] Unable to merge files.');
|
console.warn('Unable to merge files.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`[INFO][${type}] Started merging`);
|
console.info(`[${type}] Started merging`);
|
||||||
const res = exec(type, `"${bin}"`, command);
|
const res = exec(type, `"${bin}"`, command);
|
||||||
if (!res.isOk && type === 'mkvmerge' && res.err.code === 1) {
|
if (!res.isOk && type === 'mkvmerge' && res.err.code === 1) {
|
||||||
console.log(`[INFO][${type}] Mkvmerge finished with at least one warning`);
|
console.info(`[${type}] Mkvmerge finished with at least one warning`);
|
||||||
} else if (!res.isOk) {
|
} else if (!res.isOk) {
|
||||||
console.log(res.err);
|
console.error(res.err);
|
||||||
console.log(`[ERROR][${type}] Merging failed with exit code ${res.err.code}`);
|
console.error(`[${type}] Merging failed with exit code ${res.err.code}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[INFO][${type} Done]`);
|
console.info(`[${type} Done]`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { console } from './log';
|
||||||
|
|
||||||
const parseSelect = (selectString: string, but = false) : {
|
const parseSelect = (selectString: string, but = false) : {
|
||||||
isSelected: (val: string|string[]) => boolean,
|
isSelected: (val: string|string[]) => boolean,
|
||||||
values: string[]
|
values: string[]
|
||||||
|
|
@ -14,7 +16,7 @@ const parseSelect = (selectString: string, but = false) : {
|
||||||
if (part.includes('-')) {
|
if (part.includes('-')) {
|
||||||
const splits = part.split('-');
|
const splits = part.split('-');
|
||||||
if (splits.length !== 2) {
|
if (splits.length !== 2) {
|
||||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
console.warn(`[WARN] Unable to parse input "${part}"`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,14 +24,14 @@ const parseSelect = (selectString: string, but = false) : {
|
||||||
const match = firstPart.match(/[A-Za-z]+/);
|
const match = firstPart.match(/[A-Za-z]+/);
|
||||||
if (match && match.length > 0) {
|
if (match && match.length > 0) {
|
||||||
if (match.index && match.index !== 0) {
|
if (match.index && match.index !== 0) {
|
||||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
console.warn(`[WARN] Unable to parse input "${part}"`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const letters = firstPart.substring(0, match[0].length);
|
const letters = firstPart.substring(0, match[0].length);
|
||||||
const number = parseInt(firstPart.substring(match[0].length));
|
const number = parseInt(firstPart.substring(match[0].length));
|
||||||
const b = parseInt(splits[1]);
|
const b = parseInt(splits[1]);
|
||||||
if (isNaN(number) || isNaN(b)) {
|
if (isNaN(number) || isNaN(b)) {
|
||||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
console.warn(`[WARN] Unable to parse input "${part}"`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = number; i <= b; i++) {
|
for (let i = number; i <= b; i++) {
|
||||||
|
|
@ -40,7 +42,7 @@ const parseSelect = (selectString: string, but = false) : {
|
||||||
const a = parseInt(firstPart);
|
const a = parseInt(firstPart);
|
||||||
const b = parseInt(splits[1]);
|
const b = parseInt(splits[1]);
|
||||||
if (isNaN(a) || isNaN(b)) {
|
if (isNaN(a) || isNaN(b)) {
|
||||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
console.warn(`[WARN] Unable to parse input "${part}"`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = a; i <= b; i++) {
|
for (let i = a; i <= b; i++) {
|
||||||
|
|
@ -56,13 +58,13 @@ const parseSelect = (selectString: string, but = false) : {
|
||||||
const match = part.match(/[A-Za-z]+/);
|
const match = part.match(/[A-Za-z]+/);
|
||||||
if (match && match.length > 0) {
|
if (match && match.length > 0) {
|
||||||
if (match.index && match.index !== 0) {
|
if (match.index && match.index !== 0) {
|
||||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
console.warn(`[WARN] Unable to parse input "${part}"`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const letters = part.substring(0, match[0].length);
|
const letters = part.substring(0, match[0].length);
|
||||||
const number = parseInt(part.substring(match[0].length));
|
const number = parseInt(part.substring(match[0].length));
|
||||||
if (isNaN(number)) {
|
if (isNaN(number)) {
|
||||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
console.warn(`[WARN] Unable to parse input "${part}"`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
select.push(`${letters}${number}`);
|
select.push(`${letters}${number}`);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import shlp from 'sei-helper';
|
||||||
import got, { Headers, Method, Options, ReadError, Response } from 'got';
|
import got, { Headers, Method, Options, ReadError, Response } from 'got';
|
||||||
import cookieFile from './module.cookieFile';
|
import cookieFile from './module.cookieFile';
|
||||||
import * as yamlCfg from './module.cfg-loader';
|
import * as yamlCfg from './module.cfg-loader';
|
||||||
|
import { console } from './log';
|
||||||
//import curlReq from './module.curl-req';
|
//import curlReq from './module.curl-req';
|
||||||
|
|
||||||
export type Params = {
|
export type Params = {
|
||||||
|
|
@ -75,8 +76,8 @@ class Req {
|
||||||
beforeRequest: [
|
beforeRequest: [
|
||||||
(options) => {
|
(options) => {
|
||||||
if(this.debug){
|
if(this.debug){
|
||||||
console.log('[DEBUG] GOT OPTIONS:');
|
console.debug('[DEBUG] GOT OPTIONS:');
|
||||||
console.log(options);
|
console.debug(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -99,21 +100,21 @@ class Req {
|
||||||
res: Response<unknown>
|
res: Response<unknown>
|
||||||
};
|
};
|
||||||
if(error.response && error.response.statusCode && error.response.statusMessage){
|
if(error.response && error.response.statusCode && error.response.statusMessage){
|
||||||
console.log(`[ERROR] ${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
console.error(`${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log(`[ERROR] ${error.name}: ${error.code || error.message}`);
|
console.error(`${error.name}: ${error.code || error.message}`);
|
||||||
}
|
}
|
||||||
if(error.response && !error.res){
|
if(error.response && !error.res){
|
||||||
error.res = error.response;
|
error.res = error.response;
|
||||||
const docTitle = (error.res.body as string).match(/<title>(.*)<\/title>/);
|
const docTitle = (error.res.body as string).match(/<title>(.*)<\/title>/);
|
||||||
if(error.res.body && docTitle){
|
if(error.res.body && docTitle){
|
||||||
console.log('[ERROR]', docTitle[1]);
|
console.error(docTitle[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(error.res && error.res.body && error.response.statusCode
|
if(error.res && error.res.body && error.response.statusCode
|
||||||
&& error.response.statusCode != 404 && error.response.statusCode != 403){
|
&& error.response.statusCode != 404 && error.response.statusCode != 403){
|
||||||
console.log('[ERROR] Body:', error.res.body);
|
console.error('Body:', error.res.body);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|
@ -165,10 +166,10 @@ class Req {
|
||||||
}
|
}
|
||||||
if(cookieUpdated.length > 0){
|
if(cookieUpdated.length > 0){
|
||||||
if(this.debug){
|
if(this.debug){
|
||||||
console.log('[SAVING FILE]',`${this.sessCfg}.yml`);
|
console.info('[SAVING FILE]',`${this.sessCfg}.yml`);
|
||||||
}
|
}
|
||||||
yamlCfg.saveCRSession(this.session);
|
yamlCfg.saveCRSession(this.session);
|
||||||
console.log(`[INFO] Cookies were updated! (${cookieUpdated.join(', ')})\n`);
|
console.info(`Cookies were updated! (${cookieUpdated.join(', ')})\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkCookieVal(chcookie: Record<string, string>){
|
checkCookieVal(chcookie: Record<string, string>){
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import tsConfig from '../tsconfig.json';
|
||||||
import fsextra from 'fs-extra';
|
import fsextra from 'fs-extra';
|
||||||
import seiHelper from 'sei-helper';
|
import seiHelper from 'sei-helper';
|
||||||
import { workingDir } from './module.cfg-loader';
|
import { workingDir } from './module.cfg-loader';
|
||||||
|
import { console } from './log';
|
||||||
const updateFilePlace = path.join(workingDir, 'config', 'updates.json');
|
const updateFilePlace = path.join(workingDir, 'config', 'updates.json');
|
||||||
|
|
||||||
const updateIgnore = [
|
const updateIgnore = [
|
||||||
|
|
@ -53,7 +54,7 @@ export default (async (force = false) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('Checking for updates...');
|
console.info('Checking for updates...');
|
||||||
const tagRequest = await got('https://api.github.com/repos/anidl/multi-downloader-nx/tags');
|
const tagRequest = await got('https://api.github.com/repos/anidl/multi-downloader-nx/tags');
|
||||||
const tags = JSON.parse(tagRequest.body) as GithubTag[];
|
const tags = JSON.parse(tagRequest.body) as GithubTag[];
|
||||||
|
|
||||||
|
|
@ -61,10 +62,10 @@ export default (async (force = false) => {
|
||||||
const newer = tags.filter(a => {
|
const newer = tags.filter(a => {
|
||||||
return isNewer(packageJson.version, a.name);
|
return isNewer(packageJson.version, a.name);
|
||||||
});
|
});
|
||||||
console.log(`Found ${tags.length} release tags and ${newer.length} that are new.`);
|
console.info(`Found ${tags.length} release tags and ${newer.length} that are new.`);
|
||||||
|
|
||||||
if (newer.length < 1) {
|
if (newer.length < 1) {
|
||||||
console.log('[INFO] No new tags found');
|
console.info('No new tags found');
|
||||||
return done();
|
return done();
|
||||||
}
|
}
|
||||||
const newest = newer.sort((a, b) => a.name < b.name ? 1 : a.name > b.name ? -1 : 0)[0];
|
const newest = newer.sort((a, b) => a.name < b.name ? 1 : a.name > b.name ? -1 : 0)[0];
|
||||||
|
|
@ -72,7 +73,7 @@ export default (async (force = false) => {
|
||||||
|
|
||||||
const compareJSON = JSON.parse(compareRequest.body) as TagCompare;
|
const compareJSON = JSON.parse(compareRequest.body) as TagCompare;
|
||||||
|
|
||||||
console.log(`You are behind by ${compareJSON.ahead_by} releases!`);
|
console.info(`You are behind by ${compareJSON.ahead_by} releases!`);
|
||||||
const changedFiles = compareJSON.files.map(a => ({
|
const changedFiles = compareJSON.files.map(a => ({
|
||||||
...a,
|
...a,
|
||||||
filename: path.join(...a.filename.split('/'))
|
filename: path.join(...a.filename.split('/'))
|
||||||
|
|
@ -80,10 +81,10 @@ export default (async (force = false) => {
|
||||||
return !updateIgnore.some(_filter => matchString(_filter, a.filename));
|
return !updateIgnore.some(_filter => matchString(_filter, a.filename));
|
||||||
});
|
});
|
||||||
if (changedFiles.length < 1) {
|
if (changedFiles.length < 1) {
|
||||||
console.log('[INFO] No file changes found... updating package.json. If you think this is an error please get the newst version yourself.');
|
console.info('No file changes found... updating package.json. If you think this is an error please get the newst version yourself.');
|
||||||
return done(newest.name);
|
return done(newest.name);
|
||||||
}
|
}
|
||||||
console.log(`Found file changes: \n${changedFiles.map(a => ` [${
|
console.info(`Found file changes: \n${changedFiles.map(a => ` [${
|
||||||
a.status === 'modified' ? '*' : a.status === 'added' ? '+' : '-'
|
a.status === 'modified' ? '*' : a.status === 'added' ? '+' : '-'
|
||||||
}] ${a.filename}`).join('\n')}`);
|
}] ${a.filename}`).join('\n')}`);
|
||||||
|
|
||||||
|
|
@ -107,7 +108,7 @@ export default (async (force = false) => {
|
||||||
}).outputText,
|
}).outputText,
|
||||||
type: a.status === 'modified' ? ApplyType.UPDATE : a.status === 'added' ? ApplyType.ADD : ApplyType.DELETE
|
type: a.status === 'modified' ? ApplyType.UPDATE : a.status === 'added' ? ApplyType.ADD : ApplyType.DELETE
|
||||||
};
|
};
|
||||||
console.log('✓ Transpiled %s', ret.path);
|
console.info('✓ Transpiled %s', ret.path);
|
||||||
return ret;
|
return ret;
|
||||||
} else {
|
} else {
|
||||||
const ret = {
|
const ret = {
|
||||||
|
|
@ -115,7 +116,7 @@ export default (async (force = false) => {
|
||||||
content: (await got(a.raw_url)).body,
|
content: (await got(a.raw_url)).body,
|
||||||
type: a.status === 'modified' ? ApplyType.UPDATE : a.status === 'added' ? ApplyType.ADD : ApplyType.DELETE
|
type: a.status === 'modified' ? ApplyType.UPDATE : a.status === 'added' ? ApplyType.ADD : ApplyType.DELETE
|
||||||
};
|
};
|
||||||
console.log('✓ Got %s', ret.path);
|
console.info('✓ Got %s', ret.path);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
@ -124,13 +125,13 @@ export default (async (force = false) => {
|
||||||
try {
|
try {
|
||||||
fsextra.ensureDirSync(path.dirname(a.path));
|
fsextra.ensureDirSync(path.dirname(a.path));
|
||||||
fs.writeFileSync(path.join(__dirname, '..', a.path), a.content);
|
fs.writeFileSync(path.join(__dirname, '..', a.path), a.content);
|
||||||
console.log('✓ Written %s', a.path);
|
console.info('✓ Written %s', a.path);
|
||||||
} catch (er) {
|
} catch (er) {
|
||||||
console.log('✗ Error while writing %s', a.path);
|
console.info('✗ Error while writing %s', a.path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[INFO] Done');
|
console.info('Done');
|
||||||
return done();
|
return done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -147,7 +148,7 @@ function done(newVersion?: string) {
|
||||||
version: newVersion
|
version: newVersion
|
||||||
}, null, 4));
|
}, null, 4));
|
||||||
}
|
}
|
||||||
console.log('[INFO] Searching for update finished. Next time running on the ' + next.toLocaleDateString() + ' at ' + next.toLocaleTimeString() + '.');
|
console.info('[INFO] Searching for update finished. Next time running on the ' + next.toLocaleDateString() + ' at ' + next.toLocaleTimeString() + '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNewer(curr: string, compare: string) : boolean {
|
function isNewer(curr: string, compare: string) : boolean {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import childProcess from 'child_process';
|
import childProcess from 'child_process';
|
||||||
|
import { console } from './log';
|
||||||
|
|
||||||
const exec = (pname: string, fpath: string, pargs: string, spc = false): {
|
const exec = (pname: string, fpath: string, pargs: string, spc = false): {
|
||||||
isOk: true
|
isOk: true
|
||||||
|
|
@ -7,7 +8,7 @@ const exec = (pname: string, fpath: string, pargs: string, spc = false): {
|
||||||
err: Error & { code: number }
|
err: Error & { code: number }
|
||||||
} => {
|
} => {
|
||||||
pargs = pargs ? ' ' + pargs : '';
|
pargs = pargs ? ' ' + pargs : '';
|
||||||
console.log(`\n> "${pname}"${pargs}${spc ? '\n' : ''}`);
|
console.info(`\n> "${pname}"${pargs}${spc ? '\n' : ''}`);
|
||||||
try {
|
try {
|
||||||
childProcess.execSync((fpath + pargs), { stdio: 'inherit' });
|
childProcess.execSync((fpath + pargs), { stdio: 'inherit' });
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"fs-extra": "^11.1.0",
|
"fs-extra": "^11.1.0",
|
||||||
"got": "^11.8.6",
|
"got": "^11.8.6",
|
||||||
"hls-download": "^2.6.10",
|
|
||||||
"iso-639": "^0.2.2",
|
"iso-639": "^0.2.2",
|
||||||
|
"log4js": "^6.8.0",
|
||||||
"lookpath": "^1.2.2",
|
"lookpath": "^1.2.2",
|
||||||
"m3u8-parsed": "^1.3.0",
|
"m3u8-parsed": "^1.3.0",
|
||||||
"open": "^8.4.2",
|
"open": "^8.4.2",
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
"eslint": "^8.34.0",
|
"eslint": "^8.34.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-import": "^2.25.4",
|
"eslint-plugin-import": "^2.25.4",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "7.32.2",
|
||||||
"pkg": "^5.8.0",
|
"pkg": "^5.8.0",
|
||||||
"removeNPMAbsolutePaths": "^3.0.1",
|
"removeNPMAbsolutePaths": "^3.0.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
|
|
||||||
289
pnpm-lock.yaml
289
pnpm-lock.yaml
|
|
@ -20,13 +20,13 @@ specifiers:
|
||||||
eslint: ^8.34.0
|
eslint: ^8.34.0
|
||||||
eslint-config-react-app: ^7.0.1
|
eslint-config-react-app: ^7.0.1
|
||||||
eslint-plugin-import: ^2.27.5
|
eslint-plugin-import: ^2.27.5
|
||||||
eslint-plugin-react: ^7.32.2
|
eslint-plugin-react: 7.32.2
|
||||||
express: ^4.18.2
|
express: ^4.18.2
|
||||||
form-data: ^4.0.0
|
form-data: ^4.0.0
|
||||||
fs-extra: ^11.1.0
|
fs-extra: ^11.1.0
|
||||||
got: ^11.8.6
|
got: ^11.8.6
|
||||||
hls-download: ^2.6.10
|
|
||||||
iso-639: ^0.2.2
|
iso-639: ^0.2.2
|
||||||
|
log4js: ^6.8.0
|
||||||
lookpath: ^1.2.2
|
lookpath: ^1.2.2
|
||||||
m3u8-parsed: ^1.3.0
|
m3u8-parsed: ^1.3.0
|
||||||
open: ^8.4.2
|
open: ^8.4.2
|
||||||
|
|
@ -53,8 +53,8 @@ dependencies:
|
||||||
form-data: 4.0.0
|
form-data: 4.0.0
|
||||||
fs-extra: 11.1.0
|
fs-extra: 11.1.0
|
||||||
got: 11.8.6
|
got: 11.8.6
|
||||||
hls-download: 2.6.10
|
|
||||||
iso-639: 0.2.2
|
iso-639: 0.2.2
|
||||||
|
log4js: 6.8.0
|
||||||
lookpath: 1.2.2
|
lookpath: 1.2.2
|
||||||
m3u8-parsed: 1.3.0
|
m3u8-parsed: 1.3.0
|
||||||
open: 8.4.2
|
open: 8.4.2
|
||||||
|
|
@ -1539,11 +1539,6 @@ packages:
|
||||||
defer-to-connect: 2.0.1
|
defer-to-connect: 2.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tootallnate/once/1.1.2:
|
|
||||||
resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@tsconfig/node10/1.0.9:
|
/@tsconfig/node10/1.0.9:
|
||||||
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -1858,6 +1853,7 @@ packages:
|
||||||
/acorn-walk/8.2.0:
|
/acorn-walk/8.2.0:
|
||||||
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
|
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/acorn/8.8.2:
|
/acorn/8.8.2:
|
||||||
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
|
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
|
||||||
|
|
@ -1871,6 +1867,7 @@ packages:
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
/ajv/6.12.6:
|
/ajv/6.12.6:
|
||||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||||
|
|
@ -1975,13 +1972,6 @@ packages:
|
||||||
resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==}
|
resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/ast-types/0.13.4:
|
|
||||||
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.5.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/asynckit/0.4.0:
|
/asynckit/0.4.0:
|
||||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
@ -2328,6 +2318,7 @@ packages:
|
||||||
|
|
||||||
/core-util-is/1.0.3:
|
/core-util-is/1.0.3:
|
||||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/cors/2.8.5:
|
/cors/2.8.5:
|
||||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||||
|
|
@ -2379,9 +2370,9 @@ packages:
|
||||||
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/data-uri-to-buffer/3.0.1:
|
/date-format/4.0.14:
|
||||||
resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==}
|
resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>=4.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/debug/2.6.9:
|
/debug/2.6.9:
|
||||||
|
|
@ -2477,16 +2468,6 @@ packages:
|
||||||
has-property-descriptors: 1.0.0
|
has-property-descriptors: 1.0.0
|
||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
|
|
||||||
/degenerator/3.0.2:
|
|
||||||
resolution: {integrity: sha512-c0mef3SNQo56t6urUU6tdQAs+ThoD0o9B9MJ8HEt7NQcGEILCRFqQb7ZbP9JAv+QF1Ky5plydhMR/IrqWDm+TQ==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
dependencies:
|
|
||||||
ast-types: 0.13.4
|
|
||||||
escodegen: 1.14.3
|
|
||||||
esprima: 4.0.1
|
|
||||||
vm2: 3.9.14
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/delayed-stream/1.0.0:
|
/delayed-stream/1.0.0:
|
||||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
@ -2695,19 +2676,6 @@ packages:
|
||||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
/escodegen/1.14.3:
|
|
||||||
resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==}
|
|
||||||
engines: {node: '>=4.0'}
|
|
||||||
hasBin: true
|
|
||||||
dependencies:
|
|
||||||
esprima: 4.0.1
|
|
||||||
estraverse: 4.3.0
|
|
||||||
esutils: 2.0.3
|
|
||||||
optionator: 0.8.3
|
|
||||||
optionalDependencies:
|
|
||||||
source-map: 0.6.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/eslint-config-react-app/7.0.1_loofw3752f66lcbt6ckkhpg3my:
|
/eslint-config-react-app/7.0.1_loofw3752f66lcbt6ckkhpg3my:
|
||||||
resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==}
|
resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
@ -3006,12 +2974,6 @@ packages:
|
||||||
acorn-jsx: 5.3.2_acorn@8.8.2
|
acorn-jsx: 5.3.2_acorn@8.8.2
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
|
|
||||||
/esprima/4.0.1:
|
|
||||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
hasBin: true
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/esquery/1.4.2:
|
/esquery/1.4.2:
|
||||||
resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==}
|
resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==}
|
||||||
engines: {node: '>=0.10'}
|
engines: {node: '>=0.10'}
|
||||||
|
|
@ -3027,6 +2989,7 @@ packages:
|
||||||
/estraverse/4.3.0:
|
/estraverse/4.3.0:
|
||||||
resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
|
resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/estraverse/5.3.0:
|
/estraverse/5.3.0:
|
||||||
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
||||||
|
|
@ -3115,11 +3078,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
flat-cache: 3.0.4
|
flat-cache: 3.0.4
|
||||||
|
|
||||||
/file-uri-to-path/2.0.0:
|
|
||||||
resolution: {integrity: sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/fill-range/7.0.1:
|
/fill-range/7.0.1:
|
||||||
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
@ -3224,14 +3182,6 @@ packages:
|
||||||
/fs.realpath/1.0.0:
|
/fs.realpath/1.0.0:
|
||||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||||
|
|
||||||
/ftp/0.3.10:
|
|
||||||
resolution: {integrity: sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==}
|
|
||||||
engines: {node: '>=0.8.0'}
|
|
||||||
dependencies:
|
|
||||||
readable-stream: 1.1.14
|
|
||||||
xregexp: 2.0.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/function-bind/1.1.1:
|
/function-bind/1.1.1:
|
||||||
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
||||||
|
|
||||||
|
|
@ -3289,20 +3239,6 @@ packages:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
get-intrinsic: 1.2.0
|
get-intrinsic: 1.2.0
|
||||||
|
|
||||||
/get-uri/3.0.2:
|
|
||||||
resolution: {integrity: sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
dependencies:
|
|
||||||
'@tootallnate/once': 1.1.2
|
|
||||||
data-uri-to-buffer: 3.0.1
|
|
||||||
debug: 4.3.4
|
|
||||||
file-uri-to-path: 2.0.0
|
|
||||||
fs-extra: 8.1.0
|
|
||||||
ftp: 0.3.10
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/github-from-package/0.0.0:
|
/github-from-package/0.0.0:
|
||||||
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
|
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -3431,16 +3367,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.1
|
function-bind: 1.1.1
|
||||||
|
|
||||||
/hls-download/2.6.10:
|
|
||||||
resolution: {integrity: sha512-WDJptONvrk94P78Q6sQUSLQErRZIP0Ojs1bQvYDcEDx+m0Lw24Cgg8cih0nx3/0tmDfTG178S9rgK2GO09/9RQ==}
|
|
||||||
dependencies:
|
|
||||||
got: 11.8.6
|
|
||||||
proxy-agent: 5.0.0
|
|
||||||
sei-helper: 3.3.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/htmlparser2/8.0.1:
|
/htmlparser2/8.0.1:
|
||||||
resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
|
resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -3465,17 +3391,6 @@ packages:
|
||||||
toidentifier: 1.0.1
|
toidentifier: 1.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/http-proxy-agent/4.0.1:
|
|
||||||
resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
dependencies:
|
|
||||||
'@tootallnate/once': 1.1.2
|
|
||||||
agent-base: 6.0.2
|
|
||||||
debug: 4.3.4
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/http2-wrapper/1.0.3:
|
/http2-wrapper/1.0.3:
|
||||||
resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}
|
resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}
|
||||||
engines: {node: '>=10.19.0'}
|
engines: {node: '>=10.19.0'}
|
||||||
|
|
@ -3492,6 +3407,7 @@ packages:
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
/iconv-lite/0.4.24:
|
/iconv-lite/0.4.24:
|
||||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||||
|
|
@ -3549,14 +3465,6 @@ packages:
|
||||||
p-is-promise: 3.0.0
|
p-is-promise: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/ip/1.1.8:
|
|
||||||
resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/ip/2.0.0:
|
|
||||||
resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/ipaddr.js/1.9.1:
|
/ipaddr.js/1.9.1:
|
||||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
|
|
@ -3724,10 +3632,6 @@ packages:
|
||||||
is-docker: 2.2.1
|
is-docker: 2.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/isarray/0.0.1:
|
|
||||||
resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/isarray/1.0.0:
|
/isarray/1.0.0:
|
||||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -3827,14 +3731,6 @@ packages:
|
||||||
language-subtag-registry: 0.3.22
|
language-subtag-registry: 0.3.22
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/levn/0.3.0:
|
|
||||||
resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==}
|
|
||||||
engines: {node: '>= 0.8.0'}
|
|
||||||
dependencies:
|
|
||||||
prelude-ls: 1.1.2
|
|
||||||
type-check: 0.3.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/levn/0.4.1:
|
/levn/0.4.1:
|
||||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
@ -3863,6 +3759,19 @@ packages:
|
||||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/log4js/6.8.0:
|
||||||
|
resolution: {integrity: sha512-g+V8gZyurIexrOvWQ+AcZsIvuK/lBnx2argejZxL4gVZ4Hq02kUYH6WZOnqxgBml+zzQZYdaEoTN84B6Hzm8Fg==}
|
||||||
|
engines: {node: '>=8.0'}
|
||||||
|
dependencies:
|
||||||
|
date-format: 4.0.14
|
||||||
|
debug: 4.3.4
|
||||||
|
flatted: 3.2.7
|
||||||
|
rfdc: 1.3.0
|
||||||
|
streamroller: 3.1.5
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lookpath/1.2.2:
|
/lookpath/1.2.2:
|
||||||
resolution: {integrity: sha512-k2Gmn8iV6qdME3ztZC2spubmQISimFOPLuQKiPaLcVdRz0IpdxrNClVepMlyTJlhodm/zG/VfbkWERm3kUIh+Q==}
|
resolution: {integrity: sha512-k2Gmn8iV6qdME3ztZC2spubmQISimFOPLuQKiPaLcVdRz0IpdxrNClVepMlyTJlhodm/zG/VfbkWERm3kUIh+Q==}
|
||||||
engines: {npm: '>=6.13.4'}
|
engines: {npm: '>=6.13.4'}
|
||||||
|
|
@ -4019,11 +3928,6 @@ packages:
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/netmask/2.0.2:
|
|
||||||
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
|
|
||||||
engines: {node: '>= 0.4.0'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/node-abi/2.30.1:
|
/node-abi/2.30.1:
|
||||||
resolution: {integrity: sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==}
|
resolution: {integrity: sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -4152,18 +4056,6 @@ packages:
|
||||||
is-wsl: 2.2.0
|
is-wsl: 2.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/optionator/0.8.3:
|
|
||||||
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
|
||||||
engines: {node: '>= 0.8.0'}
|
|
||||||
dependencies:
|
|
||||||
deep-is: 0.1.4
|
|
||||||
fast-levenshtein: 2.0.6
|
|
||||||
levn: 0.3.0
|
|
||||||
prelude-ls: 1.1.2
|
|
||||||
type-check: 0.3.2
|
|
||||||
word-wrap: 1.2.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/optionator/0.9.1:
|
/optionator/0.9.1:
|
||||||
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
|
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
@ -4197,32 +4089,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
|
|
||||||
/pac-proxy-agent/5.0.0:
|
|
||||||
resolution: {integrity: sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
dependencies:
|
|
||||||
'@tootallnate/once': 1.1.2
|
|
||||||
agent-base: 6.0.2
|
|
||||||
debug: 4.3.4
|
|
||||||
get-uri: 3.0.2
|
|
||||||
http-proxy-agent: 4.0.1
|
|
||||||
https-proxy-agent: 5.0.1
|
|
||||||
pac-resolver: 5.0.1
|
|
||||||
raw-body: 2.5.1
|
|
||||||
socks-proxy-agent: 5.0.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/pac-resolver/5.0.1:
|
|
||||||
resolution: {integrity: sha512-cy7u00ko2KVgBAjuhevqpPeHIkCIqPe1v24cydhWjmeuzaBfmUWFCZJ1iAh5TuVzVZoUzXIW7K8sMYOZ84uZ9Q==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
dependencies:
|
|
||||||
degenerator: 3.0.2
|
|
||||||
ip: 1.1.8
|
|
||||||
netmask: 2.0.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/parent-module/1.0.1:
|
/parent-module/1.0.1:
|
||||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
@ -4352,11 +4218,6 @@ packages:
|
||||||
tunnel-agent: 0.6.0
|
tunnel-agent: 0.6.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/prelude-ls/1.1.2:
|
|
||||||
resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
|
|
||||||
engines: {node: '>= 0.8.0'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/prelude-ls/1.2.1:
|
/prelude-ls/1.2.1:
|
||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
@ -4391,26 +4252,6 @@ packages:
|
||||||
ipaddr.js: 1.9.1
|
ipaddr.js: 1.9.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/proxy-agent/5.0.0:
|
|
||||||
resolution: {integrity: sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
dependencies:
|
|
||||||
agent-base: 6.0.2
|
|
||||||
debug: 4.3.4
|
|
||||||
http-proxy-agent: 4.0.1
|
|
||||||
https-proxy-agent: 5.0.1
|
|
||||||
lru-cache: 5.1.1
|
|
||||||
pac-proxy-agent: 5.0.0
|
|
||||||
proxy-from-env: 1.1.0
|
|
||||||
socks-proxy-agent: 5.0.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/proxy-from-env/1.1.0:
|
|
||||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/pump/3.0.0:
|
/pump/3.0.0:
|
||||||
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
|
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -4465,15 +4306,6 @@ packages:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/readable-stream/1.1.14:
|
|
||||||
resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==}
|
|
||||||
dependencies:
|
|
||||||
core-util-is: 1.0.3
|
|
||||||
inherits: 2.0.4
|
|
||||||
isarray: 0.0.1
|
|
||||||
string_decoder: 0.10.31
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/readable-stream/2.3.7:
|
/readable-stream/2.3.7:
|
||||||
resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}
|
resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -4591,6 +4423,10 @@ packages:
|
||||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||||
|
|
||||||
|
/rfdc/1.3.0:
|
||||||
|
resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/rimraf/3.0.2:
|
/rimraf/3.0.2:
|
||||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
@ -4718,36 +4554,6 @@ packages:
|
||||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
/smart-buffer/4.2.0:
|
|
||||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
|
||||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/socks-proxy-agent/5.0.1:
|
|
||||||
resolution: {integrity: sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
dependencies:
|
|
||||||
agent-base: 6.0.2
|
|
||||||
debug: 4.3.4
|
|
||||||
socks: 2.7.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/socks/2.7.1:
|
|
||||||
resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==}
|
|
||||||
engines: {node: '>= 10.13.0', npm: '>= 3.0.0'}
|
|
||||||
dependencies:
|
|
||||||
ip: 2.0.0
|
|
||||||
smart-buffer: 4.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/source-map/0.6.1:
|
|
||||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
dev: false
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
/statuses/2.0.1:
|
/statuses/2.0.1:
|
||||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
@ -4766,6 +4572,17 @@ packages:
|
||||||
readable-stream: 2.3.7
|
readable-stream: 2.3.7
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/streamroller/3.1.5:
|
||||||
|
resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==}
|
||||||
|
engines: {node: '>=8.0'}
|
||||||
|
dependencies:
|
||||||
|
date-format: 4.0.14
|
||||||
|
debug: 4.3.4
|
||||||
|
fs-extra: 8.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/string-natural-compare/3.0.1:
|
/string-natural-compare/3.0.1:
|
||||||
resolution: {integrity: sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==}
|
resolution: {integrity: sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -4814,10 +4631,6 @@ packages:
|
||||||
define-properties: 1.2.0
|
define-properties: 1.2.0
|
||||||
es-abstract: 1.21.1
|
es-abstract: 1.21.1
|
||||||
|
|
||||||
/string_decoder/0.10.31:
|
|
||||||
resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/string_decoder/1.1.1:
|
/string_decoder/1.1.1:
|
||||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -4960,10 +4773,6 @@ packages:
|
||||||
/tslib/1.14.1:
|
/tslib/1.14.1:
|
||||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||||
|
|
||||||
/tslib/2.5.0:
|
|
||||||
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/tsutils/3.21.0_vjs4wwfhdfzsrv6t7i2fdbohmm:
|
/tsutils/3.21.0_vjs4wwfhdfzsrv6t7i2fdbohmm:
|
||||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
@ -4979,13 +4788,6 @@ packages:
|
||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/type-check/0.3.2:
|
|
||||||
resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}
|
|
||||||
engines: {node: '>= 0.8.0'}
|
|
||||||
dependencies:
|
|
||||||
prelude-ls: 1.1.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/type-check/0.4.0:
|
/type-check/0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
@ -5102,15 +4904,6 @@ packages:
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/vm2/3.9.14:
|
|
||||||
resolution: {integrity: sha512-HgvPHYHeQy8+QhzlFryvSteA4uQLBCOub02mgqdR+0bN/akRZ48TGB1v0aCv7ksyc0HXx16AZtMHKS38alc6TA==}
|
|
||||||
engines: {node: '>=6.0'}
|
|
||||||
hasBin: true
|
|
||||||
dependencies:
|
|
||||||
acorn: 8.8.2
|
|
||||||
acorn-walk: 8.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/webidl-conversions/3.0.1:
|
/webidl-conversions/3.0.1:
|
||||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -5192,10 +4985,6 @@ packages:
|
||||||
optional: true
|
optional: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/xregexp/2.0.0:
|
|
||||||
resolution: {integrity: sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/y18n/5.0.8:
|
/y18n/5.0.8:
|
||||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue