From 0a3b638c556803700af7f35437b4f15b3f70df8a Mon Sep 17 00:00:00 2001 From: AnimeDL Date: Sat, 6 Apr 2024 21:20:34 -0700 Subject: [PATCH] Start of Removing Funimation --- .github/ISSUE_TEMPLATE/bug.yml | 1 - @types/funiSearch.d.ts | 34 - @types/funiSubtitleRequest.d.ts | 75 -- @types/funiTypes.d.ts | 16 - @types/ws.d.ts | 4 +- docs/README.md | 4 +- funi.ts | 923 ------------------- gui/react/src/components/MenuBar/MenuBar.tsx | 2 - gui/react/src/provider/ServiceProvider.tsx | 3 +- gui/react/src/provider/Store.tsx | 2 +- gui/server/serviceHandler.ts | 7 +- gui/server/services/funimation.ts | 120 --- index.ts | 18 +- modules/build-docs.ts | 7 +- modules/module.app-args.ts | 2 +- modules/module.args.ts | 20 +- modules/module.cfg-loader.ts | 31 - modules/module.downloadArchive.ts | 22 +- modules/module.langsData.ts | 15 +- modules/module.req.ts | 4 +- package.json | 4 +- 21 files changed, 33 insertions(+), 1281 deletions(-) delete mode 100644 @types/funiSearch.d.ts delete mode 100644 @types/funiSubtitleRequest.d.ts delete mode 100644 @types/funiTypes.d.ts delete mode 100644 funi.ts delete mode 100644 gui/server/services/funimation.ts diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 5077179..2301e91 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -47,7 +47,6 @@ body: label: Service description: "Please tell us what service the bug occured in." options: - - Funimation - Crunchyroll - Hidive - All diff --git a/@types/funiSearch.d.ts b/@types/funiSearch.d.ts deleted file mode 100644 index 730985e..0000000 --- a/@types/funiSearch.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Generated by https://quicktype.io - -export interface FunimationSearch { - count: number; - items: Items; - limit: string; - offset: string; -} - -export interface Items { - hits: Hit[]; -} - -export interface Hit { - ratings: string; - description: string; - title: string; - image: { - showThumbnail: string, - [key: string]: string - }; - starRating: number; - slug: string; - languages: string[]; - synopsis: string; - quality: Quality; - id: string; - txDate: number; -} - -export interface Quality { - quality: string; - height: number; -} diff --git a/@types/funiSubtitleRequest.d.ts b/@types/funiSubtitleRequest.d.ts deleted file mode 100644 index e4b6490..0000000 --- a/@types/funiSubtitleRequest.d.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Generated by https://quicktype.io - -export interface SubtitleRequest { - primary: Primary; - fallback: Primary[]; -} - -export interface Primary { - venueVideoId: string; - alphaPackageId: string; - versionContentId: VersionContentID; - manifestPath: string; - fileExt: PrimaryFileEXT; - subtitles: Subtitle[]; - accessType: AccessType; - sessionId: string; - audioLanguage: AudioLanguage; - version: Version; - aips: Aip[]; - drmToken: string; - drmType: string; -} - -export enum AccessType { - Subscription = 'subscription', -} - -export interface Aip { - in: number; - out: number; -} - -export enum AudioLanguage { - En = 'en', - Ja = 'ja', -} - -export enum PrimaryFileEXT { - M3U8 = 'm3u8', - Mp4 = 'mp4', -} - -export interface Subtitle { - filePath: string; - fileExt: SubtitleFileEXT; - contentType: ContentType; - languageCode: LanguageCode; -} - -export enum ContentType { - Cc = 'cc', - Full = 'full', -} - -export enum SubtitleFileEXT { - Dfxp = 'dfxp', - Srt = 'srt', - Vtt = 'vtt', -} - -export enum LanguageCode { - En = 'en', - Es = 'es', - Pt = 'pt', -} - -export enum Version { - Simulcast = 'simulcast', - Uncut = 'uncut', -} - -export enum VersionContentID { - Akusim0012 = 'AKUSIM0012', - Akuunc0012 = 'AKUUNC0012', -} diff --git a/@types/funiTypes.d.ts b/@types/funiTypes.d.ts deleted file mode 100644 index 5880011..0000000 --- a/@types/funiTypes.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { LanguageItem } from '../modules/module.langsData'; - -export type FunimationMediaDownload = { - id: string, - title: string, - showTitle: string, - image: string -} - -export type Subtitle = { - url: string, - lang: LanguageItem, - ext: string, - out?: string, - closedCaption?: boolean -} \ No newline at end of file diff --git a/@types/ws.d.ts b/@types/ws.d.ts index fab66b8..7f9b00c 100644 --- a/@types/ws.d.ts +++ b/@types/ws.d.ts @@ -30,8 +30,8 @@ export type MessageTypes = { 'isDownloading': [undefined, boolean], 'openFolder': [FolderTypes, undefined], 'changeProvider': [undefined, boolean], - 'type': [undefined, 'funi'|'crunchy'|'hidive'|undefined], - 'setup': ['funi'|'crunchy'|'hidive'|undefined, undefined], + 'type': [undefined, 'crunchy'|'hidive'|undefined], + 'setup': ['crunchy'|'hidive'|undefined, undefined], 'openFile': [[FolderTypes, string], undefined], 'openURL': [string, undefined], 'isSetup': [undefined, boolean], diff --git a/docs/README.md b/docs/README.md index dab0de0..8437606 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,11 +2,11 @@ [![Discord Shield](https://discord.com/api/guilds/884479461997805568/widget.png?style=banner2)](https://discord.gg/qEpbWen5vq) -This downloader can download anime from different sites. Currently supported are *Funimation*, *Crunchyroll*, and *Hidive*. +This downloader can download anime from different sites. Currently supported are *Crunchyroll*, and *Hidive*. ## Legal Warning -This application is not endorsed by or affiliated with *Funimation*, *Crunchyroll*, or *Hidive*. This application enables you to download videos for offline viewing which may be forbidden by law in your country. The usage of this application may also cause a violation of the *Terms of Service* between you and the stream provider. This tool is not responsible for your actions; please make an informed decision before using this application. +This application is not endorsed by or affiliated with *Crunchyroll*, or *Hidive*. This application enables you to download videos for offline viewing which may be forbidden by law in your country. The usage of this application may also cause a violation of the *Terms of Service* between you and the stream provider. This tool is not responsible for your actions; please make an informed decision before using this application. ## Dependencies diff --git a/funi.ts b/funi.ts deleted file mode 100644 index 6c3d314..0000000 --- a/funi.ts +++ /dev/null @@ -1,923 +0,0 @@ -// modules build-in -import fs from 'fs'; -import path from 'path'; - -// package json -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 './modules/hls-download'; - -// extra -import * as appYargs from './modules/module.app-args'; -import * as yamlCfg from './modules/module.cfg-loader'; -import vttConvert from './modules/module.vttconvert'; - -// types -import type { Item } from './@types/items.js'; - -// params - -// Import modules after argv has been exported -import getData from './modules/module.getdata'; -import merger from './modules/module.merger'; -import parseSelect from './modules/module.parseSelect'; -import { EpisodeData, MediaChild } from './@types/episode'; -import { Subtitle } from './@types/funiTypes'; -import { StreamData } from './@types/streamData'; -import { DownloadedFile } from './@types/downloadedFile'; -import parseFileName, { Variable } from './modules/module.filename'; -import { downloaded } from './modules/module.downloadArchive'; -import { FunimationMediaDownload } from './@types/funiTypes'; -import * as langsData from './modules/module.langsData'; -import { TitleElement } from './@types/episode'; -import { AvailableFilenameVars } from './modules/module.args'; -import { AuthData, AuthResponse, CheckTokenResponse, FuniGetEpisodeData, FuniGetEpisodeResponse, FuniGetShowData, SearchData, FuniSearchReponse, FuniShowResponse, FuniStreamData, FuniSubsData, FuniEpisodeData, ResponseBase } from './@types/messageHandler'; -import { ServiceClass } from './@types/serviceClassInterface'; -import { SubtitleRequest } from './@types/funiSubtitleRequest'; - -// program name -const api_host = 'https://prod-api-funimationnow.dadcdigital.com/api'; -// check page - -// fn variables -let fnEpNum: string|number = 0, - fnOutput: string[] = [], - season = 0, - tsDlPath: { - path: string, - lang: langsData.LanguageItem - }[] = [], - stDlPath: Subtitle[] = []; - -export default class Funi implements ServiceClass { - public static epIdLen = 4; - public static typeIdLen = 0; - - public cfg: yamlCfg.ConfigObject; - private token: string | boolean; - - constructor(private debug = false) { - this.cfg = yamlCfg.loadCfg(); - this.token = yamlCfg.loadFuniToken(); - } - - public checkToken(): CheckTokenResponse { - const isOk = typeof this.token === 'string'; - return isOk ? { isOk, value: undefined } : { isOk, reason: new Error('Not authenticated') }; - } - - public async cli() : Promise { - const argv = appYargs.appArgv(this.cfg.cli); - if (argv.debug) - this.debug = true; - console.info(`\n=== Multi Downloader NX ${packageJson.version} ===\n`); - if (argv.allDubs) { - argv.dubLang = langsData.dubLanguageCodes; - } - // select mode - if (argv.silentAuth && !argv.auth) { - const data: AuthData = { - username: argv.username ?? await shlp.question('[Q] LOGIN/EMAIL'), - password: argv.password ?? await shlp.question('[Q] PASSWORD ') - }; - await this.auth(data); - } - if(argv.auth){ - const data: AuthData = { - username: argv.username ?? await shlp.question('[Q] LOGIN/EMAIL'), - password: argv.password ?? await shlp.question('[Q] PASSWORD ') - }; - await this.auth(data); - } - else if(argv.search){ - this.searchShow(true, { search: argv.search }); - } - 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.error(`${data.reason.message}`); - return false; - } - let ok = true; - for (const episodeData of data.value) { - if ((await this.getEpisode(true, { subs: { dlsubs: argv.dlsubs, nosubs: argv.nosubs, sub: false, ccTag: argv.ccTag }, dubLang: argv.dubLang, fnSlug: episodeData, s: argv.s, simul: argv.simul }, { - ass: false, - ...argv - })).isOk !== true) - ok = false; - } - return ok; - } - else{ - console.info('No option selected or invalid value entered. Try --help.'); - } - } - public async auth(data: AuthData): Promise { - const authOpts = { - user: data.username, - pass: data.password - }; - const authData = await getData({ - baseUrl: api_host, - url: '/auth/login/', - auth: authOpts, - debug: this.debug, - }); - if(authData.ok && authData.res){ - const resJSON = JSON.parse(authData.res.body); - if(resJSON.token){ - 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.info('[ERROR]%s\n', ' No token found'); - if (this.debug) { - console.info(resJSON); - } - return { isOk: false, reason: new Error(resJSON) }; - } - } - return { isOk: false, reason: new Error('Login request failed') }; - } - - public async searchShow(log: boolean, data: SearchData): Promise { - const qs = {unique: true, limit: 100, q: data.search, offset: 0 }; - const searchData = await getData({ - baseUrl: api_host, - url: '/source/funimation/search/auto/', - querystring: qs, - token: this.token, - useToken: true, - debug: this.debug, - }); - if(!searchData.ok || !searchData.res){ - return { isOk: false, reason: new Error('Request is not ok') }; - } - const searchDataJSON = JSON.parse(searchData.res.body); - if(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.info('Search Results:'); - for(const ssn in shows){ - console.info(`[#${shows[ssn].id}] ${shows[ssn].title}` + (shows[ssn].tx_date?` (${shows[ssn].tx_date})`:'')); - } - } - if (log) - console.info('Total shows found: %s\n',searchDataJSON.count); - return { isOk: true, value: searchDataJSON }; - } - - public async listShowItems(id: number) : Promise> { - const showData = await getData({ - baseUrl: api_host, - url: `/source/catalog/title/${id}`, - token: this.token, - useToken: true, - debug: this.debug, - }); - // check errors - 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.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.error('Show not found\n'); - return { isOk: false, reason: new Error('Show not found') }; - } - const showDataItem = showDataJSON.items[0]; - console.info('[#%s] %s (%s)',showDataItem.id,showDataItem.title,showDataItem.releaseYear); - // show episodes - const qs: { - limit: number, - sort: string, - sort_direction: string, - title_id: number, - language?: string - } = { limit: -1, sort: 'order', sort_direction: 'ASC', title_id: id }; - const episodesData = await getData({ - baseUrl: api_host, - url: '/funimation/episodes/', - querystring: qs, - token: this.token, - useToken: true, - debug: this.debug, - }); - if(!episodesData.ok || !episodesData.res){ return { isOk: false, reason: new Error('episodesData is not ok') }; } - - let epsDataArr: Item[] = JSON.parse(episodesData.res.body).items; - const epNumRegex = /^([A-Z0-9]*[A-Z])?(\d+)$/i; - - const parseEpStr = (epStr: string) => { - const match = epStr.match(epNumRegex); - if (!match) { - console.error('No match found'); - return ['', '']; - } - if(match.length > 2){ - const spliced = [...match].splice(1); - spliced[0] = spliced[0] ? spliced[0] : ''; - return spliced; - } - else return [ '', match[0] ]; - }; - - epsDataArr = epsDataArr.map(e => { - const baseId = e.ids.externalAsianId ? e.ids.externalAsianId : e.ids.externalEpisodeId; - e.id = baseId.replace(new RegExp('^' + e.ids.externalShowId), ''); - if(e.id.match(epNumRegex)){ - const epMatch = parseEpStr(e.id); - Funi.epIdLen = epMatch[1].length > Funi.epIdLen ? epMatch[1].length : Funi.epIdLen; - Funi.typeIdLen = epMatch[0].length > Funi.typeIdLen ? epMatch[0].length : Funi.typeIdLen; - e.id_split = epMatch; - } - else{ - Funi.typeIdLen = 3 > Funi.typeIdLen? 3 : Funi.typeIdLen; - console.error('FAILED TO PARSE: ', e.id); - e.id_split = [ 'ZZZ', 9999 ]; - } - return e; - }); - - epsDataArr.sort((a, b) => { - if (a.item.seasonOrder < b.item.seasonOrder && a.id.localeCompare(b.id) < 0) { - return -1; - } - if (a.item.seasonOrder > b.item.seasonOrder && a.id.localeCompare(b.id) > 0) { - return 1; - } - return 0; - }); - - return { isOk: true, value: epsDataArr }; - } - - public async getShow(log: boolean, data: FuniGetShowData) : Promise { - const showList = await this.listShowItems(data.id); - if (!showList.isOk) - return showList; - const eps = showList.value; - const epSelList = parseSelect(data.e as string, data.but); - const fnSlug: FuniEpisodeData[] = [], epSelEpsTxt: string[] = []; let is_selected = false; - - - for(const e in eps){ - eps[e].id_split[1] = parseInt(eps[e].id_split[1].toString()).toString().padStart(Funi.epIdLen, '0'); - let epStrId = eps[e].id_split.join(''); - // select - is_selected = false; - if (data.all || epSelList.isSelected(epStrId)) { - fnSlug.push({ - title:eps[e].item.titleSlug, - episode:eps[e].item.episodeSlug, - episodeID:epStrId, - epsiodeNumber: eps[e].item.episodeNum, - seasonTitle: eps[e].item.seasonTitle, - seasonNumber: eps[e].item.seasonNum, - ids: { - episode: eps[e].ids.externalEpisodeId, - season: eps[e].ids.externalSeasonId, - show: eps[e].ids.externalShowId - }, - image: eps[e].item.poster - }); - epSelEpsTxt.push(epStrId); - is_selected = true; - } - // console vars - const tx_snum = eps[e].item.seasonNum=='1'?'':` S${eps[e].item.seasonNum}`; - const tx_type = eps[e].mediaCategory != 'episode' ? eps[e].mediaCategory : ''; - const tx_enum = eps[e].item.episodeNum && eps[e].item.episodeNum !== '' ? - `#${(parseInt(eps[e].item.episodeNum) < 10 ? '0' : '')+eps[e].item.episodeNum}` : '#'+eps[e].item.episodeId; - const qua_str = eps[e].quality.height ? eps[e].quality.quality + eps[e].quality.height : 'UNK'; - const aud_str = eps[e].audio.length > 0 ? `, ${eps[e].audio.join(', ')}` : ''; - const rtm_str = eps[e].item.runtime !== '' ? eps[e].item.runtime : '??:??'; - // console string - eps[e].id_split[0] = eps[e].id_split[0].toString().padStart(Funi.typeIdLen, ' '); - epStrId = eps[e].id_split.join(''); - let conOut = `[${epStrId}] `; - conOut += `${eps[e].item.titleName+tx_snum} - ${tx_type+tx_enum} ${eps[e].item.episodeName} `; - conOut += `(${rtm_str}) [${qua_str+aud_str}]`; - conOut += is_selected ? ' (selected)' : ''; - conOut += eps.length-1 == parseInt(e) ? '\n' : ''; - console.info(conOut); - } - if(fnSlug.length < 1){ - if (log) - console.info('Episodes not selected!\n'); - return { isOk: true, value: [] } ; - } - else{ - if (log) - console.info('Selected Episodes: %s\n',epSelEpsTxt.join(', ')); - return { isOk: true, value: fnSlug }; - } - } - - public async getEpisode(log: boolean, data: FuniGetEpisodeData, downloadData: FuniStreamData) : Promise { - const episodeData = await getData({ - baseUrl: api_host, - url: `/source/catalog/episode/${data.fnSlug.title}/${data.fnSlug.episode}/`, - token: this.token, - useToken: true, - debug: this.debug, - }); - if(!episodeData.ok || !episodeData.res){return { isOk: false, reason: new Error('Unable to get episodeData') }; } - const ep = JSON.parse(episodeData.res.body).items[0] as EpisodeData, streamIds: { id: number, lang: langsData.LanguageItem }[] = []; - // build fn - season = parseInt(ep.parent.seasonNumber); - if(ep.mediaCategory != 'Episode'){ - ep.number = ep.number !== '' ? ep.mediaCategory+ep.number : ep.mediaCategory+'#'+ep.id; - } - fnEpNum = isNaN(parseInt(ep.number)) ? ep.number : parseInt(ep.number); - - // is uncut - const uncut = { - Japanese: false, - English: false - }; - - // end - if (log) { - console.info( - '%s - S%sE%s - %s', - ep.parent.title, - (ep.parent.seasonNumber ? ep.parent.seasonNumber : '?'), - (ep.number ? ep.number : '?'), - ep.title - ); - - console.info('Available streams (Non-Encrypted):'); - } - // map medias - const media = await Promise.all(ep.media.map(async (m) =>{ - if(m.mediaType == 'experience'){ - if(m.version.match(/uncut/i) && m.language){ - uncut[m.language] = true; - } - return { - id: m.id, - language: m.language, - version: m.version, - type: m.experienceType, - subtitles: await this.getSubsUrl(m.mediaChildren, m.language, data.subs, ep.ids.externalEpisodeId, data.subs.ccTag) - }; - } - else{ - return { id: 0, type: '' }; - } - })); - - // select - stDlPath = []; - for(const m of media){ - let selected = false; - if(m.id > 0 && m.type == 'Non-Encrypted'){ - const dub_type = m.language; - if (!dub_type) - continue; - let localSubs: Subtitle[] = []; - const selUncut = !data.simul && uncut[dub_type] && m.version?.match(/uncut/i) - ? true - : (!uncut[dub_type] || data.simul && m.version?.match(/simulcast/i) ? true : false); - for (const curDub of data.dubLang) { - const item = langsData.languages.find(a => a.code === curDub); - if(item && (dub_type === item.funi_name_lagacy || dub_type === (item.funi_name ?? item.name)) && selUncut){ - streamIds.push({ - id: m.id, - lang: item - }); - stDlPath.push(...m.subtitles); - localSubs = m.subtitles; - selected = true; - } - } - if (log) { - const subsToDisplay: langsData.LanguageItem[] = []; - localSubs.forEach(a => { - if (!subsToDisplay.includes(a.lang)) - subsToDisplay.push(a.lang); - }); - console.info(`[#${m.id}] ${dub_type} [${m.version}]${(selected?' (selected)':'')}${ - localSubs && localSubs.length > 0 && selected ? ` (using ${subsToDisplay.map(a => `'${a.name}'`).join(', ')} for subtitles)` : '' - }`); - } - } - } - - const already: string[] = []; - stDlPath = stDlPath.filter(a => { - if (already.includes(`${a.closedCaption ? 'cc' : ''}-${a.lang.code}`)) { - return false; - } else { - already.push(`${a.closedCaption ? 'cc' : ''}-${a.lang.code}`); - return true; - } - }); - if(streamIds.length < 1){ - if (log) - console.error('Track not selected\n'); - return { isOk: false, reason: new Error('Track not selected') }; - } - else{ - tsDlPath = []; - for (const streamId of streamIds) { - const streamData = await getData({ - baseUrl: api_host, - url: `/source/catalog/video/${streamId.id}/signed`, - token: this.token, - dinstid: 'uuid', - useToken: true, - debug: this.debug, - }); - if(!streamData.ok || !streamData.res){return { isOk: false, reason: new Error('Unable to get streamdata') };} - const streamDataRes = JSON.parse(streamData.res.body) as StreamData; - if(streamDataRes.errors){ - if (log) - 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{ - for(const u in streamDataRes.items){ - if(streamDataRes.items[u].videoType == 'm3u8'){ - tsDlPath.push({ - path: streamDataRes.items[u].src, - lang: streamId.lang - }); - break; - } - } - } - } - if(tsDlPath.length < 1){ - if (log) - console.error('Unknown error\n'); - return { isOk: false, reason: new Error('Unknown error') }; - } - else{ - const res = await this.downloadStreams(true, { - id: data.fnSlug.episodeID, - title: ep.title, - showTitle: ep.parent.title, - image: ep.thumb - }, downloadData); - if (res === true) { - downloaded({ - service: 'funi', - type: 's' - }, data.s, [data.fnSlug.episodeID]); - return { isOk: res, value: undefined }; - } - return { isOk: false, reason: new Error('Unknown download error') }; - } - } - } - - public async downloadStreams(log: boolean, episode: FunimationMediaDownload, data: FuniStreamData): Promise { - - // req playlist - - const purvideo: DownloadedFile[] = []; - const puraudio: DownloadedFile[] = []; - const audioAndVideo: DownloadedFile[] = []; - for (const streamPath of tsDlPath) { - const plQualityReq = await getData({ - url: streamPath.path, - debug: this.debug, - }); - if(!plQualityReq.ok || !plQualityReq.res){return;} - - const plQualityLinkList = m3u8(plQualityReq.res.body); - - const mainServersList = [ - 'vmfst-api.prd.funimationsvc.com', - 'd33et77evd9bgg.cloudfront.net', - 'd132fumi6di1wa.cloudfront.net', - 'funiprod.akamaized.net', - ]; - - const plServerList: string[] = [], - plStreams: Record = {}, - plLayersStr: string[] = [], - plLayersRes: Record = {}; - let plMaxLayer = 1, - plNewIds = 1, - plAud: undefined|{ - uri: string - language: langsData.LanguageItem - }; - - // new uris - const vplReg = /streaming_video_(\d+)_(\d+)_(\d+)_index\.m3u8/; - if(plQualityLinkList.playlists[0].uri.match(vplReg)){ - const audioKey = Object.keys(plQualityLinkList.mediaGroups.AUDIO).pop(); - if (!audioKey) - return console.error('No audio key found'); - if(plQualityLinkList.mediaGroups.AUDIO[audioKey]){ - const audioDataParts = plQualityLinkList.mediaGroups.AUDIO[audioKey], - audioEl = Object.keys(audioDataParts); - const audioData = audioDataParts[audioEl[0]]; - let language = langsData.languages.find(a => a.code === audioData.language || a.locale === audioData.language); - if (!language) { - language = langsData.languages.find(a => a.funi_name_lagacy === audioEl[0] || ((a.funi_name ?? a.name) === audioEl[0])); - if (!language) { - if (log) - console.error(`Unable to find language for locale ${audioData.language} or name ${audioEl[0]}`); - return; - } - } - plAud = { - uri: audioData.uri, - language: language - }; - } - plQualityLinkList.playlists.sort((a, b) => { - const aMatch = a.uri.match(vplReg), bMatch = b.uri.match(vplReg); - if (!aMatch || !bMatch) { - console.info('Unable to match'); - return 0; - } - const av = parseInt(aMatch[3]); - const bv = parseInt(bMatch[3]); - if(av > bv){ - return 1; - } - if (av < bv) { - return -1; - } - return 0; - }); - } - - for(const s of plQualityLinkList.playlists){ - if(s.uri.match(/_Layer(\d+)\.m3u8/) || s.uri.match(vplReg)){ - // set layer and max layer - let plLayerId: number|string = 0; - const match = s.uri.match(/_Layer(\d+)\.m3u8/); - if(match){ - plLayerId = parseInt(match[1]); - } - else{ - plLayerId = plNewIds, plNewIds++; - } - plMaxLayer = plMaxLayer < plLayerId ? plLayerId : plMaxLayer; - // set urls and servers - const plUrlDl = s.uri; - const plServer = new URL(plUrlDl).host; - if(!plServerList.includes(plServer)){ - plServerList.push(plServer); - } - if(!Object.keys(plStreams).includes(plServer)){ - plStreams[plServer] = {}; - } - if(plStreams[plServer][plLayerId] && plStreams[plServer][plLayerId] != plUrlDl){ - console.warn(`Non duplicate url for ${plServer} detected, please report to developer!`); - } - else{ - plStreams[plServer][plLayerId] = plUrlDl; - } - // set plLayersStr - const plResolution = s.attributes.RESOLUTION; - plLayersRes[plLayerId] = plResolution; - const plBandwidth = Math.round(s.attributes.BANDWIDTH/1024); - if(plLayerId<10){ - plLayerId = plLayerId.toString().padStart(2,' '); - } - const qualityStrAdd = `${plLayerId}: ${plResolution.width}x${plResolution.height} (${plBandwidth}KiB/s)`; - const qualityStrRegx = new RegExp(qualityStrAdd.replace(/(:|\(|\)|\/)/g,'\\$1'),'m'); - const qualityStrMatch = !plLayersStr.join('\r\n').match(qualityStrRegx); - if(qualityStrMatch){ - plLayersStr.push(qualityStrAdd); - } - } - else { - console.info(s.uri); - } - } - - for(const s of mainServersList){ - if(plServerList.includes(s)){ - plServerList.splice(plServerList.indexOf(s), 1); - plServerList.unshift(s); - break; - } - } - - - const plSelectedServer = plServerList[data.x-1]; - const plSelectedList = plStreams[plSelectedServer]; - - plLayersStr.sort(); - if (log) { - 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 - ? Object.keys(plLayersRes).pop() as string - : data.q; - const videoUrl = data.x < plServerList.length+1 && plSelectedList[selectedQuality] ? plSelectedList[selectedQuality] : ''; - - if(videoUrl != ''){ - if (log) { - console.info(`Selected layer: ${selectedQuality} (${plLayersRes[selectedQuality].width}x${plLayersRes[selectedQuality].height}) @ ${plSelectedServer}`); - console.info('Stream URL:',videoUrl); - } - - fnOutput = parseFileName(data.fileName, ([ - ['episode', isNaN(parseInt(fnEpNum as string)) ? fnEpNum : parseInt(fnEpNum as string), true], - ['title', episode.title, true], - ['showTitle', episode.showTitle, true], - ['season', season, false], - ['width', plLayersRes[selectedQuality].width, false], - ['height', plLayersRes[selectedQuality].height, false], - ['service', 'Funimation', false] - ] as [AvailableFilenameVars, string|number, boolean][]).map((a): Variable => { - return { - name: a[0], - replaceWith: a[1], - type: typeof a[1], - sanitize: a[2] - } as Variable; - }), data.numbers, data.override); - if (fnOutput.length < 1) - throw new Error(`Invalid path generated for input ${data.fileName}`); - if (log) - console.info(`Output filename: ${fnOutput.join(path.sep)}.ts`); - } - else if(data.x > plServerList.length){ - if (log) - console.error('Server not selected!\n'); - return; - } - else{ - if (log) - console.error('Layer not selected!\n'); - return; - } - - let dlFailed = false; - let dlFailedA = false; - - await fs.promises.mkdir(path.join(this.cfg.dir.content, ...fnOutput.slice(0, -1)), { recursive: true }); - - video: if (!data.novids) { - if (plAud && (purvideo.length > 0 || audioAndVideo.length > 0)) { - break video; - } else if (!plAud && (audioAndVideo.some(a => a.lang === streamPath.lang) || puraudio.some(a => a.lang === streamPath.lang))) { - break video; - } - // download video - const reqVideo = await getData({ - url: videoUrl, - debug: this.debug, - }); - if (!reqVideo.ok || !reqVideo.res) { break video; } - - const chunkList = m3u8(reqVideo.res.body); - - const tsFile = path.join(this.cfg.dir.content, ...fnOutput.slice(0, -1), `${fnOutput.slice(-1)}.video${(plAud?.uri ? '' : '.' + streamPath.lang.code )}`); - dlFailed = !await this.downloadFile(tsFile, chunkList, data.timeout, data.partsize, data.fsRetryTime, data.force, data.callbackMaker ? data.callbackMaker({ - fileName: `${fnOutput.slice(-1)}.video${(plAud?.uri ? '' : '.' + streamPath.lang.code )}.ts`, - parent: { - title: episode.showTitle - }, - title: episode.title, - image: episode.image, - language: streamPath.lang, - }) : undefined); - if (!dlFailed) { - if (plAud) { - purvideo.push({ - path: `${tsFile}.ts`, - lang: plAud.language - }); - } else { - audioAndVideo.push({ - path: `${tsFile}.ts`, - lang: streamPath.lang - }); - } - } - } - else{ - if (log) - console.info('Skip video downloading...\n'); - } - audio: if (plAud && !data.noaudio) { - // download audio - if (audioAndVideo.some(a => a.lang === plAud?.language) || puraudio.some(a => a.lang === plAud?.language)) - break audio; - const reqAudio = await getData({ - url: plAud.uri, - debug: this.debug, - }); - if (!reqAudio.ok || !reqAudio.res) { return; } - - const chunkListA = m3u8(reqAudio.res.body); - - const tsFileA = path.join(this.cfg.dir.content, ...fnOutput.slice(0, -1), `${fnOutput.slice(-1)}.audio.${plAud.language.code}`); - - dlFailedA = !await this.downloadFile(tsFileA, chunkListA, data.timeout, data.partsize, data.fsRetryTime, data.force, data.callbackMaker ? data.callbackMaker({ - fileName: `${fnOutput.slice(-1)}.audio.${plAud.language.code}.ts`, - parent: { - title: episode.showTitle - }, - title: episode.title, - image: episode.image, - language: plAud.language - }) : undefined); - if (!dlFailedA) - puraudio.push({ - path: `${tsFileA}.ts`, - lang: plAud.language - }); - - } - } - - // add subs - const subsExt = !data.mp4 || data.mp4 && data.ass ? '.ass' : '.srt'; - let addSubs = true; - - // download subtitles - if(stDlPath.length > 0){ - if (log) - console.info('Downloading subtitles...'); - for (const subObject of stDlPath) { - const subsSrc = await getData({ - url: subObject.url, - debug: this.debug, - }); - if(subsSrc.ok && subsSrc.res){ - const assData = vttConvert(subsSrc.res.body, (subsExt == '.srt' ? true : false), subObject.lang.name, data.fontSize, data.fontName); - subObject.out = path.join(this.cfg.dir.content, ...fnOutput.slice(0, -1), `${fnOutput.slice(-1)}.subtitle${subObject.ext}${subsExt}`); - fs.writeFileSync(subObject.out, assData); - } - else{ - if (log) - console.error('Failed to download subtitles!'); - addSubs = false; - break; - } - } - if (addSubs && log) - console.info('Subtitles downloaded!'); - } - - if((puraudio.length < 1 && audioAndVideo.length < 1) || (purvideo.length < 1 && audioAndVideo.length < 1)){ - if (log) - console.info('\nUnable to locate a video AND audio file\n'); - return; - } - - if(data.skipmux){ - if (log) - console.info('Skipping muxing...'); - return; - } - - // check exec - this.cfg.bin = await yamlCfg.loadBinCfg(); - const mergerBin = merger.checkMerger(this.cfg.bin, data.mp4, data.forceMuxer); - - if ( data.novids ){ - if (log) - console.info('Video not downloaded. Skip muxing video.'); - } - - const ffext = !data.mp4 ? 'mkv' : 'mp4'; - const mergeInstance = new merger({ - onlyAudio: puraudio, - onlyVid: purvideo, - output: `${path.join(this.cfg.dir.content, ...fnOutput)}.${ffext}`, - subtitles: stDlPath.map(a => { - return { - file: a.out as string, - language: a.lang, - title: a.lang.name, - closedCaption: a.closedCaption - }; - }), - videoAndAudio: audioAndVideo, - simul: data.simul, - skipSubMux: data.skipSubMux, - videoTitle: data.videoTitle, - options: { - ffmpeg: data.ffmpegOptions, - mkvmerge: data.mkvmergeOptions - }, - defaults: { - audio: data.defaultAudio, - sub: data.defaultSub - }, - ccTag: data.ccTag - }); - - if(mergerBin.MKVmerge){ - await mergeInstance.merge('mkvmerge', mergerBin.MKVmerge); - } - else if(mergerBin.FFmpeg){ - await mergeInstance.merge('ffmpeg', mergerBin.FFmpeg); - } - else{ - if (log) - console.info('\nDone!\n'); - return true; - } - if (data.nocleanup) { - return true; - } - - mergeInstance.cleanUp(); - if (log) - console.info('\nDone!\n'); - return true; - } - - public async downloadFile(filename: string, chunkList: { - segments: Record[], - }, timeout: number, partsize: number, fsRetryTime: number, override?: 'Y' | 'y' | 'N' | 'n' | 'C' | 'c', callback?: HLSCallback) { - const downloadStatus = await new hlsDownload({ - m3u8json: chunkList, - output: `${filename + '.ts'}`, - timeout: timeout, - threads: partsize, - fsRetryTime: fsRetryTime * 1000, - override, - callback - }).download(); - - return downloadStatus.ok; - } - - public async getSubsUrl(m: MediaChild[], parentLanguage: TitleElement|undefined, data: FuniSubsData, episodeID: string, ccTag: string) : Promise { - if((data.nosubs && !data.sub) || data.dlsubs.includes('none')){ - return []; - } - - const subs = await getData({ - baseUrl: 'https://playback.prd.funimationsvc.com/v1/play', - url: `/${episodeID}`, - token: this.token, - useToken: true, - debug: this.debug, - querystring: { deviceType: 'web' } - }); - if (!subs.ok || !subs.res || !subs.res.body) { - console.error('Subtitle Request failed.'); - return []; - } - const parsed: SubtitleRequest = JSON.parse(subs.res.body); - - const found: { - isCC: boolean; - url: string; - lang: langsData.LanguageItem; - }[] = parsed.primary.subtitles.filter(a => a.fileExt === 'vtt').map(subtitle => { - return { - isCC: subtitle.contentType === 'cc', - url: subtitle.filePath, - lang: langsData.languages.find(a => a.funi_locale === subtitle.languageCode || a.locale === subtitle.languageCode) - }; - }).concat(m.filter(a => a.filePath.split('.').pop() === 'vtt').map(media => { - const lang = langsData.languages.find(a => media.language === a.funi_name_lagacy || media.language === (a.funi_name || a.name)); - const pLang = langsData.languages.find(a => parentLanguage === a.funi_name_lagacy || (a.funi_name || a.name) === parentLanguage); - return { - isCC: pLang?.code === lang?.code, - url: media.filePath, - lang - }; - })).filter((a) => a.lang !== undefined) as { - isCC: boolean; - url: string; - lang: langsData.LanguageItem; - }[]; - - const ret = found.filter(item => { - return data.dlsubs.includes('all') || data.dlsubs.some(a => a === item.lang.locale); - }); - - return ret.map(a => ({ - ext: `.${a.lang.code}${a.isCC ? `.${ccTag}` : ''}`, - lang: a.lang, - url: a.url, - closedCaption: a.isCC - })); - } - -} diff --git a/gui/react/src/components/MenuBar/MenuBar.tsx b/gui/react/src/components/MenuBar/MenuBar.tsx index 23f5bb6..4edb3ac 100644 --- a/gui/react/src/components/MenuBar/MenuBar.tsx +++ b/gui/react/src/components/MenuBar/MenuBar.tsx @@ -23,8 +23,6 @@ const MenuBar: React.FC = () => { switch(service) { case 'crunchy': return 'Crunchyroll'; - case 'funi': - return 'Funimation'; case 'hidive': return 'Hidive'; } diff --git a/gui/react/src/provider/ServiceProvider.tsx b/gui/react/src/provider/ServiceProvider.tsx index eb770c5..9013407 100644 --- a/gui/react/src/provider/ServiceProvider.tsx +++ b/gui/react/src/provider/ServiceProvider.tsx @@ -3,7 +3,7 @@ import {Divider, Box, Button, Typography, Avatar} from '@mui/material'; import useStore from '../hooks/useStore'; import { StoreState } from './Store'; -type Services = 'funi'|'crunchy'|'hidive'; +type Services = 'crunchy'|'hidive'; export const serviceContext = React.createContext(undefined); @@ -21,7 +21,6 @@ const ServiceProvider: FCWithChildren = ({ children }) => { Please select your service - diff --git a/gui/react/src/provider/Store.tsx b/gui/react/src/provider/Store.tsx index c82ef92..eecb51c 100644 --- a/gui/react/src/provider/Store.tsx +++ b/gui/react/src/provider/Store.tsx @@ -21,7 +21,7 @@ export type DownloadOptions = { export type StoreState = { episodeListing: Episode[]; downloadOptions: DownloadOptions, - service: 'crunchy'|'funi'|'hidive'|undefined, + service: 'crunchy'|'hidive'|undefined, version: string, } diff --git a/gui/server/serviceHandler.ts b/gui/server/serviceHandler.ts index 9e8e365..87cdb92 100644 --- a/gui/server/serviceHandler.ts +++ b/gui/server/serviceHandler.ts @@ -4,7 +4,6 @@ import { IncomingMessage } from 'http'; import { MessageHandler, GuiState } from '../../@types/messageHandler'; import { setState, getState, writeYamlCfgFile } from '../../modules/module.cfg-loader'; import CrunchyHandler from './services/crunchyroll'; -import FunimationHandler from './services/funimation'; import HidiveHandler from './services/hidive'; import WebSocketHandler from './websocket'; import packageJson from '../../package.json'; @@ -31,9 +30,7 @@ export default class ServiceHandler { }); this.ws.events.on('setup', ({ data }) => { - if (data === 'funi') { - this.service = new FunimationHandler(this.ws); - } else if (data === 'crunchy') { + if (data === 'crunchy') { this.service = new CrunchyHandler(this.ws); } else if (data === 'hidive') { this.service = new HidiveHandler(this.ws); @@ -55,7 +52,7 @@ export default class ServiceHandler { this.ws.events.on('version', async (_, respond) => { respond(packageJson.version); }); - this.ws.events.on('type', async (_, respond) => respond(this.service === undefined ? undefined : this.service.name as 'hidive'|'crunchy'|'funi')); + this.ws.events.on('type', async (_, respond) => respond(this.service === undefined ? undefined : this.service.name as 'hidive'|'crunchy')); this.ws.events.on('checkToken', async (_, respond) => { if (this.service === undefined) return respond({ isOk: false, reason: new Error('No service selected') }); diff --git a/gui/server/services/funimation.ts b/gui/server/services/funimation.ts deleted file mode 100644 index 3bfc4eb..0000000 --- a/gui/server/services/funimation.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { AuthData, CheckTokenResponse, EpisodeListResponse, MessageHandler, QueueItem, ResolveItemsData, SearchData, SearchResponse } from '../../../@types/messageHandler'; -import Funimation from '../../../funi'; -import { getDefault } from '../../../modules/module.args'; -import { languages, subtitleLanguagesFilter } from '../../../modules/module.langsData'; -import WebSocketHandler from '../websocket'; -import Base from './base'; -import { console } from '../../../modules/log'; -import * as yargs from '../../../modules/module.app-args'; - -class FunimationHandler extends Base implements MessageHandler { - private funi: Funimation; - public name = 'funi'; - constructor(ws: WebSocketHandler) { - super(ws); - this.funi = new Funimation(); - this.initState(); - } - - public async listEpisodes (id: string) : Promise { - const parse = parseInt(id); - if (isNaN(parse) || parse <= 0) - return { isOk: false, reason: new Error('The ID is invalid') }; - const request = await this.funi.listShowItems(parse); - if (!request.isOk) - return request; - return { isOk: true, value: request.value.map(item => ({ - e: item.id_split.join(''), - lang: item.audio ?? [], - name: item.title, - season: item.seasonNum ?? item.seasonTitle ?? item.item.seasonNum ?? item.item.seasonTitle, - seasonTitle: item.seasonTitle, - episode: item.episodeNum, - id: item.id, - img: item.thumb, - description: item.synopsis, - time: item.runtime ?? item.item.runtime - })) }; - } - - public async handleDefault(name: string) { - return getDefault(name, this.funi.cfg.cli); - } - - public async availableDubCodes(): Promise { - const dubLanguageCodesArray: string[] = []; - for(const language of languages){ - if (language.funi_locale) - dubLanguageCodesArray.push(language.code); - } - return [...new Set(dubLanguageCodesArray)]; - } - - public async availableSubCodes(): Promise { - return subtitleLanguagesFilter; - } - - public async resolveItems(data: ResolveItemsData): Promise { - console.debug(`Got resolve options: ${JSON.stringify(data)}`); - const res = await this.funi.getShow(false, { ...data, id: parseInt(data.id) }); - if (!res.isOk) - return res.isOk; - this.addToQueue(res.value.map(a => { - return { - ...data, - ids: [a.episodeID], - title: a.title, - parent: { - title: a.seasonTitle, - season: a.seasonNumber - }, - image: a.image, - e: a.episodeID, - episode: a.epsiodeNumber - }; - })); - return true; - } - - public async search(data: SearchData): Promise { - console.debug(`Got search options: ${JSON.stringify(data)}`); - const funiSearch = await this.funi.searchShow(false, data); - if (!funiSearch.isOk) - return funiSearch; - return { isOk: true, value: funiSearch.value.items.hits.map(a => ({ - image: a.image.showThumbnail, - name: a.title, - desc: a.description, - id: a.id, - lang: a.languages, - rating: a.starRating - })) }; - } - - public async checkToken(): Promise { - return this.funi.checkToken(); - } - - public auth(data: AuthData) { - return this.funi.auth(data); - } - - public async downloadItem(data: QueueItem) { - this.setDownloading(true); - 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 _default = yargs.appArgv(this.funi.cfg.cli, true); - if (!res.isOk) - return this.alertError(res.reason); - - for (const ep of res.value) { - await this.funi.getEpisode(false, { dubLang: data.dubLang, fnSlug: ep, s: data.id, subs: { dlsubs: data.dlsubs, sub: false, ccTag: _default.ccTag } }, { ..._default, callbackMaker: this.makeProgressHandler.bind(this), ass: true, fileName: data.fileName, q: data.q, force: 'y', - noaudio: data.noaudio, novids: data.novids }); - } - this.sendMessage({ name: 'finish', data: undefined }); - this.setDownloading(false); - this.onFinish(); - } -} - -export default FunimationHandler; \ No newline at end of file diff --git a/index.ts b/index.ts index 2d6b91f..5b422c8 100644 --- a/index.ts +++ b/index.ts @@ -18,15 +18,7 @@ import update from './modules/module.updater'; } if (argv.addArchive) { - if (argv.service === 'funi') { - if (argv.s === undefined) - return console.error('`-s` not found'); - addToArchive({ - service: 'funi', - type: 's' - }, argv.s); - console.info('Added %s to the downloadArchive list', argv.s); - } else if (argv.service === 'crunchy') { + if (argv.service === 'crunchy') { if (argv.s === undefined && argv.series === undefined) return console.error('`-s` or `--srz` not found'); if (argv.s && argv.series) @@ -52,14 +44,11 @@ import update from './modules/module.updater'; overrideArguments(cfg.cli, id); /* Reimport module to override appArgv */ Object.keys(require.cache).forEach(key => { - if (key.endsWith('crunchy.js') || key.endsWith('funi.js') || key.endsWith('hidive.js')) + if (key.endsWith('crunchy.js') || key.endsWith('hidive.js')) delete require.cache[key]; }); let service: ServiceClass; switch(argv.service) { - case 'funi': - service = new (await import('./funi')).default; - break; case 'crunchy': service = new (await import('./crunchy')).default; break; @@ -75,9 +64,6 @@ import update from './modules/module.updater'; } else { let service: ServiceClass; switch(argv.service) { - case 'funi': - service = new (await import('./funi')).default; - break; case 'crunchy': service = new (await import('./crunchy')).default; break; diff --git a/modules/build-docs.ts b/modules/build-docs.ts index 5b8781b..94fe814 100644 --- a/modules/build-docs.ts +++ b/modules/build-docs.ts @@ -3,13 +3,10 @@ import fs from 'fs'; import path from 'path'; import { args, groups } from './module.args'; -const transformService = (str: Array<'funi'|'crunchy'|'hidive'|'all'>) => { +const transformService = (str: Array<'crunchy'|'hidive'|'all'>) => { const services: string[] = []; str.forEach(function(part) { switch(part) { - case 'funi': - services.push('Funimation'); - break; case 'crunchy': services.push('Crunchyroll'); break; @@ -30,7 +27,7 @@ If you find any bugs in this documentation or in the program itself please repor ## Legal Warning -This application is not endorsed by or affiliated with *Funimation*, *Hidive*, or *Crunchyroll*. +This application is not endorsed by or affiliated with *Crunchyroll* or *Hidive*. This application enables you to download videos for offline viewing which may be forbidden by law in your country. The usage of this application may also cause a violation of the *Terms of Service* between you and the stream provider. This tool is not responsible for your actions; please make an informed decision before using this application. diff --git a/modules/module.app-args.ts b/modules/module.app-args.ts index 00014c8..82cccda 100644 --- a/modules/module.app-args.ts +++ b/modules/module.app-args.ts @@ -61,7 +61,7 @@ let argvC: { debug: boolean | undefined; nocleanup: boolean; help: boolean | undefined; - service: 'funi' | 'crunchy' | 'hidive'; + service: 'crunchy' | 'hidive'; update: boolean; fontName: string | undefined; _: (string | number)[]; diff --git a/modules/module.args.ts b/modules/module.args.ts index 0507576..97f38b4 100644 --- a/modules/module.args.ts +++ b/modules/module.args.ts @@ -41,7 +41,7 @@ type TAppArg = { default: T|undefined, name?: string }, - service: Array<'funi'|'crunchy'|'hidive'|'all'>, + service: Array<'crunchy'|'hidive'|'all'>, usage: string // -(-)${name} will be added for each command, demandOption?: true, transformer?: (value: T) => K @@ -280,7 +280,7 @@ const args: TAppArg[] = [ type: 'number', alias: 'server', docDescribe: true, - service: ['crunchy','funi'], + service: ['crunchy'], usage: '${server}' }, { @@ -314,8 +314,7 @@ const args: TAppArg[] = [ name: 'dlsubs', group: 'dl', describe: 'Download subtitles by language tag (space-separated)' - + `\nFuni Only: ${languages.filter(a => a.funi_locale && !a.cr_locale).map(a => a.locale).join(', ')}` - + `\nCrunchy Only: ${languages.filter(a => a.cr_locale && !a.funi_locale).map(a => a.locale).join(', ')}`, + + `\nCrunchy Only: ${languages.filter(a => a.cr_locale).map(a => a.locale).join(', ')}`, docDescribe: true, service: ['all'], type: 'array', @@ -339,7 +338,7 @@ const args: TAppArg[] = [ group: 'dl', describe: 'Skip downloading audio', docDescribe: true, - service: ['funi'], + service: ['crunchy', 'hidive'], type: 'boolean', usage: '' }, @@ -355,8 +354,7 @@ const args: TAppArg[] = [ { name: 'dubLang', describe: 'Set the language to download: ' - + `\nFuni Only: ${languages.filter(a => a.funi_locale && !a.cr_locale).map(a => a.code).join(', ')}` - + `\nCrunchy Only: ${languages.filter(a => a.cr_locale && !a.funi_locale).map(a => a.code).join(', ')}`, + + `\nCrunchy Only: ${languages.filter(a => a.cr_locale).map(a => a.code).join(', ')}`, docDescribe: true, group: 'dl', choices: dubLanguageCodes, @@ -439,7 +437,7 @@ const args: TAppArg[] = [ group: 'dl', describe: 'Force downloading simulcast version instead of uncut version (if available).', docDescribe: true, - service: ['funi', 'hidive'], + service: ['hidive'], type: 'boolean', usage: '', default: { @@ -572,7 +570,7 @@ const args: TAppArg[] = [ group: 'util', service: ['all'], type: 'string', - choices: ['funi', 'crunchy', 'hidive'], + choices: ['crunchy', 'hidive'], usage: '${service}', default: { default: '' @@ -593,7 +591,7 @@ const args: TAppArg[] = [ group: 'fonts', describe: 'Set the font to use in subtiles', docDescribe: true, - service: ['funi', 'hidive'], + service: ['hidive'], type: 'string', usage: '${fontName}', }, @@ -677,7 +675,7 @@ const args: TAppArg[] = [ describe: 'Authenticate every time the script runs. Use at your own risk.', docDescribe: true, group: 'auth', - service: ['funi','crunchy'], + service: ['crunchy'], type: 'boolean', usage: '', default: { diff --git a/modules/module.cfg-loader.ts b/modules/module.cfg-loader.ts index 6f39d8c..49196d5 100644 --- a/modules/module.cfg-loader.ts +++ b/modules/module.cfg-loader.ts @@ -18,13 +18,11 @@ const guiCfgFile = path.join(workingDir, 'config', 'gui'); const cliCfgFile = path.join(workingDir, 'config', 'cli-defaults'); const hdPflCfgFile = path.join(workingDir, 'config', 'hd_profile'); const sessCfgFile = { - funi: path.join(workingDir, 'config', 'funi_sess'), cr: path.join(workingDir, 'config', 'cr_sess'), hd: path.join(workingDir, 'config', 'hd_sess') }; const stateFile = path.join(workingDir, 'config', 'guistate'); const tokenFile = { - funi: path.join(workingDir, 'config', 'funi_token'), cr: path.join(workingDir, 'config', 'cr_token'), hd: path.join(workingDir, 'config', 'hd_token'), hdNew: path.join(workingDir, 'config', 'hd_new_token') @@ -312,33 +310,6 @@ const saveNewHDToken = (data: Record) => { } }; -const loadFuniToken = () => { - const loadedToken = loadYamlCfgFile<{ - token?: string - }>(tokenFile.funi, true); - let token: false|string = false; - if (loadedToken && loadedToken.token) - token = loadedToken.token; - // info if token not set - if(!token){ - console.info('[INFO] Token not set!\n'); - } - return token; -}; - -const saveFuniToken = (data: { - token?: string -}) => { - const cfgFolder = path.dirname(tokenFile.funi); - try{ - fs.ensureDirSync(cfgFolder); - fs.writeFileSync(`${tokenFile.funi}.yml`, yaml.stringify(data)); - } - catch(e){ - console.error('Can\'t save token file to disk!'); - } -}; - const cfgDir = path.join(workingDir, 'config'); const getState = (): GuiState => { @@ -373,8 +344,6 @@ const setState = (state: GuiState) => { export { loadBinCfg, loadCfg, - loadFuniToken, - saveFuniToken, saveCRSession, loadCRSession, saveCRToken, diff --git a/modules/module.downloadArchive.ts b/modules/module.downloadArchive.ts index bdac33c..b324c2c 100644 --- a/modules/module.downloadArchive.ts +++ b/modules/module.downloadArchive.ts @@ -11,9 +11,6 @@ export type ItemType = { }[] export type DataType = { - funi: { - s: ItemType - }, hidive: { s: ItemType }, @@ -24,9 +21,6 @@ export type DataType = { } const addToArchive = (kind: { - service: 'funi', - type: 's' -} | { service: 'crunchy', type: 's'|'srz' } | { @@ -45,16 +39,7 @@ const addToArchive = (kind: { }); (data as any)[kind.service][kind.type] = items; } else { - if (kind.service === 'funi') { - data['funi'] = { - s: [ - { - id: ID, - already: [] - } - ] - }; - } else if (kind.service === 'crunchy') { + if (kind.service === 'crunchy') { data['crunchy'] = { s: ([] as ItemType).concat(kind.type === 's' ? { id: ID, @@ -80,9 +65,6 @@ const addToArchive = (kind: { }; const downloaded = (kind: { - service: 'funi', - type: 's' -} | { service: 'crunchy', type: 's'|'srz' } | { @@ -105,7 +87,7 @@ const downloaded = (kind: { fs.writeFileSync(archiveFile, JSON.stringify(data, null, 4)); }; -const makeCommand = (service: 'funi'|'crunchy'|'hidive') : Partial[] => { +const makeCommand = (service: 'crunchy'|'hidive') : Partial[] => { const data = loadData(); const ret: Partial[] = []; const kind = data[service]; diff --git a/modules/module.langsData.ts b/modules/module.langsData.ts index d139229..af46eee 100644 --- a/modules/module.langsData.ts +++ b/modules/module.langsData.ts @@ -7,19 +7,16 @@ export type LanguageItem = { locale: string, code: string, name: string, - language?: string, - funi_locale?: string, - funi_name?: string, - funi_name_lagacy?: string + language?: string } const languages: LanguageItem[] = [ - { cr_locale: 'en-US', new_hd_locale: 'en-US', hd_locale: 'English', funi_locale: 'enUS', locale: 'en', code: 'eng', name: 'English' }, + { cr_locale: 'en-US', new_hd_locale: 'en-US', hd_locale: 'English', locale: 'en', code: 'eng', name: 'English' }, { cr_locale: 'en-IN', locale: 'en-IN', code: 'eng', name: 'English (India)', }, - { cr_locale: 'es-LA', new_hd_locale: 'es-MX', hd_locale: 'Spanish LatAm', funi_name: 'Spanish (LAS)', funi_name_lagacy: 'Spanish (Latin Am)', funi_locale: 'esLA', locale: 'es-419', code: 'spa', name: 'Spanish', language: 'Latin American Spanish' }, + { cr_locale: 'es-LA', new_hd_locale: 'es-MX', hd_locale: 'Spanish LatAm', locale: 'es-419', code: 'spa', name: 'Spanish', language: 'Latin American Spanish' }, { cr_locale: 'es-419',hd_locale: 'Spanish', locale: 'es-419', code: 'spa-419', name: 'Spanish', language: 'Latin American Spanish' }, { cr_locale: 'es-ES', new_hd_locale: 'es-ES', hd_locale: 'Spanish Europe', locale: 'es-ES', code: 'spa-ES', name: 'Castilian', language: 'European Spanish' }, - { cr_locale: 'pt-BR', new_hd_locale: 'pt-BR', hd_locale: 'Portuguese', funi_name: 'Portuguese (Brazil)', funi_locale: 'ptBR', locale: 'pt-BR', code: 'por', name: 'Portuguese', language: 'Brazilian Portuguese' }, + { cr_locale: 'pt-BR', new_hd_locale: 'pt-BR', hd_locale: 'Portuguese', locale: 'pt-BR', code: 'por', name: 'Portuguese', language: 'Brazilian Portuguese' }, { cr_locale: 'pt-PT', locale: 'pt-PT', code: 'por', name: 'Portuguese (Portugal)', language: 'Portugues (Portugal)' }, { cr_locale: 'fr-FR', hd_locale: 'French', locale: 'fr', code: 'fra', name: 'French' }, { cr_locale: 'de-DE', hd_locale: 'German', locale: 'de', code: 'deu', name: 'German' }, @@ -29,7 +26,7 @@ const languages: LanguageItem[] = [ { cr_locale: 'ru-RU', hd_locale: 'Russian', locale: 'ru', code: 'rus', name: 'Russian' }, { cr_locale: 'tr-TR', hd_locale: 'Turkish', locale: 'tr', code: 'tur', name: 'Turkish' }, { cr_locale: 'hi-IN', locale: 'hi', code: 'hin', name: 'Hindi' }, - { funi_locale: 'zhMN', locale: 'zh', code: 'cmn', name: 'Chinese (Mandarin, PRC)' }, + { locale: 'zh', code: 'cmn', name: 'Chinese (Mandarin, PRC)' }, { cr_locale: 'zh-CN', locale: 'zh-CN', code: 'zho', name: 'Chinese (Mainland China)' }, { cr_locale: 'zh-TW', locale: 'zh-TW', code: 'chi', name: 'Chinese (Taiwan)' }, { cr_locale: 'ko-KR', hd_locale: 'Korean', locale: 'ko', code: 'kor', name: 'Korean' }, @@ -41,7 +38,7 @@ const languages: LanguageItem[] = [ { cr_locale: 'vi-VN', locale: 'vi-VN', code: 'vie', name: 'Vietnamese', language: 'Tiếng Việt' }, { cr_locale: 'id-ID', locale: 'id-ID', code: 'ind', name: 'Indonesian', language: 'Bahasa Indonesia' }, { cr_locale: 'te-IN', locale: 'te-IN', code: 'tel', name: 'Telugu (India)', language: 'తెలుగు' }, - { cr_locale: 'ja-JP', hd_locale: 'Japanese', funi_locale: 'jaJP', locale: 'ja', code: 'jpn', name: 'Japanese' }, + { cr_locale: 'ja-JP', hd_locale: 'Japanese', locale: 'ja', code: 'jpn', name: 'Japanese' }, ]; // add en language names diff --git a/modules/module.req.ts b/modules/module.req.ts index 232abe0..aee4ffc 100644 --- a/modules/module.req.ts +++ b/modules/module.req.ts @@ -27,7 +27,7 @@ const usefulCookies = { // req class Req { private sessCfg: string; - private service: 'cr'|'funi'|'hd'; + private service: 'cr'|'hd'; private session: Record, private debug: boolean, private nosess = false, private type: 'cr'|'funi'|'hd') { + constructor(private domain: Record, private debug: boolean, private nosess = false, private type: 'cr'|'hd') { this.sessCfg = yamlCfg.sessCfgFile[type]; this.service = type; } diff --git a/package.json b/package.json index d93fddd..1a906ad 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,10 @@ "name": "multi-downloader-nx", "short_name": "aniDL", "version": "4.6.3", - "description": "Downloader for Crunchyroll, Funimation, or Hidive via CLI or GUI", + "description": "Downloader for Crunchyroll and Hidive with CLI and GUI", "keywords": [ "download", "downloader", - "funimation", - "funimationnow", "hidive", "crunchy", "crunchyroll", -- 2.45.2