From 42ac8d85c41b52dd9314cbbcfa19f4d582200ec3 Mon Sep 17 00:00:00 2001 From: AnidlSupport Date: Fri, 3 Mar 2023 19:10:20 +0100 Subject: [PATCH] New Logger + HLS-Download include --- .eslintignore | 3 +- .eslintrc.json | 10 + @types/hls-download.d.ts | 32 -- crunchy.ts | 233 ++++----- funi.ts | 97 ++-- gui.ts | 2 + gui/react/src/@types/FC.d.ts | 2 +- gui/react/src/App.tsx | 2 +- gui/react/src/Layout.tsx | 20 +- gui/react/src/Style.tsx | 6 +- .../src/components/AddToQueue/AddToQueue.tsx | 16 +- .../DownloadSelector/DownloadSelector.tsx | 26 +- .../Listing/EpisodeListing.tsx | 176 +++---- .../AddToQueue/SearchBox/SearchBox.tsx | 24 +- gui/react/src/components/AuthButton.tsx | 22 +- gui/react/src/components/LogoutButton.tsx | 20 +- .../DownloadManager/DownloadManager.tsx | 16 +- .../src/components/MainFrame/MainFrame.tsx | 10 +- .../src/components/MainFrame/Queue/Queue.tsx | 18 +- gui/react/src/components/MenuBar/MenuBar.tsx | 10 +- gui/react/src/components/Require.tsx | 8 +- gui/react/src/components/StartQueue.tsx | 18 +- .../src/components/reusable/ContextMenu.tsx | 24 +- .../reusable/LinearProgressWithLabel.tsx | 2 +- .../src/components/reusable/MultiSelect.tsx | 10 +- gui/react/src/hooks/useStore.tsx | 6 +- gui/react/src/index.tsx | 8 +- gui/react/src/provider/ErrorHandler.tsx | 32 +- gui/react/src/provider/MessageChannel.tsx | 24 +- gui/react/src/provider/QueueProvider.tsx | 4 +- gui/react/src/provider/ServiceProvider.tsx | 2 +- gui/react/src/provider/Store.tsx | 4 +- gui/server/index.ts | 3 +- gui/server/services/base.ts | 3 +- gui/server/services/crunchyroll.ts | 7 +- gui/server/services/funimation.ts | 7 +- gui/server/websocket.ts | 17 +- index.ts | 13 +- modules/build.ts | 5 +- modules/hls-download.ts | 441 ++++++++++++++++++ modules/log.ts | 66 +++ modules/module.cfg-loader.ts | 13 +- modules/module.curl-req.ts | 3 +- modules/module.filename.ts | 4 +- modules/module.getdata.ts | 15 +- modules/module.merger.ts | 22 +- modules/module.parseSelect.ts | 14 +- modules/module.req.ts | 17 +- modules/module.updater.ts | 25 +- modules/sei-helper-fixes.ts | 3 +- package.json | 4 +- pnpm-lock.yaml | 289 ++---------- 52 files changed, 1076 insertions(+), 782 deletions(-) delete mode 100644 @types/hls-download.d.ts create mode 100644 modules/hls-download.ts create mode 100644 modules/log.ts diff --git a/.eslintignore b/.eslintignore index a3d93c5..dd20ec6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ lib /videos/*.ts build -dev.js \ No newline at end of file +dev.js +tsc.ts \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 1b48ac0..146345f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,7 +21,17 @@ "react", "@typescript-eslint" ], + "overrides": [ + { + "files": ["gui/react/**/*"], + "rules": { + "no-console": 0 + } + } + ], "rules": { + "no-console": 2, + "react/prop-types": 0, "react-hooks/exhaustive-deps": 0, "@typescript-eslint/no-explicit-any": "off", "indent": [ diff --git a/@types/hls-download.d.ts b/@types/hls-download.d.ts deleted file mode 100644 index 8d51c3f..0000000 --- a/@types/hls-download.d.ts +++ /dev/null @@ -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[], - 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 - } - }> - } -} \ No newline at end of file diff --git a/crunchy.ts b/crunchy.ts index 6fa8c87..a2f59d5 100644 --- a/crunchy.ts +++ b/crunchy.ts @@ -5,9 +5,10 @@ import fs from 'fs-extra'; // package program import packageJson from './package.json'; // plugins +import { console } from './modules/log'; import shlp from 'sei-helper'; import m3u8 from 'm3u8-parsed'; -import streamdl from 'hls-download'; +import streamdl from './modules/hls-download'; // custom modules import * as fontsData from './modules/module.fontsData'; @@ -60,7 +61,7 @@ export default class Crunchy implements ServiceClass { } 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); // load binaries @@ -103,7 +104,7 @@ export default class Crunchy implements ServiceClass { if (selected.isOk) { for (const select of selected.value) { 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; } } @@ -117,14 +118,14 @@ export default class Crunchy implements ServiceClass { else if(argv.s && argv.s.match(/^[0-9A-Z]{9}$/)){ await this.refreshToken(); 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]]; const selected = await this.getSeasonById(argv.s, argv.numbers, argv.e, argv.but, argv.all); if (selected.isOk) { for (const select of selected.value) { 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; } } @@ -136,24 +137,24 @@ export default class Crunchy implements ServiceClass { const selected = await this.getObjectById(argv.e, false); for (const select of selected as Partial[]) { 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 true; } 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() { - console.log('[INFO] Downloading fonts...'); + console.info('Downloading fonts...'); const fonts = Object.values(fontsData.fontFamilies).reduce((pre, curr) => pre.concat(curr)); for(const f of fonts) { const fontLoc = path.join(this.cfg.dir.fonts, f); if(fs.existsSync(fontLoc) && fs.statSync(fontLoc).size != 0){ - console.log(`[INFO] ${f} already downloaded!`); + console.info(`${f} already downloaded!`); } else{ const fontFolder = path.dirname(fontLoc); @@ -164,20 +165,20 @@ export default class Crunchy implements ServiceClass { fs.ensureDirSync(fontFolder); } catch(e){ - console.log(); + console.info(''); } const fontUrl = fontsData.root + f; const getFont = await this.req.getData(fontUrl, { binary: true }); if(getFont.ok && getFont.res){ fs.writeFileSync(fontLoc, getFont.res.body); - console.log(`[INFO] Downloaded: ${f}`); + console.info(`Downloaded: ${f}`); } 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 { @@ -194,14 +195,14 @@ export default class Crunchy implements ServiceClass { }; const authReq = await this.req.getData(api.beta_auth, authReqOpts); if(!authReq.ok || !authReq.res){ - console.log('[ERROR] Authentication failed!'); + console.error('Authentication failed!'); return { isOk: false, reason: new Error('Authentication failed') }; } this.token = JSON.parse(authReq.res.body); this.token.expires = new Date(Date.now() + this.token.expires_in); yamlCfg.saveCRToken(this.token); 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 }; } @@ -217,7 +218,7 @@ export default class Crunchy implements ServiceClass { }; const authReq = await this.req.getData(api.beta_auth, authReqOpts); if(!authReq.ok || !authReq.res){ - console.log('[ERROR] Authentication failed!'); + console.error('Authentication failed!'); return; } this.token = JSON.parse(authReq.res.body); @@ -227,7 +228,7 @@ export default class Crunchy implements ServiceClass { public async getProfile() : Promise { if(!this.token.access_token){ - console.log('[ERROR] No access token!'); + console.error('No access token!'); return false; } const profileReqOptions = { @@ -238,11 +239,11 @@ export default class Crunchy implements ServiceClass { }; const profileReq = await this.req.getData(api.beta_profile, profileReqOptions); if(!profileReq.ok || !profileReq.res){ - console.log('[ERROR] Get profile failed!'); + console.error('Get profile failed!'); return false; } 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; } @@ -256,7 +257,7 @@ export default class Crunchy implements ServiceClass { if (!(Date.now() > new Date(this.token.expires).getTime()) && ifNeeded) { return; } 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({ '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); if(!authReq.ok || !authReq.res){ - console.log('[ERROR] Authentication failed!'); + console.error('Authentication failed!'); return; } this.token = JSON.parse(authReq.res.body); @@ -281,14 +282,14 @@ export default class Crunchy implements ServiceClass { if (!silent) await this.getProfile(); } else { - console.log('[INFO] USER: Anonymous'); + console.info('USER: Anonymous'); } await this.getCMStoken(); } public async getCMStoken(){ if(!this.token.access_token){ - console.log('[ERROR] No access token!'); + console.error('No access token!'); return; } const cmsTokenReqOpts = { @@ -299,17 +300,17 @@ export default class Crunchy implements ServiceClass { }; const cmsTokenReq = await this.req.getData(api.beta_cmsToken, cmsTokenReqOpts); if(!cmsTokenReq.ok || !cmsTokenReq.res){ - console.log('[ERROR] Authentication CMS token failed!'); + console.error('Authentication CMS token failed!'); return; } 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(){ // check token if(!this.cmsToken.cms){ - console.log('[ERROR] Authentication required!'); + console.error('Authentication required!'); return; } // opts @@ -325,15 +326,15 @@ export default class Crunchy implements ServiceClass { ].join(''); const indexReq = await this.req.getData(indexReqOpts); if(!indexReq.ok || ! indexReq.res){ - console.log('[ERROR] Get CMS index FAILED!'); + console.error('Get CMS index FAILED!'); return; } - console.log(JSON.parse(indexReq.res.body)); + console.info(JSON.parse(indexReq.res.body)); } public async doSearch(data: SearchData): Promise{ if(!this.token.access_token){ - console.log('[ERROR] Authentication required!'); + console.error('Authentication required!'); return { isOk: false, reason: new Error('Not authenticated') }; } const searchReqOpts = { @@ -352,12 +353,12 @@ export default class Crunchy implements ServiceClass { }).toString(); const searchReq = await this.req.getData(`${api.search}?${searchParams}`, searchReqOpts); 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') }; } const searchResults = JSON.parse(searchReq.res.body) as CrunchySearch; if(searchResults.total < 1){ - console.log('[INFO] Nothing Found!'); + console.info('Nothing Found!'); return { isOk: true, value: [] }; } @@ -368,23 +369,23 @@ export default class Crunchy implements ServiceClass { 'episode': 'Found episodes' }; 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 const pageCur = searchStart > 0 ? Math.ceil(searchStart/5) + 1 : 1; const pageMax = Math.ceil(search_item.count/5); // pages per category if(search_item.count < 1){ - console.log(' [INFO] Nothing Found...'); + console.info(' Nothing Found...'); } if(search_item.count > 0){ if(pageCur > pageMax){ - console.log(' [INFO] Last page is %s...', pageMax); + console.info(' Last page is %s...', pageMax); continue; } for(const item of search_item.items){ 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'); @@ -404,7 +405,7 @@ export default class Crunchy implements ServiceClass { public async logObject(item: ParseItem, pad?: number, getSeries?: boolean, getMovieListing?: boolean){ if(this.debug){ - console.log(item); + console.info(item); } pad = pad ?? 2; getSeries = getSeries === undefined ? true : getSeries; @@ -542,7 +543,7 @@ export default class Crunchy implements ServiceClass { } // show entry - console.log( + console.info( '%s%s[%s] %s%s%s', ''.padStart(item.isSelected ? pad-1 : pad, ' '), item.isSelected ? '✓' : '', @@ -553,27 +554,27 @@ export default class Crunchy implements ServiceClass { ); 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){ iMetadata.subtitle_locales = item.subtitle_locales; } if (item.versions && audio_languages.length > 0) { - console.log( + console.info( '%s- Versions: %s', ''.padStart(pad + 2, ' '), langsData.parseSubtitlesArray(audio_languages) ); } if(iMetadata.subtitle_locales && iMetadata.subtitle_locales.length > 0){ - console.log( + console.info( '%s- Subtitles: %s', ''.padStart(pad + 2, ' '), langsData.parseSubtitlesArray(iMetadata.subtitle_locales) ); } if(item.availability_notes){ - console.log( + console.info( '%s- Availability notes: %s', ''.padStart(pad + 2, ' '), item.availability_notes.replace(/\[[^\]]*\]?/gm, '') @@ -581,11 +582,11 @@ export default class Crunchy implements ServiceClass { } if(item.type == 'series' && getSeries){ await this.logSeriesById(item.id, pad, true); - console.log(); + console.info(''); } if(item.type == 'movie_listing' && getMovieListing){ 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; // check token if(!this.cmsToken.cms){ - console.log('[ERROR] Authentication required!'); + console.error('Authentication required!'); return; } // opts @@ -609,7 +610,7 @@ export default class Crunchy implements ServiceClass { if(!hideSeriesTitle){ const seriesReq = await this.req.getData(`${api.cms}/series/${id}?preferred_audio_language=ja-JP`, AuthHeaders); if(!seriesReq.ok || !seriesReq.res){ - console.log('[ERROR] Series Request FAILED!'); + console.error('Series Request FAILED!'); return; } const seriesData = JSON.parse(seriesReq.res.body); @@ -618,13 +619,13 @@ export default class Crunchy implements ServiceClass { // seasons list const seriesSeasonListReq = await this.req.getData(`${api.cms}/series/${id}/seasons?preferred_audio_language=ja-JP`, AuthHeaders); if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){ - console.log('[ERROR] Series Request FAILED!'); + console.error('Series Request FAILED!'); return; } // parse data const seasonsList = JSON.parse(seriesSeasonListReq.res.body) as SeriesSearch; if(seasonsList.total < 1){ - console.log('[INFO] Series is empty!'); + console.info('Series is empty!'); return; } for(const item of seasonsList.data){ @@ -635,7 +636,7 @@ export default class Crunchy implements ServiceClass { public async logMovieListingById(id: string, pad?: number){ pad = pad || 2; if(!this.cmsToken.cms){ - console.log('[ERROR] Authentication required!'); + console.error('Authentication required!'); return; } const movieListingReqOpts = [ @@ -651,12 +652,12 @@ export default class Crunchy implements ServiceClass { ].join(''); const movieListingReq = await this.req.getData(movieListingReqOpts); if(!movieListingReq.ok || !movieListingReq.res){ - console.log('[ERROR] Movie Listing Request FAILED!'); + console.error('Movie Listing Request FAILED!'); return; } const movieListing = JSON.parse(movieListingReq.res.body); if(movieListing.total < 1){ - console.log('[INFO] Movie Listing is empty!'); + console.info('Movie Listing is empty!'); return; } for(const item of movieListing.items){ @@ -666,7 +667,7 @@ export default class Crunchy implements ServiceClass { public async getNewlyAdded(page?: number){ if(!this.token.access_token){ - console.log('[ERROR] Authentication required!'); + console.error('Authentication required!'); return; } const newlyAddedReqOpts = { @@ -682,11 +683,11 @@ export default class Crunchy implements ServiceClass { }).toString(); const newlyAddedReq = await this.req.getData(`${api.beta_browse}?${newlyAddedParams}`, newlyAddedReqOpts); if(!newlyAddedReq.ok || !newlyAddedReq.res){ - console.log('[ERROR] Get newly added FAILED!'); + console.error('Get newly added FAILED!'); return; } const newlyAddedResults = JSON.parse(newlyAddedReq.res.body); - console.log('[INFO] Newly added:'); + console.info('Newly added:'); for(const i of newlyAddedResults.items){ 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 pageCur = itemPad > 0 ? Math.ceil(itemPad/25) + 1 : 1; 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> { if(!this.cmsToken.cms){ - console.log('[ERROR] Authentication required!'); + console.error('Authentication required!'); return { isOk: false, reason: new Error('Authentication required') }; } @@ -714,7 +715,7 @@ export default class Crunchy implements ServiceClass { //get show info const showInfoReq = await this.req.getData(`${api.cms}/seasons/${id}?preferred_audio_language=ja-JP`, AuthHeaders); 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.') }; } const showInfo = JSON.parse(showInfoReq.res.body); @@ -723,7 +724,7 @@ export default class Crunchy implements ServiceClass { //get episode info const reqEpsList = await this.req.getData(`${api.cms}/seasons/${id}/episodes?preferred_audio_language=ja-JP`, AuthHeaders); 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.') }; } const episodeList = JSON.parse(reqEpsList.res.body) as CrunchyEpisodeList; @@ -735,7 +736,7 @@ export default class Crunchy implements ServiceClass { const epNumLen = numbers; if(episodeList.total < 1){ - console.log(' [INFO] Season is empty!'); + console.info(' Season is empty!'); return { isOk: true, value: [] }; } @@ -808,10 +809,10 @@ export default class Crunchy implements ServiceClass { // display 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 }; } @@ -831,19 +832,19 @@ export default class Crunchy implements ServiceClass { public async getObjectById(e?: string, earlyReturn?: boolean): Promise[]|undefined> { if(!this.cmsToken.cms){ - console.log('[ERROR] Authentication required!'); + console.error('Authentication required!'); return; } const doEpsFilter = parseSelect(e as string); if(doEpsFilter.values.length < 1){ - console.log('\n[INFO] Objects not selected!\n'); + console.info('\nObjects not selected!\n'); return; } // 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 = { headers: { @@ -855,10 +856,10 @@ export default class Crunchy implements ServiceClass { // reqs const objectReq = await this.req.getData(`${api.cms}/objects/${doEpsFilter.values.join(',')}?preferred_audio_language=ja-JP`, AuthHeaders); 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){ 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; return objectInfo; } @@ -913,7 +914,7 @@ export default class Crunchy implements ServiceClass { } await this.logObject(item, 2); } - console.log(); + console.info(''); return selectedMedia; } @@ -923,7 +924,7 @@ export default class Crunchy implements ServiceClass { error: boolean } | undefined> { if(!this.cmsToken.cms){ - console.log('[ERROR] Authentication required!'); + console.error('Authentication required!'); return; } @@ -937,7 +938,7 @@ export default class Crunchy implements ServiceClass { const files: DownloadedMedia[] = []; if(medias.data.every(a => !a.playback)){ - console.log('[WARN] Video not available!'); + console.warn('Video not available!'); return undefined; } @@ -946,7 +947,7 @@ export default class Crunchy implements ServiceClass { 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 await this.refreshToken(true, true); @@ -962,7 +963,7 @@ export default class Crunchy implements ServiceClass { if (mMeta.versions && mMeta.lang) { mediaId = mMeta.versions.find(a => a.audio_locale == mMeta.lang?.cr_locale)?.media_guid as string; if (!mediaId) { - console.log('[ERROR] Selected language not found.'); + console.error('Selected language not found.'); return undefined; } } @@ -973,10 +974,10 @@ export default class Crunchy implements ServiceClass { let playbackReq = await this.req.getData(`${api.cms}/videos/${mediaId}/streams`, AuthHeaders); 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); if(!playbackReq.ok || !playbackReq.res){ - console.log('[ERROR] Fallback Request Stream URLs FAILED!'); + console.error('Fallback Request Stream URLs FAILED!'); return undefined; } } @@ -1021,7 +1022,7 @@ export default class Crunchy implements ServiceClass { } if(streams.length < 1){ - console.log('[WARN] No full streams found!'); + console.warn('No full streams found!'); return undefined; } @@ -1044,7 +1045,7 @@ export default class Crunchy implements ServiceClass { if(options.hslang != 'none'){ 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) => { if(s.hardsub_lang == '-'){ return false; @@ -1053,9 +1054,9 @@ export default class Crunchy implements ServiceClass { }); } 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){ - console.log('[WARN] Try other hardsubs stream:', hsLangs.join(', ')); + console.warn('Try other hardsubs stream:', hsLangs.join(', ')); } dlFailed = true; } @@ -1068,13 +1069,13 @@ export default class Crunchy implements ServiceClass { return true; }); if(streams.length < 1){ - console.log('[WARN] Raw streams not available!'); + console.warn('Raw streams not available!'); if(hsLangs.length > 0){ - console.log('[WARN] Try hardsubs stream:', hsLangs.join(', ')); + console.warn('Try hardsubs stream:', hsLangs.join(', ')); } dlFailed = true; } - console.log('[INFO] Selecting raw stream'); + console.info('Selecting raw stream'); } let curStream: @@ -1086,19 +1087,19 @@ export default class Crunchy implements ServiceClass { streams.forEach((s, i) => { 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]; - 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){ const streamPlaylistsReq = await this.req.getData(curStream.url); if(!streamPlaylistsReq.ok || !streamPlaylistsReq.res){ - console.log('[ERROR] CAN\'T FETCH VIDEO PLAYLISTS!'); + console.error('CAN\'T FETCH VIDEO PLAYLISTS!'); dlFailed = true; } else{ @@ -1139,7 +1140,7 @@ export default class Crunchy implements ServiceClass { && plStreams[plServer][plResolutionText] != pl.uri && 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{ plStreams[plServer][plResolutionText] = pl.uri; @@ -1170,7 +1171,7 @@ export default class Crunchy implements ServiceClass { }); let quality = options.q === 0 ? plQuality.length : options.q; 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; } // 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]] : ''; - console.log(`[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(`Servers available:\n\t${plServerList.join('\n\t')}`); + console.info(`Available qualities:\n\t${plQuality.map((a, ind) => `[${ind+1}] ${a.str}`).join('\n\t')}`); if(selPlUrl != ''){ variables.push({ @@ -1196,18 +1197,18 @@ export default class Crunchy implements ServiceClass { }); const lang = langsData.languages.find(a => a.code === curStream?.audio_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; } - console.log(`[INFO] Selected quality: ${Object.keys(plSelectedList).find(a => plSelectedList[a] === selPlUrl)} @ ${plSelectedServer}`); - console.log('[INFO] Stream URL:', selPlUrl); + console.info(`Selected quality: ${Object.keys(plSelectedList).find(a => plSelectedList[a] === selPlUrl)} @ ${plSelectedServer}`); + console.info('Stream URL:', selPlUrl); // TODO check filename 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); - console.log(`[INFO] Output filename: ${outFile}`); + console.info(`Output filename: ${outFile}`); const chunkPage = await this.req.getData(selPlUrl); if(!chunkPage.ok || !chunkPage.res){ - console.log('[ERROR] CAN\'T FETCH VIDEO PLAYLIST!'); + console.error('CAN\'T FETCH VIDEO PLAYLIST!'); dlFailed = true; } else{ @@ -1215,7 +1216,7 @@ export default class Crunchy implements ServiceClass { const totalParts = chunkPlaylist.segments.length; const mathParts = Math.ceil(totalParts / 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 split = outFile.split(path.sep).slice(0, -1); split.forEach((val, ind, arr) => { @@ -1242,7 +1243,7 @@ export default class Crunchy implements ServiceClass { }) : undefined }).download(); if(!dlStreamByPl.ok){ - console.log(`[ERROR] DL Stats: ${JSON.stringify(dlStreamByPl.parts)}\n`); + console.error(`DL Stats: ${JSON.stringify(dlStreamByPl.parts)}\n`); dlFailed = true; } files.push({ @@ -1254,14 +1255,14 @@ export default class Crunchy implements ServiceClass { } } else{ - console.log('[ERROR] Quality not selected!\n'); + console.error('Quality not selected!\n'); dlFailed = true; } } } else if(options.novids){ 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'){ - console.log('[WARN] Subtitles downloading disabled for hardsubs streams.'); + console.warn('Subtitles downloading disabled for hardsubs streams.'); options.skipsubs = true; } @@ -1307,7 +1308,7 @@ export default class Crunchy implements ServiceClass { sxData.title = `${langItem.language} / ${sxData.title}`; sxData.fonts = fontsData.assFonts(sBody) as Font[]; fs.writeFileSync(sxData.path, sBody); - console.log(`[INFO] Subtitle downloaded: ${sxData.file}`); + console.info(`Subtitle downloaded: ${sxData.file}`); files.push({ type: 'Subtitle', ...sxData as sxItem, @@ -1315,17 +1316,17 @@ export default class Crunchy implements ServiceClass { }); } else{ - console.log(`[WARN] Failed to download subtitle: ${sxData.file}`); + console.warn(`Failed to download subtitle: ${sxData.file}`); } } } } else{ - console.log('[WARN] Can\'t find urls for subtitles!'); + console.warn('Can\'t find urls for subtitles!'); } } else{ - console.log('[INFO] Subtitles downloading skipped!'); + console.info('Subtitles downloading skipped!'); } } return { @@ -1338,7 +1339,7 @@ export default class Crunchy implements ServiceClass { public async muxStreams(data: DownloadedMedia[], options: CrunchyMuxOptions) { this.cfg.bin = await yamlCfg.loadBinCfg(); 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({ onlyVid: [], skipSubMux: options.skipSubMux, @@ -1385,7 +1386,7 @@ export default class Crunchy implements ServiceClass { await merger.merge('ffmpeg', bin.FFmpeg); isMuxed = true; } else{ - console.log('\n[INFO] Done!\n'); + console.info('\nDone!\n'); return; } if (isMuxed && !options.nocleanup) @@ -1465,7 +1466,7 @@ export default class Crunchy implements ServiceClass { for (const key of Object.keys(episodes)) { 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() } - Season ${item.items[0].season_number} - ${item.items[0].title} [${ item.items.map((a, index) => { @@ -1477,7 +1478,7 @@ export default class Crunchy implements ServiceClass { //TODO: Sort episodes to have specials at the end 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]) => { @@ -1500,13 +1501,13 @@ export default class Crunchy implements ServiceClass { public async downloadFromSeriesID(id: string, data: CurnchyMultiDownload) : Promise> { const { data: episodes } = await this.listSeriesID(id); - console.log(); - console.log('-'.repeat(30)); - console.log(); + console.info(''); + console.info('-'.repeat(30)); + console.info(''); const selected = this.itemSelectMultiDub(episodes, data.dubLang, data.but, data.all, data.e); for (const key of Object.keys(selected)) { 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 => { return `✓ ${a.lang?.name || 'Unknown Language'}`; }).join(', ') @@ -1611,7 +1612,7 @@ export default class Crunchy implements ServiceClass { public async parseSeriesById(id: string) { if(!this.cmsToken.cms){ - console.log('[ERROR] Authentication required!'); + console.error('Authentication required!'); return; } @@ -1625,13 +1626,13 @@ export default class Crunchy implements ServiceClass { // seasons list const seriesSeasonListReq = await this.req.getData(`${api.cms}/series/${id}/seasons?preferred_audio_language=ja-JP`, AuthHeaders); if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){ - console.log('[ERROR] Series Request FAILED!'); + console.error('Series Request FAILED!'); return; } // parse data const seasonsList = JSON.parse(seriesSeasonListReq.res.body) as SeriesSearch; if(seasonsList.total < 1){ - console.log('[INFO] Series is empty!'); + console.info('Series is empty!'); return; } return seasonsList; @@ -1639,7 +1640,7 @@ export default class Crunchy implements ServiceClass { public async getSeasonDataById(item: SeriesSearchItem, log = false){ if(!this.cmsToken.cms){ - console.log('[ERROR] Authentication required!'); + console.error('Authentication required!'); return; } @@ -1653,7 +1654,7 @@ export default class Crunchy implements ServiceClass { //get show info const showInfoReq = await this.req.getData(`${api.cms}/seasons/${item.id}?preferred_audio_language=ja-JP`, AuthHeaders); if(!showInfoReq.ok || !showInfoReq.res){ - console.log('[ERROR] Show Request FAILED!'); + console.error('Show Request FAILED!'); return; } const showInfo = JSON.parse(showInfoReq.res.body); @@ -1662,13 +1663,13 @@ export default class Crunchy implements ServiceClass { //get episode info const reqEpsList = await this.req.getData(`${api.cms}/seasons/${item.id}/episodes?preferred_audio_language=ja-JP`, AuthHeaders); if(!reqEpsList.ok || !reqEpsList.res){ - console.log('[ERROR] Episode List Request FAILED!'); + console.error('Episode List Request FAILED!'); return; } const episodeList = JSON.parse(reqEpsList.res.body) as CrunchyEpisodeList; if(episodeList.total < 1){ - console.log(' [INFO] Season is empty!'); + console.info(' Season is empty!'); return; } return episodeList; diff --git a/funi.ts b/funi.ts index b486d98..6c3d314 100644 --- a/funi.ts +++ b/funi.ts @@ -6,9 +6,10 @@ import path from 'path'; import packageJson from './package.json'; // modules extra +import { console } from './modules/log'; import * as shlp from 'sei-helper'; import m3u8 from 'm3u8-parsed'; -import hlsDownload, { HLSCallback } from 'hls-download'; +import hlsDownload, { HLSCallback } from './modules/hls-download'; // extra 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); if (argv.debug) this.debug = true; - console.log(`\n=== Multi Downloader NX ${packageJson.version} ===\n`); + console.info(`\n=== Multi Downloader NX ${packageJson.version} ===\n`); if (argv.allDubs) { 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){ const data = await this.getShow(true, { id: parseInt(argv.s), but: argv.but, all: argv.all, e: argv.e }); if (!data.isOk) { - console.log(`[ERROR] ${data.reason.message}`); + console.error(`${data.reason.message}`); return false; } let ok = true; @@ -112,7 +113,7 @@ export default class Funi implements ServiceClass { return ok; } 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 { @@ -129,14 +130,14 @@ export default class Funi implements ServiceClass { if(authData.ok && authData.res){ const resJSON = JSON.parse(authData.res.body); 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}); this.token = resJSON.token; return { isOk: true, value: undefined }; } else { - console.log('[ERROR]%s\n', ' No token found'); + console.info('[ERROR]%s\n', ' No token found'); if (this.debug) { - console.log(resJSON); + console.info(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); if(searchDataJSON.detail){ - console.log(`[ERROR] ${searchDataJSON.detail}`); + console.error(`${searchDataJSON.detail}`); return { isOk: false, reason: new Error(searchDataJSON.defail) }; } if(searchDataJSON.items && searchDataJSON.items.hits && log){ const shows = searchDataJSON.items.hits; - console.log('[INFO] Search Results:'); + console.info('Search Results:'); 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) - console.log('[INFO] Total shows found: %s\n',searchDataJSON.count); + console.info('Total shows found: %s\n',searchDataJSON.count); 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') }; } const showDataJSON = JSON.parse(showData.res.body); 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) }; } 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') }; } 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 const qs: { limit: number, @@ -219,7 +220,7 @@ export default class Funi implements ServiceClass { const parseEpStr = (epStr: string) => { const match = epStr.match(epNumRegex); if (!match) { - console.error('[ERROR] No match found'); + console.error('No match found'); return ['', '']; } if(match.length > 2){ @@ -241,7 +242,7 @@ export default class Funi implements ServiceClass { } else{ 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 ]; } return e; @@ -308,16 +309,16 @@ export default class Funi implements ServiceClass { conOut += `(${rtm_str}) [${qua_str+aud_str}]`; conOut += is_selected ? ' (selected)' : ''; conOut += eps.length-1 == parseInt(e) ? '\n' : ''; - console.log(conOut); + console.info(conOut); } if(fnSlug.length < 1){ if (log) - console.log('[INFO] Episodes not selected!\n'); + console.info('Episodes not selected!\n'); return { isOk: true, value: [] } ; } else{ if (log) - console.log('[INFO] Selected Episodes: %s\n',epSelEpsTxt.join(', ')); + console.info('Selected Episodes: %s\n',epSelEpsTxt.join(', ')); return { isOk: true, value: fnSlug }; } } @@ -347,15 +348,15 @@ export default class Funi implements ServiceClass { // end if (log) { - console.log( - '[INFO] %s - S%sE%s - %s', + console.info( + '%s - S%sE%s - %s', ep.parent.title, (ep.parent.seasonNumber ? ep.parent.seasonNumber : '?'), (ep.number ? ep.number : '?'), ep.title ); - console.log('[INFO] Available streams (Non-Encrypted):'); + console.info('Available streams (Non-Encrypted):'); } // map medias 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)) 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)` : '' }`); } @@ -424,7 +425,7 @@ export default class Funi implements ServiceClass { }); if(streamIds.length < 1){ if (log) - console.log('[ERROR] Track not selected\n'); + console.error('Track not selected\n'); return { isOk: false, reason: new Error('Track not selected') }; } else{ @@ -442,7 +443,7 @@ export default class Funi implements ServiceClass { const streamDataRes = JSON.parse(streamData.res.body) as StreamData; if(streamDataRes.errors){ 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) }; } else{ @@ -459,7 +460,7 @@ export default class Funi implements ServiceClass { } if(tsDlPath.length < 1){ if (log) - console.log('[ERROR] Unknown error\n'); + console.error('Unknown error\n'); return { isOk: false, reason: new Error('Unknown error') }; } else{ @@ -525,7 +526,7 @@ export default class Funi implements ServiceClass { if(plQualityLinkList.playlists[0].uri.match(vplReg)){ const audioKey = Object.keys(plQualityLinkList.mediaGroups.AUDIO).pop(); if (!audioKey) - return console.log('[ERROR] No audio key found'); + return console.error('No audio key found'); if(plQualityLinkList.mediaGroups.AUDIO[audioKey]){ const audioDataParts = plQualityLinkList.mediaGroups.AUDIO[audioKey], 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])); if (!language) { 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; } } @@ -547,7 +548,7 @@ export default class Funi implements ServiceClass { plQualityLinkList.playlists.sort((a, b) => { const aMatch = a.uri.match(vplReg), bMatch = b.uri.match(vplReg); if (!aMatch || !bMatch) { - console.log('[ERROR] Unable to match'); + console.info('Unable to match'); return 0; } const av = parseInt(aMatch[3]); @@ -584,7 +585,7 @@ export default class Funi implements ServiceClass { plStreams[plServer] = {}; } 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{ plStreams[plServer][plLayerId] = plUrlDl; @@ -604,7 +605,7 @@ export default class Funi implements ServiceClass { } } else { - console.log(s.uri); + console.info(s.uri); } } @@ -622,8 +623,8 @@ export default class Funi implements ServiceClass { plLayersStr.sort(); if (log) { - console.log(`[INFO] Servers available:\n\t${plServerList.join('\n\t')}`); - console.log(`[INFO] Available qualities:\n\t${plLayersStr.join('\n\t')}`); + console.info(`Servers available:\n\t${plServerList.join('\n\t')}`); + console.info(`Available qualities:\n\t${plLayersStr.join('\n\t')}`); } const selectedQuality = data.q === 0 || data.q > Object.keys(plLayersRes).length @@ -633,8 +634,8 @@ export default class Funi implements ServiceClass { if(videoUrl != ''){ if (log) { - console.log(`[INFO] Selected layer: ${selectedQuality} (${plLayersRes[selectedQuality].width}x${plLayersRes[selectedQuality].height}) @ ${plSelectedServer}`); - console.log('[INFO] Stream URL:',videoUrl); + console.info(`Selected layer: ${selectedQuality} (${plLayersRes[selectedQuality].width}x${plLayersRes[selectedQuality].height}) @ ${plSelectedServer}`); + console.info('Stream URL:',videoUrl); } fnOutput = parseFileName(data.fileName, ([ @@ -656,16 +657,16 @@ export default class Funi implements ServiceClass { if (fnOutput.length < 1) throw new Error(`Invalid path generated for input ${data.fileName}`); 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){ if (log) - console.log('[ERROR] Server not selected!\n'); + console.error('Server not selected!\n'); return; } else{ if (log) - console.log('[ERROR] Layer not selected!\n'); + console.error('Layer not selected!\n'); return; } @@ -715,7 +716,7 @@ export default class Funi implements ServiceClass { } else{ if (log) - console.log('[INFO] Skip video downloading...\n'); + console.info('Skip video downloading...\n'); } audio: if (plAud && !data.noaudio) { // download audio @@ -756,7 +757,7 @@ export default class Funi implements ServiceClass { // download subtitles if(stDlPath.length > 0){ if (log) - console.log('[INFO] Downloading subtitles...'); + console.info('Downloading subtitles...'); for (const subObject of stDlPath) { const subsSrc = await getData({ url: subObject.url, @@ -769,24 +770,24 @@ export default class Funi implements ServiceClass { } else{ if (log) - console.log('[ERROR] Failed to download subtitles!'); + console.error('Failed to download subtitles!'); addSubs = false; break; } } 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 (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; } if(data.skipmux){ if (log) - console.log('[INFO] Skipping muxing...'); + console.info('Skipping muxing...'); return; } @@ -796,7 +797,7 @@ export default class Funi implements ServiceClass { if ( data.novids ){ 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'; @@ -835,7 +836,7 @@ export default class Funi implements ServiceClass { } else{ if (log) - console.log('\n[INFO] Done!\n'); + console.info('\nDone!\n'); return true; } if (data.nocleanup) { @@ -844,7 +845,7 @@ export default class Funi implements ServiceClass { mergeInstance.cleanUp(); if (log) - console.log('\n[INFO] Done!\n'); + console.info('\nDone!\n'); return true; } @@ -878,7 +879,7 @@ export default class Funi implements ServiceClass { querystring: { deviceType: 'web' } }); if (!subs.ok || !subs.res || !subs.res.body) { - console.log('[ERROR] Subtitle Request failed.'); + console.error('Subtitle Request failed.'); return []; } const parsed: SubtitleRequest = JSON.parse(subs.res.body); diff --git a/gui.ts b/gui.ts index 0b191df..5bb963e 100644 --- a/gui.ts +++ b/gui.ts @@ -1 +1,3 @@ +process.env.isGUI = 'true'; +import './modules/log'; import './gui/server/index'; \ No newline at end of file diff --git a/gui/react/src/@types/FC.d.ts b/gui/react/src/@types/FC.d.ts index a6cdb42..5ec3de1 100644 --- a/gui/react/src/@types/FC.d.ts +++ b/gui/react/src/@types/FC.d.ts @@ -1,3 +1,3 @@ -type FCWithChildren = React.FC<{ +type FCWithChildren = React.FC<{ children?: React.ReactNode[]|React.ReactNode } & T> \ No newline at end of file diff --git a/gui/react/src/App.tsx b/gui/react/src/App.tsx index 7763b06..8f278f3 100644 --- a/gui/react/src/App.tsx +++ b/gui/react/src/App.tsx @@ -5,6 +5,6 @@ const App: React.FC = () => { return ( ); -} +}; export default App; diff --git a/gui/react/src/Layout.tsx b/gui/react/src/Layout.tsx index 9b473dd..450f00f 100644 --- a/gui/react/src/Layout.tsx +++ b/gui/react/src/Layout.tsx @@ -1,13 +1,13 @@ -import React from "react"; -import AuthButton from "./components/AuthButton"; -import { Box, Button } from "@mui/material"; -import MainFrame from "./components/MainFrame/MainFrame"; -import LogoutButton from "./components/LogoutButton"; -import AddToQueue from "./components/AddToQueue/AddToQueue"; +import React from 'react'; +import AuthButton from './components/AuthButton'; +import { Box, Button } from '@mui/material'; +import MainFrame from './components/MainFrame/MainFrame'; +import LogoutButton from './components/LogoutButton'; +import AddToQueue from './components/AddToQueue/AddToQueue'; import { messageChannelContext } from './provider/MessageChannel'; -import { ClearAll, Folder } from "@mui/icons-material"; -import StartQueueButton from "./components/StartQueue"; -import MenuBar from "./components/MenuBar/MenuBar"; +import { ClearAll, Folder } from '@mui/icons-material'; +import StartQueueButton from './components/StartQueue'; +import MenuBar from './components/MenuBar/MenuBar'; const Layout: React.FC = () => { @@ -27,6 +27,6 @@ const Layout: React.FC = () => { ; -} +}; export default Layout; \ No newline at end of file diff --git a/gui/react/src/Style.tsx b/gui/react/src/Style.tsx index ae508bb..4f44a07 100644 --- a/gui/react/src/Style.tsx +++ b/gui/react/src/Style.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import { Container, Box, ThemeProvider, createTheme, Theme } from "@mui/material"; +import React from 'react'; +import { Container, Box, ThemeProvider, createTheme, Theme } from '@mui/material'; const makeTheme = (mode: 'dark'|'light') : Partial => { return createTheme({ @@ -16,6 +16,6 @@ const Style: FCWithChildren = ({children}) => { {children} ; -} +}; export default Style; \ No newline at end of file diff --git a/gui/react/src/components/AddToQueue/AddToQueue.tsx b/gui/react/src/components/AddToQueue/AddToQueue.tsx index f6005ff..98333aa 100644 --- a/gui/react/src/components/AddToQueue/AddToQueue.tsx +++ b/gui/react/src/components/AddToQueue/AddToQueue.tsx @@ -1,9 +1,9 @@ -import { Add } from "@mui/icons-material"; -import { Box, Button, Dialog, Divider } from "@mui/material"; -import React from "react"; -import DownloadSelector from "./DownloadSelector/DownloadSelector"; -import EpisodeListing from "./DownloadSelector/Listing/EpisodeListing"; -import SearchBox from "./SearchBox/SearchBox"; +import { Add } from '@mui/icons-material'; +import { Box, Button, Dialog, Divider } from '@mui/material'; +import React from 'react'; +import DownloadSelector from './DownloadSelector/DownloadSelector'; +import EpisodeListing from './DownloadSelector/Listing/EpisodeListing'; +import SearchBox from './SearchBox/SearchBox'; const AddToQueue: React.FC = () => { const [isOpen, setOpen] = React.useState(false); @@ -21,7 +21,7 @@ const AddToQueue: React.FC = () => { Add to Queue - -} + ; +}; export default AddToQueue; \ No newline at end of file diff --git a/gui/react/src/components/AddToQueue/DownloadSelector/DownloadSelector.tsx b/gui/react/src/components/AddToQueue/DownloadSelector/DownloadSelector.tsx index 155262a..489b8f1 100644 --- a/gui/react/src/components/AddToQueue/DownloadSelector/DownloadSelector.tsx +++ b/gui/react/src/components/AddToQueue/DownloadSelector/DownloadSelector.tsx @@ -1,10 +1,10 @@ -import React from "react"; -import { Box, Button, TextField } from "@mui/material"; -import useStore from "../../../hooks/useStore"; -import MultiSelect from "../../reusable/MultiSelect"; -import { messageChannelContext } from "../../../provider/MessageChannel"; +import React from 'react'; +import { Box, Button, TextField } from '@mui/material'; +import useStore from '../../../hooks/useStore'; +import MultiSelect from '../../reusable/MultiSelect'; +import { messageChannelContext } from '../../../provider/MessageChannel'; import LoadingButton from '@mui/lab/LoadingButton'; -import { useSnackbar } from "notistack"; +import { useSnackbar } from 'notistack'; type DownloadSelectorProps = { onFinish?: () => unknown @@ -54,7 +54,7 @@ const DownloadSelector: React.FC = ({ onFinish }) => { setLoading(false); if (onFinish) onFinish(); - } + }; const listEpisodes = async () => { if (!store.downloadOptions.id) { @@ -76,7 +76,7 @@ const DownloadSelector: React.FC = ({ onFinish }) => { }); } setLoading(false); - } + }; return @@ -84,7 +84,7 @@ const DownloadSelector: React.FC = ({ onFinish }) => { dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, id: e.target.value } - }) + }); }} label='Item ID' /> { const parsed = parseInt(e.target.value); @@ -93,13 +93,13 @@ const DownloadSelector: React.FC = ({ onFinish }) => { dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, q: parsed } - }) + }); }} label='Quality Level (0 for max)' /> { dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, e: e.target.value } - }) + }); }} label='Episode Select' /> = ({ onFinish }) => { dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, fileName: e.target.value } - }) + }); }} sx={{ width: '50%' }} label='Filename' /> @@ -140,7 +140,7 @@ const DownloadSelector: React.FC = ({ onFinish }) => { List episodes Add to Queue - + ; }; export default DownloadSelector; \ No newline at end of file diff --git a/gui/react/src/components/AddToQueue/DownloadSelector/Listing/EpisodeListing.tsx b/gui/react/src/components/AddToQueue/DownloadSelector/Listing/EpisodeListing.tsx index b4f9464..98e75a6 100644 --- a/gui/react/src/components/AddToQueue/DownloadSelector/Listing/EpisodeListing.tsx +++ b/gui/react/src/components/AddToQueue/DownloadSelector/Listing/EpisodeListing.tsx @@ -1,7 +1,7 @@ -import { Box, List, ListItem, Typography, Divider, Dialog, Select, MenuItem, FormControl, InputLabel, Checkbox } from "@mui/material"; -import { CheckBox, CheckBoxOutlineBlank } from '@mui/icons-material' -import React from "react"; -import useStore from "../../../../hooks/useStore"; +import { Box, List, ListItem, Typography, Divider, Dialog, Select, MenuItem, FormControl, InputLabel, Checkbox } from '@mui/material'; +import { CheckBox, CheckBoxOutlineBlank } from '@mui/icons-material'; +import React from 'react'; +import useStore from '../../../../hooks/useStore'; const EpisodeListing: React.FC = () => { @@ -17,13 +17,13 @@ const EpisodeListing: React.FC = () => { s.push(season); } return s; - }, [ store.episodeListing ]) + }, [ store.episodeListing ]); const [selected, setSelected] = React.useState([]); React.useEffect(() => { setSelected(parseSelect(store.downloadOptions.e)); - }, [ store.episodeListing ]) + }, [ store.episodeListing ]); const close = () => { dispatch({ @@ -36,96 +36,96 @@ const EpisodeListing: React.FC = () => { ...store.downloadOptions, e: `${([...new Set([...parseSelect(store.downloadOptions.e), ...selected])]).join(',')}` } - }) - } + }); + }; return 0} onClose={close} scroll='paper' maxWidth='xl' sx={{ p: 2 }}> - - + + Episodes - - - Season - - - - - - selected.includes(a.e)) && !store.episodeListing.every(a => selected.includes(a.e))} - checked={store.episodeListing.every(a => selected.includes(a.e))} - onChange={() => { - if (selected.length > 0) { - setSelected([]); - } else { - setSelected(store.episodeListing.map(a => a.e)); - } - }} - /> - - {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 isSelected = selected.includes(e.toString()); - return { - let arr: string[] = []; - if (isSelected) { - arr = [...selected.filter(a => a !== e.toString())]; - } else { - arr = [...selected, e.toString()]; - } - setSelected(arr.filter(a => a.length > 0)); - }}> - - { isSelected ? : } - - {e} - - thumbnail - - - - {item.name} - - - {item.time.startsWith('00:') ? item.time.slice(3) : item.time} - - - - {item.description} + + + Season + + + + + + selected.includes(a.e)) && !store.episodeListing.every(a => selected.includes(a.e))} + checked={store.episodeListing.every(a => selected.includes(a.e))} + onChange={() => { + if (selected.length > 0) { + setSelected([]); + } else { + setSelected(store.episodeListing.map(a => a.e)); + } + }} + /> + + {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 isSelected = selected.includes(e.toString()); + return { + let arr: string[] = []; + if (isSelected) { + arr = [...selected.filter(a => a !== e.toString())]; + } else { + arr = [...selected, e.toString()]; + } + setSelected(arr.filter(a => a.length > 0)); + }}> + + { isSelected ? : } + + {e} + + thumbnail + + + + {item.name} + + + {item.time.startsWith('00:') ? item.time.slice(3) : item.time} - - -
- Available audio languages: {item.lang.join(', ')} -
-
-
- {index < length - 1 && } -
- })} -
-
-} + + {item.description} + + + +
+ Available audio languages: {item.lang.join(', ')} +
+
+ + + {index < length - 1 && } + ; + })} + + ; +}; const parseSelect = (s: string): string[] => { const ret: string[] = []; s.split(',').forEach(item => { if (item.includes('-')) { - let split = item.split('-'); + const split = item.split('-'); if (split.length !== 2) return; const match = split[0].match(/[A-Za-z]+/); @@ -156,8 +156,8 @@ const parseSelect = (s: string): string[] => { } else { ret.push(item); } - }) + }); return [...new Set(ret)]; -} +}; export default EpisodeListing; \ No newline at end of file diff --git a/gui/react/src/components/AddToQueue/SearchBox/SearchBox.tsx b/gui/react/src/components/AddToQueue/SearchBox/SearchBox.tsx index cd08fae..f33995b 100644 --- a/gui/react/src/components/AddToQueue/SearchBox/SearchBox.tsx +++ b/gui/react/src/components/AddToQueue/SearchBox/SearchBox.tsx @@ -1,11 +1,11 @@ -import React from "react"; -import { Box, ClickAwayListener, Divider, List, ListItem, Paper, TextField, Typography } from "@mui/material"; -import { SearchResponse } from "../../../../../../@types/messageHandler"; -import useStore from "../../../hooks/useStore"; -import { messageChannelContext } from "../../../provider/MessageChannel"; +import React from 'react'; +import { Box, ClickAwayListener, Divider, List, ListItem, Paper, TextField, Typography } from '@mui/material'; +import { SearchResponse } from '../../../../../../@types/messageHandler'; +import useStore from '../../../hooks/useStore'; +import { messageChannelContext } from '../../../provider/MessageChannel'; import './SearchBox.css'; -import ContextMenu from "../../reusable/ContextMenu"; -import { useSnackbar } from "notistack"; +import ContextMenu from '../../reusable/ContextMenu'; +import { useSnackbar } from 'notistack'; const SearchBox: React.FC = () => { const messageHandler = React.useContext(messageChannelContext); @@ -52,7 +52,7 @@ const SearchBox: React.FC = () => { searchResult.value.map((a, ind, arr) => { const imageRef = React.createRef(); return - { + { selectItem(a.id); setFocus(false); }}> @@ -83,13 +83,13 @@ const SearchBox: React.FC = () => { }); }} ]} popupItem={imageRef} /> {(ind < arr.length - 1) && } - + ; }) - : <>} + : <>} } - -} + ; +}; export default SearchBox; \ No newline at end of file diff --git a/gui/react/src/components/AuthButton.tsx b/gui/react/src/components/AuthButton.tsx index 4673e94..8f5d273 100644 --- a/gui/react/src/components/AuthButton.tsx +++ b/gui/react/src/components/AuthButton.tsx @@ -1,9 +1,9 @@ -import { Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from "@mui/material"; -import { Check, Close } from '@mui/icons-material' -import React from "react"; -import { messageChannelContext } from "../provider/MessageChannel"; -import Require from "./Require"; -import { useSnackbar } from "notistack"; +import { Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from '@mui/material'; +import { Check, Close } from '@mui/icons-material'; +import React from 'react'; +import { messageChannelContext } from '../provider/MessageChannel'; +import Require from './Require'; +import { useSnackbar } from 'notistack'; const AuthButton: React.FC = () => { const snackbar = useSnackbar(); @@ -24,9 +24,9 @@ const AuthButton: React.FC = () => { const checkAuth = async () => { setAuthed((await messageChannel?.checkToken())?.isOk ?? false); - } + }; - React.useEffect(() => { checkAuth() }, []); + React.useEffect(() => { checkAuth(); }, []); const handleSubmit = async () => { if (!messageChannel) @@ -52,7 +52,7 @@ const AuthButton: React.FC = () => { } await checkAuth(); setLoading(false); - } + }; return @@ -106,7 +106,7 @@ const AuthButton: React.FC = () => { - -} + ; +}; export default AuthButton; \ No newline at end of file diff --git a/gui/react/src/components/LogoutButton.tsx b/gui/react/src/components/LogoutButton.tsx index 5827820..c324dda 100644 --- a/gui/react/src/components/LogoutButton.tsx +++ b/gui/react/src/components/LogoutButton.tsx @@ -1,9 +1,9 @@ -import { ExitToApp } from "@mui/icons-material"; -import { Button } from "@mui/material"; -import React from "react"; -import useStore from "../hooks/useStore"; -import { messageChannelContext } from "../provider/MessageChannel"; -import Require from "./Require"; +import { ExitToApp } from '@mui/icons-material'; +import { Button } from '@mui/material'; +import React from 'react'; +import useStore from '../hooks/useStore'; +import { messageChannelContext } from '../provider/MessageChannel'; +import Require from './Require'; const LogoutButton: React.FC = () => { const messageChannel = React.useContext(messageChannelContext); @@ -16,10 +16,10 @@ const LogoutButton: React.FC = () => { dispatch({ type: 'service', payload: undefined - }) + }); else alert('Unable to change service'); - } + }; return - + ; -} +}; export default LogoutButton; \ No newline at end of file diff --git a/gui/react/src/components/MainFrame/DownloadManager/DownloadManager.tsx b/gui/react/src/components/MainFrame/DownloadManager/DownloadManager.tsx index 11baefc..7917707 100644 --- a/gui/react/src/components/MainFrame/DownloadManager/DownloadManager.tsx +++ b/gui/react/src/components/MainFrame/DownloadManager/DownloadManager.tsx @@ -1,7 +1,7 @@ -import React from "react"; -import { ExtendedProgress, QueueItem } from "../../../../../../@types/messageHandler"; -import { RandomEvent } from "../../../../../../@types/randomEvents"; -import { messageChannelContext } from "../../../provider/MessageChannel"; +import React from 'react'; +import { ExtendedProgress, QueueItem } from '../../../../../../@types/messageHandler'; +import { RandomEvent } from '../../../../../../@types/randomEvents'; +import { messageChannelContext } from '../../../provider/MessageChannel'; const useDownloadManager = () => { const messageHandler = React.useContext(messageChannelContext); @@ -13,15 +13,15 @@ const useDownloadManager = () => { const handler = (ev: RandomEvent<'progress'>) => { console.log(ev.data); setProgressData(ev.data); - } + }; const currentHandler = (ev: RandomEvent<'current'>) => { setCurrent(ev.data); - } + }; const finishHandler = () => { setProgressData(undefined); - } + }; messageHandler?.randomEvents.on('progress', handler); messageHandler?.randomEvents.on('current', currentHandler); @@ -35,6 +35,6 @@ const useDownloadManager = () => { }, [messageHandler]); return { data: progressData, current}; -} +}; export default useDownloadManager; \ No newline at end of file diff --git a/gui/react/src/components/MainFrame/MainFrame.tsx b/gui/react/src/components/MainFrame/MainFrame.tsx index 9e72789..de4db33 100644 --- a/gui/react/src/components/MainFrame/MainFrame.tsx +++ b/gui/react/src/components/MainFrame/MainFrame.tsx @@ -1,12 +1,12 @@ -import { Box } from "@mui/material"; -import React from "react"; +import { Box } from '@mui/material'; +import React from 'react'; import './MainFrame.css'; -import Queue from "./Queue/Queue"; +import Queue from './Queue/Queue'; const MainFrame: React.FC = () => { return - -} + ; +}; export default MainFrame; \ No newline at end of file diff --git a/gui/react/src/components/MainFrame/Queue/Queue.tsx b/gui/react/src/components/MainFrame/Queue/Queue.tsx index 7803b88..5a79c9e 100644 --- a/gui/react/src/components/MainFrame/Queue/Queue.tsx +++ b/gui/react/src/components/MainFrame/Queue/Queue.tsx @@ -1,9 +1,9 @@ -import { Box, Button, CircularProgress, Divider, LinearProgress, Skeleton, Typography } from "@mui/material"; -import React from "react"; -import { messageChannelContext } from "../../../provider/MessageChannel"; -import { queueContext } from "../../../provider/QueueProvider"; +import { Box, Button, CircularProgress, Divider, LinearProgress, Skeleton, Typography } from '@mui/material'; +import React from 'react'; +import { messageChannelContext } from '../../../provider/MessageChannel'; +import { queueContext } from '../../../provider/QueueProvider'; -import useDownloadManager from "../DownloadManager/DownloadManager"; +import useDownloadManager from '../DownloadManager/DownloadManager'; const Queue: React.FC = () => { const { data, current } = useDownloadManager(); @@ -12,7 +12,7 @@ const Queue: React.FC = () => { if (!msg) - return <>Never + return <>Never; return data || queue.length > 0 ? <> {data && <> @@ -114,8 +114,8 @@ const Queue: React.FC = () => { - -} + ; +}; const formatTime = (time: number) => { time = Math.floor(time / 1000); @@ -123,6 +123,6 @@ const formatTime = (time: number) => { time = time % 60; return `${minutes.toFixed(0).length < 2 ? `0${minutes}` : minutes}m${time.toFixed(0).length < 2 ? `0${time}` : time}s`; -} +}; export default Queue; \ No newline at end of file diff --git a/gui/react/src/components/MenuBar/MenuBar.tsx b/gui/react/src/components/MenuBar/MenuBar.tsx index 08a0728..4675ad3 100644 --- a/gui/react/src/components/MenuBar/MenuBar.tsx +++ b/gui/react/src/components/MenuBar/MenuBar.tsx @@ -1,6 +1,6 @@ -import { Box, Button, Menu, MenuItem } from "@mui/material"; -import React from "react" -import { messageChannelContext } from "../../provider/MessageChannel"; +import { Box, Button, Menu, MenuItem } from '@mui/material'; +import React from 'react'; +import { messageChannelContext } from '../../provider/MessageChannel'; const MenuBar: React.FC = () => { const [ openMenu, setMenuOpen ] = React.useState<'settings'|'help'|undefined>(); @@ -18,7 +18,7 @@ const MenuBar: React.FC = () => { }; if (!msg) - return <> + return <>; return - + ; -} +}; export default StartQueueButton; \ No newline at end of file diff --git a/gui/react/src/components/reusable/ContextMenu.tsx b/gui/react/src/components/reusable/ContextMenu.tsx index e7f6dca..12ebc96 100644 --- a/gui/react/src/components/reusable/ContextMenu.tsx +++ b/gui/react/src/components/reusable/ContextMenu.tsx @@ -1,5 +1,5 @@ -import { Box, Button, Divider, List, SxProps } from "@mui/material"; -import React from "react"; +import { Box, Button, Divider, List, SxProps } from '@mui/material'; +import React from 'react'; export type Option = { text: string, @@ -34,32 +34,32 @@ function ContextMenu(props: ContextMenuProps) { ev.preventDefault(); setAnchor({ x: ev.x + 10, y: ev.y + 10 }); setShow(true); - } + }; ref.current.addEventListener('contextmenu', listener); return () => { if (ref.current) - ref.current.removeEventListener('contextmenu', listener) + ref.current.removeEventListener('contextmenu', listener); }; - }, [ props.popupItem ]) + }, [ props.popupItem ]); return show ? {props.options.map((item, i) => { return item === 'divider' ? : - + ; })} - : <> + : <>; } export default ContextMenu; \ No newline at end of file diff --git a/gui/react/src/components/reusable/LinearProgressWithLabel.tsx b/gui/react/src/components/reusable/LinearProgressWithLabel.tsx index ad0d434..28ac451 100644 --- a/gui/react/src/components/reusable/LinearProgressWithLabel.tsx +++ b/gui/react/src/components/reusable/LinearProgressWithLabel.tsx @@ -19,6 +19,6 @@ const LinearProgressWithLabel: React.FC = (props) ); -} +}; export default LinearProgressWithLabel; \ No newline at end of file diff --git a/gui/react/src/components/reusable/MultiSelect.tsx b/gui/react/src/components/reusable/MultiSelect.tsx index fffa650..2bcd527 100644 --- a/gui/react/src/components/reusable/MultiSelect.tsx +++ b/gui/react/src/components/reusable/MultiSelect.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import { FormControl, InputLabel, MenuItem, OutlinedInput, Select, Theme, useTheme } from "@mui/material"; +import React from 'react'; +import { FormControl, InputLabel, MenuItem, OutlinedInput, Select, Theme, useTheme } from '@mui/material'; export type MultiSelectProps = { values: string[], @@ -41,7 +41,7 @@ const MultiSelect: React.FC = (props) => { multiple value={(props.selected ?? [])} 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.values.length === val.length - 1) props.onChange([]); @@ -68,7 +68,7 @@ const MultiSelect: React.FC = (props) => { ))} - -} + ; +}; export default MultiSelect; \ No newline at end of file diff --git a/gui/react/src/hooks/useStore.tsx b/gui/react/src/hooks/useStore.tsx index 77d55bd..9d9b302 100644 --- a/gui/react/src/hooks/useStore.tsx +++ b/gui/react/src/hooks/useStore.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import { StoreAction, StoreContext, StoreState } from "../provider/Store"; +import React from 'react'; +import { StoreAction, StoreContext, StoreState } from '../provider/Store'; const useStore = () => { const context = React.useContext(StoreContext as unknown as React.Context<[StoreState, React.Dispatch>]>); @@ -7,6 +7,6 @@ const useStore = () => { throw new Error('useStore must be used under Store'); } return context; -} +}; export default useStore; \ No newline at end of file diff --git a/gui/react/src/index.tsx b/gui/react/src/index.tsx index d7bc510..c1e1261 100644 --- a/gui/react/src/index.tsx +++ b/gui/react/src/index.tsx @@ -4,8 +4,8 @@ import App from './App'; import ServiceProvider from './provider/ServiceProvider'; import Style from './Style'; import MessageChannel from './provider/MessageChannel'; -import { IconButton } from "@mui/material"; -import { CloseOutlined } from "@mui/icons-material"; +import { IconButton } from '@mui/material'; +import { CloseOutlined } from '@mui/icons-material'; import { SnackbarProvider, SnackbarKey } from 'notistack'; import Store from './provider/Store'; import ErrorHandler from './provider/ErrorHandler'; @@ -18,7 +18,7 @@ const onClickDismiss = (key: SnackbarKey | undefined) => () => { }; const container = document.getElementById('root'); -const root = createRoot(container!); +const root = createRoot(container as HTMLElement); root.render( @@ -29,7 +29,7 @@ root.render( )} - > + >