diff --git a/.gitignore b/.gitignore index a9ecb05..0a3122a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ token.yml *.srt *.resume *.user.yml +lib diff --git a/@types/downloadedFile.d.ts b/@types/downloadedFile.d.ts new file mode 100644 index 0000000..9f66467 --- /dev/null +++ b/@types/downloadedFile.d.ts @@ -0,0 +1,4 @@ +export type DownloadedFile = { + path: string, + lang: string +} \ No newline at end of file diff --git a/@types/episode.d.ts b/@types/episode.d.ts new file mode 100644 index 0000000..6d79b67 --- /dev/null +++ b/@types/episode.d.ts @@ -0,0 +1,392 @@ +// Generated by https://quicktype.io + +export interface EpisodeData { + id: number; + title: string; + mediaDict: { [key: string]: string }; + episodeSlug: string; + starRating: number; + parent: EpisodeDataParent; + number: string; + description: string; + filename: string; + seriesBanner: string; + media: Media[]; + externalItemId: string; + contentId: string; + metaItems: MetaItems; + thumb: string; + type: Type; + default: { [key: string]: Default }; + published: boolean; + versions: VersionClass[]; + mediaCategory: string; + order: number; + seriesVersions: any[]; + source: Source; + ids: EpisodeDataIDS; + runtime: string; + siblings: PreviousSeasonEpisode[]; + seriesTitle: string; + seriesSlug: string; + next: Next; + previousSeasonEpisode: PreviousSeasonEpisode; + seasonTitle: string; + quality: Quality; + ratings: Array; + languages: TitleElement[]; + releaseDate: string; + historicalSelections: HistoricalSelections; + userRating: UserRating; +} + +export interface Default { + items: DefaultItem[]; +} + +export interface DefaultItem { + languages: string[]; + territories: string[]; + version: null; + value: Value[]; + devices: any[]; +} + +export interface Value { + name: MetaType; + value: string; + label: Label; +} + +export enum Label { + Rating = "Rating", + RatingSystem = "Rating System", + ReleaseDate = "Release Date", + Synopsis = "Synopsis", + SynopsisType = "Synopsis Type", +} + +export enum MetaType { + Rating = "rating", + RatingSystemType = "RatingSystemType", + ReleaseDate = "release-date", + Synopsis = "synopsis", + Synopsistype = "synopsistype", + VideoRatingType = "VideoRatingType", +} + +export interface HistoricalSelections { + version: string; + language: string; +} + +export interface EpisodeDataIDS { + externalShowId: string; + externalSeasonId: string; + externalEpisodeId: string; +} + +export enum TitleElement { + Empty = "", + English = "English", +} + +export interface Media { + id: number; + title: string; + experienceType: string; + created: string; + createdBy: string; + itemFieldData: Next; + keyPath: string; + filename: string; + complianceStatus: null; + events: any[]; + clients: string[]; + qcStatus: null; + qcStatusDate: null; + image: string; + thumb: string; + ext: string; + avails: Avail[]; + version: string; + startTimecode: null; + endTimecode: null; + versionId: string; + mediaType: string; + status: string; + languages: LanguageClass[]; + territories: any[]; + devices: any[]; + keyType: string; + purpose: null; + externalItemId: null | string; + proxyId: null; + externalDbId: null; + mediaChildren: MediaChild[]; + isDefault: boolean; + parent: MediaChildParent; + filePath: null | string; + mediaInfo: Next; + type: string; + approved: boolean; + mediaKey: string; + itemFields: any[]; + source: Source; + fieldData: Next; + sourceId: null | string; + timecodeOverride: null; + seriesTitle: string; + episodeTitle: string; + genre: any[]; + txDate: string; + description: string; + synopsis: string; + resolution: null; + restrictedAccess: boolean; + createdById: string; + userIdsWithAccess: any[]; + runtime?: number; + language?: TitleElement; + purchased: boolean; +} + +export interface Avail { + id: number; + description: string; + endDate: string; + startDate: string; + ids: AvailIDS; + originalAirDate: null; + physicalReleaseDate: null; + preorderDate: null; + language: TitleElement; + territory: string; + territoryCode: string; + license: string; + parentAvail: null; + item: number; + version: string; + applyToLevel: null; + availLevel: string; + availDisplayCode: string; + availStatus: string; + bundleOnly: boolean; + contentOwnerOrganization: string; + currency: null; + price: null; + purchase: string; + priceValue: string; + resolutionFormat: null; + runtimeMilliseconds: null; + seasonOrEpisodeNumber: null; + tmsid: null; + deviceList: string; + tvodSku: null; +} + +export interface AvailIDS { + externalSeasonId: string; + externalAsianId: null; + externalShowId: string; + externalEpisodeId: string; + externalEnglishId: string; + externalAlphaId: string; +} + +export interface Next { +} + +export interface LanguageClass { + code: string; + id: number; + title: TitleElement; +} + +export interface MediaChild { + id: number; + title: string; + experienceType: string; + created: string; + createdBy: string; + itemFieldData: Next; + keyPath: null; + filename: string; + complianceStatus: null; + events: any[]; + clients: string[]; + qcStatus: null; + qcStatusDate: null; + image: string; + ext: string; + avails: any[]; + version: string; + startTimecode: null; + endTimecode: null; + versionId: string; + mediaType: string; + status: string; + languages: LanguageClass[]; + territories: any[]; + devices: any[]; + keyType: string; + purpose: null; + externalItemId: string; + proxyId: null; + externalDbId: null; + mediaChildren: any[]; + isDefault: boolean; + parent: MediaChildParent; + filePath: string; + mediaInfo: MediaInfo; + type: string; + approved: boolean; + mediaKey: null; + itemFields: any[]; + source: Source; + fieldData: Next; + sourceId: null; + timecodeOverride: null; + seriesTitle: string; + episodeTitle: string; + genre: any[]; + txDate: string; + description: string; + synopsis: string; + resolution: null | string; + restrictedAccess: boolean; + createdById: string; + userIdsWithAccess: any[]; + language: TitleElement; +} + +export interface MediaInfo { + imageAspectRatio: null | string; + format: string; + scanMode: null | string; + burnedInSubtitleLanguage: string; + screenAspectRatio: null | string; + subtitleFormat: null | string; + subtitleContent: null | string; + frameHeight: number | null; + frameWidth: number | null; + video: Video; +} + +export interface Video { + codecId: null | string; + container: null | string; + encodingRate: number | null; + frameRate: null | string; + height: number | null; + width: number | null; + duration: number | null; + bitRate: number | null; +} + +export interface MediaChildParent { + title: string; + type: string; + catalogParent: CatalogParent; + slug: string; + grandparentId: number; + id: number; +} + +export interface CatalogParent { + id: number; + title: string; +} + +export enum Source { + Dbb = "dbb", +} + +export interface MetaItems { + items: Items; + filters: Filters; +} + +export interface Filters { + territory: any[]; + language: any[]; +} + +export interface Items { + "release-date": AnimationProductionStudio; + rating: AnimationProductionStudio; + synopsis: AnimationProductionStudio; + "animation-production-studio": AnimationProductionStudio; +} + +export interface AnimationProductionStudio { + items: AnimationProductionStudioItem[]; + label: string; + id: number; + slug: string; +} + +export interface AnimationProductionStudioItem { + id: number; + metaType: MetaType; + metaTypeId: string; + client: null; + languages: TitleElement; + territories: string; + devices: string; + isDefault: boolean; + value: Value[]; + approved: boolean; + version: null; + source: Source; +} + +export interface EpisodeDataParent { + seasonId: number; + seasonNumber: string; + title: string; + titleSlug: string; + titleType: string; + titleId: number; +} + +export interface PreviousSeasonEpisode { + seasonTitle?: string; + mediaCategory: Type; + thumb: string; + title: string; + image: string; + number: string; + id: number; + version: string[]; + order: number; + slug: string; + season?: number; + languages?: TitleElement[]; +} + +export enum Type { + Episode = "episode", + Ova = "ova", +} + +export interface Quality { + quality: string; + height: number; +} + +export interface UserRating { + overall: number; + ja: number; + eng: number; +} + +export interface VersionClass { + compliance_approved: boolean; + title: string; + version_id: string; + is_default: boolean; + runtime: string; + external_id: string; + id: number; +} diff --git a/@types/hls-download.d.ts b/@types/hls-download.d.ts index 3ce039f..4ed0f6a 100644 --- a/@types/hls-download.d.ts +++ b/@types/hls-download.d.ts @@ -2,9 +2,7 @@ declare module 'hls-download' { export default class hlsDownload { constructor(options: { m3u8json: { - segments: { - - }[], + segments: {}[], mediaSequence?: number, }, output?: string, @@ -16,13 +14,13 @@ declare module 'hls-download' { skipInit?: boolean, timeout?: number }) - async download() : { + async download() : Promise<{ ok: boolean, parts: { first: number, total: number, compleated: number } - } + }> } } \ No newline at end of file diff --git a/@types/items.d.ts b/@types/items.d.ts index 7062bec..b7ac98d 100644 --- a/@types/items.d.ts +++ b/@types/items.d.ts @@ -38,37 +38,11 @@ export interface Item { mostRecentAvod: MostRecent; } -export enum Access { - AVODSimulcastEnglish = "A-VOD_Simulcast_English", - AVODUncutEnglish = "A-VOD_Uncut_English", - SVODSimulcastEnglish = "SVOD_Simulcast_English", - SVODUncutEnglish = "SVOD_Uncut_English", -} - -export enum AltAvail { - MostRecentSvodJpnUs = "most_recent_svod_jpn_us", -} - -export enum Audio { - English = "English", -} - export enum ContentType { Episode = "episode", Ova = "ova", } -export enum EngAllTerritoryAvail { - MostRecentSvodEngAllTerr = "most_recent_svod_eng_all_terr", -} - -export enum Genre { - ActionAdventure = "Action/Adventure", - Comedy = "Comedy", - Drama = "Drama", - Fantasy = "Fantasy", -} - export interface IDs { externalShowId: ID; externalSeasonId: ExternalSeasonID; @@ -76,47 +50,26 @@ export interface IDs { externalAsianId?: string } -export enum ExternalSeasonID { - TrsS11 = "TRS-S1-1", - TrsS22 = "TRS-S2-2", -} - -export enum ID { - Trs = "TRS", -} - export interface Item { - seasonTitle: SeasonTitle; + seasonTitle: string; seasonId: number; episodeOrder: number; episodeSlug: string; created: Date; - titleSlug: TitleSlug; + titleSlug: string; episodeNum: string; episodeId: number; titleId: number; seasonNum: string; ratings: Array; showImage: string; - titleName: TitleName; + titleName: string; runtime: string; episodeName: string; seasonOrder: number; - titleExternalId: ID; + titleExternalId: string; } -export enum SeasonTitle { - Season1 = "Season 1", - Season2 = "Season 2", -} - -export enum TitleName { - ThatTimeIGotReincarnatedAsASlime = "That Time I Got Reincarnated as a Slime", -} - -export enum TitleSlug { - ThatTimeIGotReincarnatedAsASlime = "that-time-i-got-reincarnated-as-a-slime", -} export interface MostRecent { image?: string; @@ -147,14 +100,6 @@ export interface MostRecent { purchased?: boolean; } -export enum Device { - All = "All", -} - -export enum Distributor { - FunimationVenue = "FunimationVenue", -} - export interface MostRecentAvodIDS { externalSeasonId: ExternalSeasonID; externalAsianId: null; @@ -171,14 +116,6 @@ export enum Purchase { Svod = "SVOD", } -export enum MostRecentAvodQuality { - HD1080 = "HD 1080", -} - -export enum Territory { - Usa = "USA", -} - export enum Version { Simulcast = "Simulcast", Uncut = "Uncut", @@ -187,10 +124,6 @@ export enum Version { export interface MostRecentSvodJpnUs { } -export enum PrimaryAvail { - MostRecentSvodUs = "most_recent_svod_us", -} - export interface QualityClass { quality: QualityQuality; height: number; diff --git a/@types/m3u8-parsed.d.ts b/@types/m3u8-parsed.d.ts index f16f908..437a2a3 100644 --- a/@types/m3u8-parsed.d.ts +++ b/@types/m3u8-parsed.d.ts @@ -16,7 +16,7 @@ declare module 'm3u8-parsed' { timeline: number }[], version: number, - mediaGroups?: { + mediaGroups: { [type: string]: { [index: string]: { [language: string]: { @@ -28,7 +28,7 @@ declare module 'm3u8-parsed' { } } }, - playlists?: { + playlists: { uri: string, timeline: number, attributes: { diff --git a/@types/pkg.d.ts b/@types/pkg.d.ts new file mode 100644 index 0000000..86ad545 --- /dev/null +++ b/@types/pkg.d.ts @@ -0,0 +1,3 @@ +declare module 'pkg' { + export async function exec(config: string[]); +} \ No newline at end of file diff --git a/@types/removeNPMAbsolutePaths.d.ts b/@types/removeNPMAbsolutePaths.d.ts new file mode 100644 index 0000000..eae351b --- /dev/null +++ b/@types/removeNPMAbsolutePaths.d.ts @@ -0,0 +1,3 @@ +declare module 'removeNPMAbsolutePaths' { + export default async function modulesCleanup(path: string); +} \ No newline at end of file diff --git a/@types/sei-helper.d.ts b/@types/sei-helper.d.ts index 224c060..2cd732e 100644 --- a/@types/sei-helper.d.ts +++ b/@types/sei-helper.d.ts @@ -1,3 +1,5 @@ declare module 'sei-helper' { export async function question(qStr: string): string; + export function cleanupFilename(str: string): string; + export function exec(str: string, str1: string, str2: string); } \ No newline at end of file diff --git a/@types/streamData.d.ts b/@types/streamData.d.ts new file mode 100644 index 0000000..5ddee55 --- /dev/null +++ b/@types/streamData.d.ts @@ -0,0 +1,28 @@ +// Generated by https://quicktype.io + +export interface StreamData { + items: Item[]; + watchHistorySaveInterval: number; + errors?: Error[] +} + +export interface Error { + detail: string, + code: number +} + +export interface Item { + src: string; + kind: string; + isPromo: boolean; + videoType: string; + aips: Aip[]; + experienceId: string; + showAds: boolean; + id: number; +} + +export interface Aip { + out: number; + in: number; +} diff --git a/@types/subtitleObject.d.ts b/@types/subtitleObject.d.ts new file mode 100644 index 0000000..266fe10 --- /dev/null +++ b/@types/subtitleObject.d.ts @@ -0,0 +1,7 @@ +export type Subtitle = { + path: string, + ext: string, + langName: string, + language: string, + file?: string +} \ No newline at end of file diff --git a/funi.ts b/funi.ts index 815b304..388194d 100644 --- a/funi.ts +++ b/funi.ts @@ -5,7 +5,7 @@ import fs from 'fs'; import path from 'path'; // package json -const packageJson = JSON.parse(fs.readFileSync('./package.json').toString()) +import packageJson from './package.json'; // program name console.log(`\n=== Funimation Downloader NX ${packageJson.version} ===\n`); @@ -32,8 +32,12 @@ const argv = appYargs.appArgv(cfg.cli); // Import modules after argv has been exported import getData from './modules/module.getdata.js'; -import merger from './modules/module.merger'; +import merger, { SubtitleInput } from './modules/module.merger'; import parseSelect from './modules/module.parseSelect'; +import { EpisodeData, MediaChild } from './@types/episode'; +import { Subtitle } from './@types/subtitleObject'; +import { StreamData } from './@types/streamData'; +import { DownloadedFile } from './@types/downloadedFile'; // check page argv.p = 1; @@ -41,11 +45,14 @@ argv.p = 1; // fn variables let title = '', showTitle = '', - fnEpNum = 0, - fnOutput = [], + fnEpNum: string|number = 0, + fnOutput: string[] = [], season = 0, - tsDlPath = [], - stDlPath = []; + tsDlPath: { + path: string, + lang: string, + }[] = [], + stDlPath: Subtitle[] = []; // main (async () => { @@ -81,7 +88,7 @@ async function auth(){ 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', authData.token.slice(0,8),'*'.repeat(32)); + console.log('[INFO] Authentication success, your token: %s%s\n', resJSON.token.slice(0,8),'*'.repeat(32)); yamlCfg.saveFuniToken({'token': resJSON.token}); } else { console.log('[ERROR]%s\n', ' No token found'); @@ -199,7 +206,10 @@ async function getShow(){ epSelList = parseSelect(argv.e as string); - let fnSlug = [], is_selected = false; + let fnSlug: { + title: string, + episode: string + }[] = [], is_selected = false; let eps = epsDataArr; epsDataArr.sort((a, b) => { @@ -226,21 +236,21 @@ async function getShow(){ is_selected = false; } // console vars - let tx_snum = eps[e].item.seasonNum==1?'':` S${eps[e].item.seasonNum}`; + let tx_snum = eps[e].item.seasonNum=='1'?'':` S${eps[e].item.seasonNum}`; let tx_type = eps[e].mediaCategory != 'episode' ? eps[e].mediaCategory : ''; let tx_enum = eps[e].item.episodeNum && eps[e].item.episodeNum !== '' ? - `#${(eps[e].item.episodeNum < 10 ? '0' : '')+eps[e].item.episodeNum}` : '#'+eps[e].item.episodeId; + `#${(parseInt(eps[e].item.episodeNum) < 10 ? '0' : '')+eps[e].item.episodeNum}` : '#'+eps[e].item.episodeId; let qua_str = eps[e].quality.height ? eps[e].quality.quality + eps[e].quality.height : 'UNK'; let aud_str = eps[e].audio.length > 0 ? `, ${eps[e].audio.join(', ')}` : ''; let rtm_str = eps[e].item.runtime !== '' ? eps[e].item.runtime : '??:??'; // console string - eps[e].id_split[0] = eps[e].id_split[0].padStart(typeIdLen, ' '); + eps[e].id_split[0] = eps[e].id_split[0].toString().padStart(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 == e ? '\n' : ''; + conOut += eps.length-1 == parseInt(e) ? '\n' : ''; console.log(conOut); } if(fnSlug.length < 1){ @@ -256,17 +266,19 @@ async function getShow(){ } -async function getEpisode(fnSlug){ +async function getEpisode(fnSlug: { + title: string, + episode: string +}) { let episodeData = await getData({ baseUrl: api_host, url: `/source/catalog/episode/${fnSlug.title}/${fnSlug.episode}/`, token: token, useToken: true, - useProxy: true, debug: argv.debug, }); - if(!episodeData.ok){return;} - let ep = JSON.parse(episodeData.res.body).items[0], streamIds = []; + if(!episodeData.ok || !episodeData.res){return;} + let ep = JSON.parse(episodeData.res.body).items[0] as EpisodeData, streamIds = []; // build fn showTitle = ep.parent.title; title = ep.title; @@ -296,7 +308,7 @@ async function getEpisode(fnSlug){ // map medias let media = ep.media.map(function(m){ if(m.mediaType == 'experience'){ - if(m.version.match(/uncut/i)){ + if(m.version.match(/uncut/i) && m.language){ uncut[m.language] = true; } return { @@ -326,16 +338,24 @@ async function getEpisode(fnSlug){ let selected = false; if(m.id > 0 && m.type == 'Non-Encrypted'){ let dub_type = m.language; - let localSubs = []; - let selUncut = !argv.simul && uncut[dub_type] && m.version.match(/uncut/i) + if (!dub_type) + continue; + let localSubs: Subtitle[] = []; + let selUncut = !argv.simul && uncut[dub_type] && m.version?.match(/uncut/i) ? true - : (!uncut[dub_type] || argv.simul && m.version.match(/simulcast/i) ? true : false); - for (let curDub of argv.dub) { + : (!uncut[dub_type] || argv.simul && m.version?.match(/simulcast/i) ? true : false); + for (let curDub of (argv.dub as appYargs.possibleDubs)) { if(dub_type == dubType[curDub] && selUncut){ streamIds.push({ id: m.id, lang: merger.getLanguageCode(curDub, curDub.slice(0, -2)) }); + if (!m.subtitles) { + console.log('[ERROR] Unable to find subs for episode ', m.id) + if (argv.debug) + console.log(m) + continue; + } stDlPath.push(...m.subtitles); localSubs = m.subtitles; selected = true; @@ -347,7 +367,7 @@ async function getEpisode(fnSlug){ } } - let already = []; + let already: string[] = []; stDlPath = stDlPath.filter(a => { if (already.includes(a.language)) { return false; @@ -369,20 +389,19 @@ async function getEpisode(fnSlug){ token: token, dinstid: 'uuid', useToken: true, - useProxy: true, debug: argv.debug, }); - if(!streamData.ok){return;} - streamData = JSON.parse(streamData.res.body); - if(streamData.errors){ - console.log('[ERROR] Error #%s: %s\n',streamData.errors[0].code,streamData.errors[0].detail); + if(!streamData.ok || !streamData.res){return;} + const streamDataRes = JSON.parse(streamData.res.body) as StreamData; + if(streamDataRes.errors){ + console.log('[ERROR] Error #%s: %s\n',streamDataRes.errors[0].code,streamDataRes.errors[0].detail); return; } else{ - for(let u in streamData.items){ - if(streamData.items[u].videoType == 'm3u8'){ + for(let u in streamDataRes.items){ + if(streamDataRes.items[u].videoType == 'm3u8'){ tsDlPath.push({ - path: streamData.items[u].src, + path: streamDataRes.items[u].src, lang: streamId.lang }); break; @@ -400,12 +419,12 @@ async function getEpisode(fnSlug){ } } -function getSubsUrl(m){ +function getSubsUrl(m: MediaChild[]) : Subtitle[] { if(argv.nosubs && !argv.sub){ return []; } - let subLangs = argv.subLang; + let subLangs = argv.subLang as appYargs.possibleSubs; const subType = { 'enUS': 'English', @@ -419,7 +438,7 @@ function getSubsUrl(m){ subLangs = [ 'enUS' ]; } - let found = []; + let found: Subtitle[] = []; for(let i in m){ let fpp = m[i].filePath.split('.'); @@ -443,16 +462,15 @@ async function downloadStreams(){ // req playlist - let purvideo = []; - let puraudio = []; - let audioAndVideo = []; + let purvideo: DownloadedFile[] = []; + let puraudio: DownloadedFile[] = []; + let audioAndVideo: DownloadedFile[] = []; for (let streamPath of tsDlPath) { let plQualityReq = await getData({ url: streamPath.path, - useProxy: (argv.ssp ? false : true), debug: argv.debug, }); - if(!plQualityReq.ok){return;} + if(!plQualityReq.ok || !plQualityReq.res){return;} let plQualityLinkList = m3u8(plQualityReq.res.body); @@ -463,27 +481,43 @@ async function downloadStreams(){ 'funiprod.akamaized.net', ]; - let plServerList = [], - plStreams = {}, + let plServerList: string[] = [], + plStreams: Record = {}, plLayersStr = [], - plLayersRes = {}, + plLayersRes: Record = {}, plMaxLayer = 1, plNewIds = 1, - plAud = { uri: '' }; + plAud: { + uri: string, + langStr: string, + language: string + } = { uri: '', langStr: '', language: '' }; // new uris let vplReg = /streaming_video_(\d+)_(\d+)_(\d+)_index\.m3u8/; if(plQualityLinkList.playlists[0].uri.match(vplReg)){ let audioKey = Object.keys(plQualityLinkList.mediaGroups.AUDIO).pop(); + if (!audioKey) + return console.log('[ERROR] No audio key found') if(plQualityLinkList.mediaGroups.AUDIO[audioKey]){ - let audioData = plQualityLinkList.mediaGroups.AUDIO[audioKey], - audioEl = Object.keys(audioData); - audioData = audioData[audioEl[0]]; + const audioDataParts = plQualityLinkList.mediaGroups.AUDIO[audioKey], + audioEl = Object.keys(audioDataParts); + const audioData = audioDataParts[audioEl[0]]; plAud = { ...audioData, ...{ langStr: audioEl[0] } }; } plQualityLinkList.playlists.sort((a, b) => { - let av = parseInt(a.uri.match(vplReg)[3]); - let bv = parseInt(b.uri.match(vplReg)[3]); + const aMatch = a.uri.match(vplReg), bMatch = b.uri.match(vplReg); + if (!aMatch || !bMatch) { + console.log('[ERROR] Unable to match') + return 0; + } + let av = parseInt(aMatch[3]); + let bv = parseInt(bMatch[3]); if(av > bv){ return 1; } @@ -497,9 +531,10 @@ async function downloadStreams(){ for(let s of plQualityLinkList.playlists){ if(s.uri.match(/_Layer(\d+)\.m3u8/) || s.uri.match(vplReg)){ // set layer and max layer - let plLayerId = 0; - if(s.uri.match(/_Layer(\d+)\.m3u8/)){ - plLayerId = parseInt(s.uri.match(/_Layer(\d+)\.m3u8/)[1]); + let plLayerId: number|string = 0; + const match = s.uri.match(/_Layer(\d+)\.m3u8/); + if(match){ + plLayerId = parseInt(match[1]); } else{ plLayerId = plNewIds, plNewIds++; @@ -546,10 +581,7 @@ async function downloadStreams(){ break; } } - - if(typeof argv.q == 'object' && argv.q.length > 1){ - argv.q = argv.q[argv.q.length-1]; - } + argv.q = argv.q < 1 || argv.q > plMaxLayer ? plMaxLayer : argv.q; @@ -567,7 +599,7 @@ async function downloadStreams(){ fnOutput = parseFileName(argv.fileName, title, fnEpNum, showTitle, season, plLayersRes[argv.q].width, plLayersRes[argv.q].height); if (fnOutput.length < 1) - throw new Error('Invalid path', fnOutput); + throw new Error(`Invalid path generated for input ${argv.fileName}`); console.log(`[INFO] Output filename: ${fnOutput.join(path.sep)}.ts`); } else if(argv.x > plServerList.length){ @@ -593,10 +625,9 @@ async function downloadStreams(){ // download video let reqVideo = await getData({ url: videoUrl, - useProxy: (argv.ssp ? false : true), debug: argv.debug, }); - if (!reqVideo.ok) { break video; } + if (!reqVideo.ok || !reqVideo.res) { break video; } let chunkList = m3u8(reqVideo.res.body); @@ -625,10 +656,9 @@ async function downloadStreams(){ break audio; let reqAudio = await getData({ url: plAud.uri, - useProxy: (argv.ssp ? false : true), debug: argv.debug, }); - if (!reqAudio.ok) { return; } + if (!reqAudio.ok || !reqAudio.res) { return; } let chunkListA = m3u8(reqAudio.res.body); @@ -654,10 +684,9 @@ async function downloadStreams(){ for (let subObject of stDlPath) { let subsSrc = await getData({ url: subObject.path, - useProxy: true, debug: argv.debug, }); - if(subsSrc.ok){ + if(subsSrc.ok && subsSrc.res){ let assData = vttConvert(subsSrc.res.body, (subsExt == '.srt' ? true : false), subObject.langName, argv.fontSize); subObject.file = path.join(cfg.dir.content, ...fnOutput.slice(0, -1), `${fnOutput.slice(-1)}.subtitle${subObject.ext}${subsExt}`); fs.writeFileSync(subObject.file, assData); @@ -683,7 +712,7 @@ async function downloadStreams(){ } // check exec - const mergerBin = await merger.checkMerger(cfg.bin, argv.mp4); + const mergerBin = merger.checkMerger(cfg.bin, argv.mp4); if ( argv.novids ){ console.log('[INFO] Video not downloaded. Skip muxing video.'); @@ -697,16 +726,22 @@ async function downloadStreams(){ console.log('[WARN] FFmpeg not found...'); } + const ffext = !argv.mp4 ? 'mkv' : 'mp4'; + const mergeInstance = new merger({ + onlyAudio: puraudio, + onlyVid: purvideo, + output: `${path.join(cfg.dir.content, ...fnOutput)}.${ffext}`, + subtitels: stDlPath as SubtitleInput[], + videoAndAudio: audioAndVideo, + simul: argv.simul + }) + if(!argv.mp4 && mergerBin.MKVmerge){ - let ffext = !argv.mp4 ? 'mkv' : 'mp4'; - let command = merger.buildCommandMkvMerge(argv.simul, audioAndVideo, purvideo, puraudio, stDlPath, `${path.join(cfg.dir.content, - ...fnOutput)}.${ffext}`); + let command = mergeInstance.MkvMerge(); shlp.exec('mkvmerge', `"${mergerBin.MKVmerge}"`, command); } else if(mergerBin.FFmpeg){ - let ffext = !argv.mp4 ? 'mkv' : 'mp4'; - let command = merger.buildCommandFFmpeg(argv.simul, audioAndVideo, purvideo, puraudio, stDlPath, `${path.join(cfg.dir.content, - ...fnOutput)}.${ffext}`); + let command = mergeInstance.FFmpeg(); shlp.exec('ffmpeg',`"${mergerBin.FFmpeg}"`,command); } else{ @@ -717,34 +752,28 @@ async function downloadStreams(){ return; audioAndVideo.concat(puraudio).concat(purvideo).forEach(a => fs.unlinkSync(a.path)); - stDlPath.forEach(subObject => fs.unlinkSync(subObject.file)); + stDlPath.forEach(subObject => subObject.file && fs.unlinkSync(subObject.file)); console.log('\n[INFO] Done!\n'); } -async function downloadFile(filename, chunkList) { +async function downloadFile(filename: string, chunkList: { + segments: {}[], +}) { const downloadStatus = await new hlsDownload({ m3u8json: chunkList, output: `${filename + '.ts'}`, timeout: argv.timeout, - pcount: argv.partsize + threads: argv.partsize }).download(); return downloadStatus.ok; } -/** - * @param {string} input - * @param {string} title - * @param {number|string} episode - * @param {string} showTitle - * @param {number} season - * @param {number} width - * @param {number} height - * @returns {Array} - */ -function parseFileName(input, title, episode, showTitle, season, width, height) { +function parseFileName(input: string, title: string, episode:number|string, showTitle: string, season: number, width: number, height: number): string[] { const varRegex = /\${[A-Za-z1-9]+}/g; const vars = input.match(varRegex); + if (!vars) + return [input]; for (let i = 0; i < vars.length; i++) { const type = vars[i]; switch (type.slice(2, -1).toLowerCase()) { @@ -754,7 +783,7 @@ function parseFileName(input, title, episode, showTitle, season, width, height) case 'episode': { if (typeof episode === 'number') { let len = episode.toFixed(0).toString().length; - input = input.replace(vars[i], len < argv.numbers ? '0'.repeat(argv.numbers - len) + episode : episode); + input = input.replace(vars[i], len < argv.numbers ? '0'.repeat(argv.numbers - len) + episode : episode.toString()); } else { input = input.replace(vars[i], episode); } @@ -765,14 +794,14 @@ function parseFileName(input, title, episode, showTitle, season, width, height) break; case 'season': { let len = season.toFixed(0).toString().length; - input = input.replace(vars[i], len < argv.numbers ? '0'.repeat(argv.numbers - len) + season : season); + input = input.replace(vars[i], len < argv.numbers ? '0'.repeat(argv.numbers - len) + season : season.toString()); break; } case 'width': - input = input.replace(vars[i], width); + input = input.replace(vars[i], width.toString()); break; case 'height': - input = input.replace(vars[i], height); + input = input.replace(vars[i], height.toString()); break; default: break; diff --git a/modules/build.js b/modules/build.ts similarity index 89% rename from modules/build.js rename to modules/build.ts index 5670c2e..84199ab 100644 --- a/modules/build.js +++ b/modules/build.ts @@ -1,10 +1,10 @@ #!/usr/bin/env node // build requirements -const pkg = require('../package.json'); -const fs = require('fs-extra'); -const modulesCleanup = require('removeNPMAbsolutePaths'); -const { exec } = require('pkg'); +import fs from 'fs-extra'; +import pkg from '../package.json'; +import modulesCleanup from 'removeNPMAbsolutePaths'; +import { exec } from 'pkg'; const buildsDir = './_builds'; const nodeVer = 'node14-'; @@ -50,6 +50,7 @@ const nodeVer = 'node14-'; fs.copySync('./config/dir-path.yml', `${buildDir}/config/dir-path.yml`); fs.copySync('./modules/cmd-here.bat', `${buildDir}/cmd-here.bat`); fs.copySync('./modules/NotoSans-Regular.ttf', `${buildDir}/NotoSans-Regular.ttf`); + fs.copySync('./package.json', `${buildDir}/package.json`) fs.copySync('./docs/', `${buildDir}/docs/`); fs.copySync('./LICENSE.md', `${buildDir}/docs/LICENSE.md`); if(fs.existsSync(`${buildsDir}/${buildFull}.7z`)){ @@ -58,7 +59,7 @@ const nodeVer = 'node14-'; require('child_process').execSync(`7z a -t7z "${buildsDir}/${buildFull}.7z" "${buildDir}"`,{stdio:[0,1,2]}); }()); -function getTarget(bt){ +function getTarget(bt: string) : string { switch(bt){ case 'win64': return 'windows-x64'; diff --git a/modules/module.app-args.ts b/modules/module.app-args.ts index 74f1380..fa2e255 100644 --- a/modules/module.app-args.ts +++ b/modules/module.app-args.ts @@ -9,20 +9,26 @@ const availableFilenameVars = [ 'height' ]; -const subLang = ['enUS', 'esLA', 'ptBR']; -const dubLang = ['enUS', 'esLA', 'ptBR', 'zhMN', 'jaJP']; +export type possibleDubs = ( + 'enUS' | 'esLA' | 'ptBR' | 'zhMN' | 'jaJP' +)[]; +export type possibleSubs = ( + 'enUS' | 'esLA' | 'ptBR' +)[]; +const subLang: possibleSubs = ['enUS', 'esLA', 'ptBR']; +const dubLang: possibleDubs = ['enUS', 'esLA', 'ptBR', 'zhMN', 'jaJP']; + const appArgv = (cfg: { [key: string]: unknown }) => { // init - const parseDefault = (key: string, _default: unknown) => { + const parseDefault = (key: string, _default: T) : T=> { if (Object.prototype.hasOwnProperty.call(cfg, key)) { - return cfg[key]; + return cfg[key] as T; } else return _default; }; - const argv = yargs.parserConfiguration({ 'duplicate-arguments-array': true, 'camel-case-expansion': false @@ -59,27 +65,27 @@ const appArgv = (cfg: { group: 'Downloading:', describe: 'Used to download all episodes from the show', type: 'boolean', - default: parseDefault('all', false) + default: parseDefault('all', false) }) .option('partsize', { group: 'Downloading:', describe: 'The amount of parts that should be downloaded in paralell', type: 'number', - default: parseDefault('partsize', 10) + default: parseDefault('partsize', 10) }) // quality .option('q', { group: 'Downloading:', describe: 'Select video layer (0 is max)', choices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - default: parseDefault('videoLayer', 7), + default: parseDefault('videoLayer', 7), type: 'number', }) // alt listing .option('alt', { group: 'Downloading:', describe: 'Alternative episode listing (if available)', - default: parseDefault('altList', false), + default: parseDefault('altList', false), type: 'boolean', }) // switch to subs @@ -87,20 +93,20 @@ const appArgv = (cfg: { group: 'Downloading:', describe: 'Download non-Japanese Dub (English Dub mode by default)', choices: dubLang, - default: parseDefault('dub', 'enUS'), + default: parseDefault('dub', ['enUS']), type: 'array', }) .option('subLang', { group: 'Downloading:', describe: 'Set the subtitle language (English is default and fallback)', - default: parseDefault('subLang', 'enUS'), + default: parseDefault('subLang', ['enUS']), choices: subLang, type: 'array' }) .option('fontSize', { group: 'Downloading:', describe: 'Used to set the fontsize of the subtitles', - default: parseDefault('fontSize', 55), + default: parseDefault('fontSize', 55), type: 'number' }) .option('allSubs', { @@ -119,7 +125,7 @@ const appArgv = (cfg: { .option('simul', { group: 'Downloading:', describe: 'Force downloading simulcast ver. instead of uncut ver. (if uncut ver. available)', - default: parseDefault('forceSimul', false), + default: parseDefault('forceSimul', false), type: 'boolean', }) // server number @@ -128,7 +134,7 @@ const appArgv = (cfg: { group: 'Downloading:', describe: 'Select server', choices: [1, 2, 3, 4], - default: parseDefault('nServer', 1), + default: parseDefault('nServer', 1), type: 'number', }) // skip @@ -153,19 +159,19 @@ const appArgv = (cfg: { .option('proxy', { group: 'Proxy:', describe: 'Set http(s)/socks proxy WHATWG url', - default: parseDefault('proxy', false), + default: parseDefault('proxy', false), hidden: true, }) .option('proxy-auth', { group: 'Proxy:', describe: 'Colon-separated username and password for proxy', - default: parseDefault('proxy_auth', false), + default: parseDefault('proxy_auth', false), hidden: true, }) .option('ssp', { group: 'Proxy:', describe: 'Don\'t use proxy for stream and subtitles downloading', - default: parseDefault('proxy_ssp', false), + default: parseDefault('proxy_ssp', false), hidden: true, type: 'boolean', }) @@ -178,7 +184,7 @@ const appArgv = (cfg: { .option('mp4', { group: 'Muxing:', describe: 'Mux into mp4', - default: parseDefault('mp4mux', false), + default: parseDefault('mp4mux', false), type: 'boolean' }) // filenaming @@ -187,20 +193,20 @@ const appArgv = (cfg: { describe: `Set the filename template. Use \${variable_name} to insert variables.\nYou may use ${availableFilenameVars .map(a => `'${a}'`).join(', ')} as variables.`, type: 'string', - default: parseDefault('fileName', '[Funimation] ${showTitle} - ${episode} [${height}p]') + default: parseDefault('fileName', '[Funimation] ${showTitle} - ${episode} [${height}p]') }) .option('numbers', { group: 'Filename Template:', describe: `Set how long a number in the title should be at least.\n${[[3, 5, '005'], [2, 1, '01'], [1, 20, '20']] .map(val => `Set in config: ${val[0]}; Episode number: ${val[1]}; Output: ${val[2]}`).join('\n')}`, type: 'number', - default: parseDefault('numbers', 2) + default: parseDefault('numbers', 2) }) // util .option('nocleanup', { group: 'Utilities:', describe: 'Dont\'t delete the input files after muxing', - default: parseDefault('noCleanUp', false), + default: parseDefault('noCleanUp', false), type: 'boolean' }) .option('timeout', { diff --git a/modules/module.getdata.ts b/modules/module.getdata.ts index 4e851f3..e40a2e3 100644 --- a/modules/module.getdata.ts +++ b/modules/module.getdata.ts @@ -26,7 +26,7 @@ export type Options = { }, useToken?: boolean, token?: string|boolean, - dinstid?: boolean, + dinstid?: boolean|string, debug?: boolean } const getData = async (options: Options) => { diff --git a/modules/module.merger.ts b/modules/module.merger.ts index e2b51e9..672e510 100644 --- a/modules/module.merger.ts +++ b/modules/module.merger.ts @@ -177,6 +177,29 @@ class Merger { return args.join(' '); }; + public static checkMerger(bin: { + mkvmerge?: string, + ffmpeg?: string + }, useMP4format: boolean) { + const merger: { + MKVmerge: undefined|string|false, + FFmpeg: undefined|string|false + } = { + MKVmerge: bin.mkvmerge, + FFmpeg: bin.ffmpeg, + }; + if( !useMP4format && !merger.MKVmerge ){ + console.log('[WARN] MKVMerge not found, skip using this...'); + merger.MKVmerge = false; + } + if( !merger.MKVmerge && !merger.FFmpeg || useMP4format && !merger.FFmpeg ){ + console.log('[WARN] FFmpeg not found, skip using this...'); + merger.FFmpeg = false; + } + return merger; + + } + } export default Merger; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 08caaaa..a2dd9ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "eslint": "^7.30.0", "pkg": "^5.3.3", "removeNPMAbsolutePaths": "^2.0.0", + "ts-node": "^10.4.0", "typescript": "^4.4.4" } }, @@ -169,6 +170,27 @@ "to-fast-properties": "^2.0.0" } }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -274,6 +296,30 @@ "node": ">= 6" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "node_modules/@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -569,6 +615,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -643,6 +698,12 @@ "readable-stream": "^2.0.6" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -927,6 +988,12 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1128,6 +1195,15 @@ "node": ">=0.10" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2334,6 +2410,12 @@ "global": "^4.4.0" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3561,6 +3643,59 @@ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true }, + "node_modules/ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/tslib": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", @@ -3861,6 +3996,15 @@ "engines": { "node": ">=10" } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } }, "dependencies": { @@ -3973,6 +4117,21 @@ "to-fast-properties": "^2.0.0" } }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -4051,6 +4210,30 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -4251,6 +4434,12 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -4306,6 +4495,12 @@ "readable-stream": "^2.0.6" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -4519,6 +4714,12 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4656,6 +4857,12 @@ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -5608,6 +5815,12 @@ "global": "^4.4.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6534,6 +6747,34 @@ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true }, + "ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true + } + } + }, "tslib": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", @@ -6759,6 +7000,12 @@ "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/package.json b/package.json index 7f224fd..42c0632 100644 --- a/package.json +++ b/package.json @@ -44,12 +44,17 @@ "eslint": "^7.30.0", "pkg": "^5.3.3", "removeNPMAbsolutePaths": "^2.0.0", + "ts-node": "^10.4.0", "typescript": "^4.4.4" }, "scripts": { - "build-win64": "node modules/build win64", - "build-linux64": "node modules/build linux64", - "build-macos64": "node modules/build macos64", + "tsc": "ts-node tsc.ts", + "prebuild-win64": "npm run tsc", + "prebuild-linux64": "npm run tsc", + "prebuild-maxos64": "npm run tsc", + "build-win64": "cd lib && node modules/build win64", + "build-linux64": "cd lib && node modules/build linux64", + "build-macos64": "cd lib && node modules/build macos64", "eslint": "eslint *.js modules", "eslint-fix": "eslint *.js modules --fix", "test": "echo \"Error: no test specified\" && exit 1" diff --git a/tsc.ts b/tsc.ts new file mode 100644 index 0000000..6d28af0 --- /dev/null +++ b/tsc.ts @@ -0,0 +1,63 @@ +import { exec } from "child_process"; +import fs from "fs"; +import path from "path"; +import { removeSync, copyFileSync } from "fs-extra"; + +const ignore = [ + '.git', + 'lib', + 'node_modules', + '@types' +].map(a => path.join(__dirname, a)); + +(async () => { + removeSync('lib'); + const tsc = exec('npx tsc'); + tsc.stdout?.on("data", console.log); + tsc.stderr?.on("data", console.log); + + tsc.on("close", () => { + const files = readDir(__dirname); + const filtered = files.filter(a => { + if (a.stats.isFile()) { + return a.path.split('.').pop() !== 'ts'; + } else { + return true + } + }) + filtered.forEach(item => { + const itemPath = path.join(__dirname, 'lib', item.path.replace(__dirname, '')); + if (item.stats.isDirectory()) { + if (!fs.existsSync(itemPath)) + fs.mkdirSync(itemPath) + } else { + copyFileSync(item.path, itemPath) + } + }) + }) +})() + +const readDir = (dir: string) : { + path: string, + stats: fs.Stats +}[] => { + const items: { + path: string, + stats: fs.Stats + }[] = []; + const content = fs.readdirSync(dir); + for (const item of content) { + const itemPath = path.join(dir, item); + if (ignore.some(a => itemPath.startsWith(a))) + continue; + const stats = fs.statSync(itemPath); + items.push({ + path: itemPath, + stats + }); + if (stats.isDirectory()) { + items.push(...readDir(itemPath)) + } + } + return items; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c9f603c..d25803f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ + "outDir": "./lib", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ @@ -63,7 +63,12 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "resolveJsonModule": true, "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } + }, + "exclude": [ + "./videos", + "./tsc.ts" + ] }